Adapters and Runtime
Semitra is Cloudflare-native in runtime and adapter-driven at the edges.
That means:
- Worker code stays built around Web Standard APIs
- subsystem packages depend on framework contracts
- adapters translate Cloudflare bindings into those contracts
Package boundary model
Section titled “Package boundary model”The important runtime packages are:
packages/adapterspackages/corepackages/recordpackages/resourcepackages/policypackages/jobpackages/mailpackages/realtimepackages/eventspackages/cachepackages/storagepackages/tenancypackages/schema
packages/semitra is the public application-facing package exported as
@semitra/cli. packages/cli is the internal binary package that powers the
semitra command.
Runtime path
Section titled “Runtime path”At request time:
- the Worker entrypoint forwards into the generated app
SemitraApp.fetch()creates the request contextresolveAdapters(env)inspects Worker bindings- Semitra stores adapter registries on
SemitraRequestContext - controllers and records consume the runtime through framework APIs
This is why application code should not reach for raw env.DB, env.R2, or
env.KV directly unless you are intentionally bypassing the framework.
The reference Worker entrypoint is a thin handoff into the generated app:
import app from "../.semitra/generated/app.ts";
export default { fetch(request: Request, env: Record<string, unknown>, ctx: ExecutionContext) { return app.fetch(request, env, ctx); }};Default Cloudflare mapping
Section titled “Default Cloudflare mapping”- D1-shaped bindings become database adapters
- KV-shaped bindings become cache adapters
- R2-shaped bindings become storage adapters
- Queue-shaped bindings become job adapters
- Durable Object namespaces become realtime-capable adapters
Runtime container
Section titled “Runtime container”The request context also exposes a request-scoped container for runtime dependencies such as:
- request
- env
- ctx
- adapters
- tenancy
- route
- params
- validated params
- controller
- response
- locals
This allows plugins and subsystems to resolve shared runtime state without smuggling it through unrelated function arguments.
The app-level runtime config wires in tenancy resolution and database binding selection:
import { composeTenantResolvers, createBoundTenantDatabaseProvider, headerTenantResolver, subdomainTenantResolver} from "@semitra/cli";
export default { tenancy: { defaultTenant: "public", databaseProvider: createBoundTenantDatabaseProvider({ bindingMap: { public: { DB: "DB" } } }), resolver: composeTenantResolvers( headerTenantResolver(), subdomainTenantResolver() ) }};Why this matters
Section titled “Why this matters”The adapter boundary keeps Semitra from duplicating responsibilities:
- controllers orchestrate
- records persist
- storage stores objects
- cache stores cached values
- adapters normalize platform access
Each layer stays narrow and easier to replace or test.