Custom adapters
Implement the Storage interface for any backend, with zero-DB helpers and a conformance battery to prove it.
The Storage interface is technology-agnostic on purpose: you can back Postel with anything — libSQL, Turso, D1, CockroachDB, a backend the first-party adapters don't cover — by implementing it yourself. Wire your adapter in exactly like a first-party one:
const postel = Postel({
outbound: {
storage: myAdapter,
},
});What you implement
Storage is operation-shaped, not CRUD. The hot-path operations carry semantics a plain update can't express — reserveBatch is lock-acquire + lease-assign + return in one atomic step (FOR UPDATE SKIP LOCKED on Postgres, BEGIN IMMEDIATE on SQLite). You implement that operation set plus the endpoints / secrets / tenants sub-namespaces, dedup, and transaction(cb). Declare a capabilities object so the worker scheduler degrades gracefully (poll when notify is absent, run sequentially when transactional is false).
Don't reimplement the glue — use @postel/storage-helpers
@postel/storage-helpers has zero database dependencies and carries everything an adapter would otherwise hand-roll: dialect-aware timestamp / JSON / bytes codecs, the canonical capability flag sets, idempotency-key formatting, message / attempt / endpoint / secret row encode-decode, the schema-version constant, and the code-side filter/transform callback registry (those callbacks aren't serializable, so adapters keep them in memory keyed by endpoint id and re-attach them on read).
Prove it with the conformance battery
@postel/storage-testkit exports runStorageTests(factory) — the same behavior battery the in-memory reference and every first-party adapter pass. Point it at your adapter from a test file:
import { makeFakeClock, runStorageTests } from "@postel/storage-testkit";
import { myAdapter } from "../src/index.js";
runStorageTests({
name: "my-adapter",
capabilities: { notify: false, txIsolation: true },
async create() {
const clock = makeFakeClock();
return { storage: myAdapter({ clock }), clock };
},
});The capabilities flags let the battery skip scenarios your backend genuinely can't honor (e.g. cross-connection notify on a single-connection store). Everything else must pass — that's the bar for being a real Postel storage backend.