Skip to content

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

The important runtime packages are:

  • packages/adapters
  • packages/core
  • packages/record
  • packages/resource
  • packages/policy
  • packages/job
  • packages/mail
  • packages/realtime
  • packages/events
  • packages/cache
  • packages/storage
  • packages/tenancy
  • packages/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.

At request time:

  1. the Worker entrypoint forwards into the generated app
  2. SemitraApp.fetch() creates the request context
  3. resolveAdapters(env) inspects Worker bindings
  4. Semitra stores adapter registries on SemitraRequestContext
  5. 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);
}
};
  • 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

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()
)
}
};

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.