better-effect
Reference

Writing a storage adapter

Implement an advanced JobStore provider without leaking backend details.

This is an advanced implementation page. Application authors should start with the MQ guide and an adapter quick start. A storage adapter implements JobStore.Contract and provides it through a default or named JobStore token; its database, transactions, connection lifecycle, and layout stay behind that boundary.

Keep transactions, indexes, wake delivery, migrations, and connection lifecycle inside the adapter. A caller-supplied pool/client is borrowed; a resource created by the adapter is owned and must be closed exactly once. Backend types, identifiers, credentials, and layout details must not leak into better-effect-mq.

Every operation returns a completed better-result Result or a PromiseLike of one. Validate untrusted DTOs and decoded records, use the request's single now value, and persist only JSON-safe records and bounded failure envelopes. The required atomic boundaries are enqueue identity/idempotency, ordered claim and lease acquisition, settlement plus attempt ledger, fenced release and heartbeat, stalled recovery, and administrative transitions.

Optional parent/child composition, queue controls, events, schedules, and outbox are separate capabilities. Implement each only when the adapter can provide its atomicity, migration, and recovery guarantees, and keep cross-store enqueue and relay explicitly at-least-once.

awaitWake is an optimization over persisted wake state. Polling must remain correct, and wake messages must not contain payloads, metadata, idempotency keys, lease tokens, or arbitrary causes. Publish one frozen descriptor with the adapter and layout capabilities it supports; Worker startup rejects an incomplete or incompatible handshake before supervision begins.

Run the runner-neutral jobStoreContract suite against every supported capability configuration, passing every capability explicitly (including false) so the descriptor manifest is checked. A declared-true durableChangeFeed, globalConcurrency, or rateLimiting flag requires a separate extension Service/interface and an executable extensions scenario; these flags never add optional methods to the JobStore contract. The report marks a true declaration without such a scenario as capabilitiesNotTested. Then add backend tests for rollback, concurrent writers, reconnects, lost responses, migration boundaries, and borrowed-versus-owned cleanup. Before release, run the current TypeScript 7.x declaration fixture, full Bun checks, package boundaries, publint, and the external tarball consumer.