Adapters

The mock, remote and runtime adapters behind the typed client — the same app code in every environment, which methods exist at all, and what a mock does not enforce.

createDb() takes an adapter. Your app code does not change between environments; the adapter does.

AdapterWhat it doesUse it for
mockIn-memory fixtures, no infrastructure.Unit tests and fast local iteration.
remoteReal data over HTTP against a dev tenant.Checking behaviour against the real data plane before you deploy.
runtimeProduction. Auto-configured from the function's invocation context.The deployed app.
mock
const { createDb } = require('./db.generated');

const db = createDb({ adapter: 'mock', seed: { device_serials: [] } });
await db.device_serials.create({ serial: 'SN-000001', status: 'in_stock' });
runtime
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:

src/main.js
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:

manifest.json
"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, a notNull, or a unique index does not exist in memory. The unique-collision 409 your API is supposed to return will not happen in a mock.
  • Foreign keys. A references with onDelete: cascade does 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.expression is 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 notNull column without a default cannot 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:

Terminal
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

Was this page helpful?