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.