Directory Structure

Repository

nodejs-cloudflare/
  src/                        source (see below)
  test/                       Jest specs, mirroring src/
  lib/                        build output — generated, never committed
  dist/                       docs, coverage, and book output — generated
  docs/                       this book
    getting-started/
    guides/
    reference/
    release-notes/
    changes-log/
    styles/website.css        HonKit theme
    book.json                 HonKit configuration
    SUMMARY.md                HonKit table of contents
  docker/                     documentation site image
  .github/                    workflows and templates
  eslint.config.ts            flat ESLint config, the documentation rules, the Hono boundary
  rollup.config.ts            two entries, the externals list, and the check that enforces it
  typedoc.json                API reference generation, with validation
  jest.config.json            test and coverage configuration
  tsconfig*.json              compiler configuration
  wrangler.json               Pages config for the documentation site, not for the package

src/

src/
  index.ts                    public API barrel — the main published surface
  core/                       the domain layer — what this library does
    index.ts
    bindings/                 one accessor per Cloudflare binding
      index.ts
      Binding.ts              base: holds a binding name, resolves it from env per call
      AIBinding.ts, AnalyticsBinding.ts, D1Binding.ts, DurableObjectBinding.ts,
      HyperdriveBinding.ts, KVBinding.ts, QueueBinding.ts, R2Binding.ts,
      RateLimitBinding.ts, VectorizeBinding.ts
    services/                 one capability service per accessor
      index.ts
      KVService.ts            implements CacheStore
      D1Service.ts            implements DataStore
      R2Service.ts            implements ObjectStore
      QueueService.ts         implements MessageQueue
      …                       ten in total
      CloudflareRequestMetadata.ts  the one reader of cf-ray, cf-connecting-ip, request.cf
  middlewares/                the Hono delivery layer — separate subpath export
    index.ts                  the ./middlewares entry
    SecurityMiddleware.ts     CORS, origin guard, client-IP capture, API-key auth
    logger.ts                 logToAnalytics — one log line + one metric per request
  constants/
    index.ts
    Defaults.ts               cloudflareDefaults
  exceptions/
    index.ts
    MissingBindingError.ts    extends the kernel's MissingCapabilityError
  types/
    index.ts
    CloudflareEnv.ts          the environment contract a consuming Worker extends
    AISettings.ts, KVSettings.ts, …   one settings shape per binding
    CloudflareConfiguration.ts
    d1/                       D1Settings, D1Statement, D1WriteResult
    hyperdrive/               HyperdriveSettings, HyperdriveCredentials
  utils/
    index.ts
    lazySettings.ts           deferred settings lookup

core/ holds what the library does. constants/, exceptions/, types/, and utils/ keep cross-cutting definitions out of the domain directories and give each kind of source a predictable location.

middlewares/ is the one addition, and it sits beside core/ rather than inside it. It is a second published entry with a dependency the main one does not have, so the directory boundary and the package boundary are the same line — see The Hono boundary below.

Module responsibility

Directory Holds May import from
src/index.ts re-exports only — no implementation every layer except middlewares/
src/core/ the domain: accessors and capability services every layer below
src/core/bindings/ binding accessors over Binding every layer below
src/core/services/ capability services implementing kernel interfaces core/bindings/ and every layer below
src/middlewares/ Hono middleware everything, plus hono
src/constants/ cloudflareDefaults types/
src/exceptions/ the faults raised the kernel only
src/types/ the environment contract and the settings shapes nothing in src/
src/utils/ the deferred settings lookup the kernel only

The Hono boundary

hono is an optional peer dependency, reachable only through the ./middlewares subpath. That is what lets a Worker use these bindings without adopting a particular HTTP framework — and it holds only while nothing outside src/middlewares/ imports Hono. The moment a binding does, resolving the package's main entry drags the framework in and the peer stops being optional.

It is enforced by a no-restricted-imports rule in eslint.config.ts, not by convention. A convention survives only as long as everyone remembers it, and the import that breaks it looks exactly like every other import at the point it is written:

// src/core/services/KVService.ts — fails `npm run lint`
import { Context } from 'hono'

If something outside middlewares/ needs request context, it takes it as an argument.

Grouping inside a layer

A subject with more than one shape to its name gets a directory; a lone shape stays flat. AISettings is the only AI shape, so it sits directly in types/; D1 has settings, a statement, and a write result, so those three share types/d1/.

It is the rule the kernel's types/ follows — one convention across the two packages rather than one each.

One declaration per file

Every exported class, interface, type, and constant lives in its own file, named after it. index.ts files are the exception and hold nothing but re-exports.

The rule is about finding things and about diffs. A file named after the thing in it means the path is the answer to "where is KVSettings", with no grep step; and a change to one interface touches one file. It also stops the slow accretion by which a types.ts becomes the place every new shape gets appended.

It is enforced by the documentation/one-declaration-per-file lint rule, and barrels absorb the cost at the call site: @/types still resolves, so importing twenty settings interfaces is still one import.

Imports

Internal non-relative imports resolve through the @/* mapping in tsconfig.json:

import { KVBinding } from '@/core/bindings/KVBinding'
import { resolve } from '@bayudwiyansatria/core'

baseUrl is deliberately not used. It would make import … from 'types' mean src/types, but types, core, and constants are all plausible package names — the meaning of such an import would depend on what happens to be installed. The @/ prefix cannot collide, and it keeps @bayudwiyansatria/core unambiguously the package.

Three consumers have to agree on the mapping, and all three are configured:

Consumer Where
TypeScript tsconfig.json#compilerOptions.paths
Jest jest.config.json#moduleNameMapper
Rollup @rollup/plugin-typescript, same tsconfig

A bare @/… specifier surviving into lib/ means one of them has drifted.

results matching ""

    No results matching ""