Adapters
createDb() takes an adapter. Your app code does not change between environments; the adapter does.
| Adapter | What it does | Use it for |
|---|---|---|
mock | In-memory fixtures, no infrastructure. | Unit tests and fast local iteration. |
remote | Real data over HTTP against a dev tenant. | Checking behaviour against the real data plane before you deploy. |
runtime | Production. Auto-configured from the function's invocation context. | The deployed app. |
const { createDb } = require('./db.generated');
const db = createDb({ adapter: 'mock', seed: { device_serials: [] } });
await db.device_serials.create({ serial: 'SN-000001', status: 'in_stock' });
module.exports = async (context) => {
const db = createDb({ adapter: 'runtime', context });
return buildApp(db).handler()(context);
};
The pattern that makes this work is to build your routes against an injected db rather than constructing one inside them:
function buildApp(db) {
const app = createApp({ name: 'serials' });
mountCrud(app, db.device_serials, { path: '/serials', columns: ENTITIES.device_serials.columns });
return app;
}
module.exports = async (context) => buildApp(createDb({ adapter: 'runtime', context })).handler()(context);
module.exports.buildApp = buildApp;
Your tests then call buildApp(createDb({ adapter: 'mock' })) and exercise the same routes the gateway will.
runtime configures itself
In production the runtime adapter reads the app's invocation context, so you wire up no endpoint, no tenant and no credential. There is nothing to put in an environment variable and nothing to rotate. The tenant comes from the context, not from your code — see Tenant isolation.
remote uses the public auth contract
Pointed at a dev tenant, the data-plane call carries exactly the credentials any client would: Authorization: Bearer <token> and X-Revenexx-Tenant: <slug>. Nothing privileged, nothing internal.
Use it for the step a mock cannot do: confirming a real constraint fires, a real index makes a read fast, and a real notNull column applies to a table that already holds rows.
Permission-gated methods
The generated client only has the methods your manifest grants. This is not an adapter behaviour — it is codegen, so it is the same under all three:
"permissions": [
{ "entity": "audit_entries", "access": ["read", "create"] }
]
db.audit_entries has list, page, get and create. There is no update and no delete to call, so an append-only ledger cannot be rewritten by a bug. Widen the grant, regenerate, and the methods appear. See Entity access.
What a mock does not enforce
A mock is an in-memory store. It is fast, it needs nothing running, and it is wrong about the database in ways worth listing, because each one is a class of bug that will reach production if a mock is your only test:
- Constraints. A
check, anotNull, or auniqueindex does not exist in memory. The unique-collision409your API is supposed to return will not happen in a mock. - Foreign keys. A
referenceswithonDelete: cascadedoes not cascade, and a write pointing at a missing parent succeeds. - SQL defaults.
"default": "now()"and"gen_random_uuid()"are database expressions. A mock fills in something plausible; it is not the same value your production row will carry. - Generated columns. A
generated.expressionis evaluated by PostgreSQL. In a mock it is absent or stale. - Migrations. A mock has no populated table, so it can never tell you that a new
notNullcolumn without adefaultcannot be applied to one. - Scoping. Neither tenant isolation nor a scope dimension is exercised, because there is only ever one tenant and no scope context.
- Ordering and paging over real data. An in-memory array happens to be insertion-ordered. A real table is not, unless you declared an
order.
So mocks are for your logic and the real thing is for the database. The practical division:
npm test # mock: routes, guards, branching, error mapping
# then, against a dev tenant with real-shaped data:
revenexx tenants use acme-staging
revenexx deploy app
See Testing for the harness, and Verifying live for the calls to make afterwards.
Next steps
- The typed client — the surface every adapter serves.
- Testing — the in-memory harness in full.
- Migrations — the part only a real tenant can verify.