Pick a store adapter by package + subpath and the adapter’s StorageAdapterManifest — not by marketing copy. All production adapters implement the same store contracts (idempotency, webhook inbox, reconciliation). Inject stores at the app layer; @paykernel/core and @paykernel/webhooks never depend on a database package.
All store adapters are published: store-postgres/redis/sqlite/turso/d1/durable-objects 0.1.1, store-contracts/sql-foundation 0.1.0 on npm.
Honesty
| Fact | Implication |
|---|---|
| Redis is optional | You do not need Redis to use @paykernel/core or the webhook engine |
| Local SQLite is single-host | Bun / Node / better-sqlite3 file DBs are not multi-host coordination |
:memory: is one process |
Lost on process restart; not the file-backed durable claim |
| Turso is remote multi-host | No /sync export; not local store-sqlite |
| D1 ≠ Durable Objects ≠ Turso ≠ local SQLite | Separate packages, APIs, and consistency models |
| Memory stores are NON-PRODUCTION | MEMORY_STORAGE_ADAPTER_MANIFEST is single-process + ephemeral |
No adapter declares coordinationScope: "multi-region" |
Do not invent multi-region strong consistency |
@paykernel/internal-sql-store is private. Do not install it. Relational adapters depend on @paykernel/sql-foundation.
Inbox stores claim work. handleWebhook only verifies and normalizes — it does not claim, lease, or set HTTP status. Never fulfill in onWebhookVerified. Fulfill after an inbox claim, and only when the rematched event is payment.succeeded or capture.completed and payment.status === "paid", bound to gatewayPaymentId. See Webhooks.
Capability matrix
| Adapter | Package | Distributed | Durable audit | Atomic claim | Best use | Limitation |
|---|---|---|---|---|---|---|
| PostgreSQL | @paykernel/store-postgres |
Yes (multi-host) |
Yes (durable) |
Yes (strong) |
General default when you have PostgreSQL | Multi-primary without consensus is out of scope |
| Redis/Valkey | @paykernel/store-redis /bun /ioredis /node-redis |
Yes (multi-host); Bun: no Cluster/Sentinel |
Configuration-dependent | Yes (Lua) | Low-latency coordination | Optional infra; not automatic long-term audit |
| Upstash Redis | @paykernel/store-redis/upstash |
Yes (multi-host) |
Configuration-dependent | Yes (EVAL) | Serverless coordination over HTTP | Same hybrid-audit caveats; HTTP/network model |
| Bun SQLite | @paykernel/store-sqlite/bun |
No — single host | Yes with a durable file (:memory: is process-local) |
Yes (BEGIN IMMEDIATE) |
Bun local / single-server | Not cross-host; no network FS sharing |
| Node SQLite | @paykernel/store-sqlite/node |
No — single host | Yes (file-backed) | Yes | Node local / single server | node:sqlite is experimental; Node ≥ 22.5.0 |
| better-sqlite3 | @paykernel/store-sqlite/better-sqlite3 |
No — single host | Yes (file-backed) | Yes | Mature Node SQLite | Native dependency; sync API |
| Turso serverless | @paykernel/store-turso/serverless |
Yes (multi-host remote) |
Yes (durable) |
Yes | Shared remote SQLite-compatible | Not local SQLite; no /sync |
| libSQL | @paykernel/store-turso/libsql |
Yes remote; local file: is single-host testing |
Yes remote | Yes | Existing @libsql/client projects |
Not interchangeable with /serverless; no /sync |
| Cloudflare D1 | @paykernel/store-d1 |
Yes (multi-host, shared D1) |
Yes (durable) |
Yes | Worker-native shared relational | Not SQLite/Turso/DO; readAfterWrite: "session" |
| Durable Objects | @paykernel/store-durable-objects |
Yes, partitioned | Yes (SQLite-backed DO) | Yes within a partition | Per-key / per-partition serialization | Never one global DO; not D1 |
| Memory (testkit) | @paykernel/testkit |
No — single process | No (ephemeral) |
Strong only in one isolate | Tests / examples | NON-PRODUCTION |
Manifest cheat sheet
| Adapter | coordinationScope |
durability |
claims |
readAfterWrite |
staleReadsPossible |
|---|---|---|---|---|---|
| postgres | multi-host |
durable |
strong |
strong |
false |
| redis (all bindings) | multi-host |
configuration-dependent |
strong |
strong |
false |
| sqlite (all bindings) | single-host |
durable |
strong |
strong |
false |
| turso (both bindings) | multi-host |
durable |
strong |
strong |
false |
| cloudflare-d1 | multi-host |
durable |
strong |
session |
true |
| cloudflare-do | multi-host (partitioned) |
durable |
strong |
strong (per DO) |
false (per partition) |
| memory | single-process |
ephemeral |
strong (isolate only) |
strong |
false |
Decision (stop at the first fit)
- Already run PostgreSQL that all workers share? →
@paykernel/store-postgres - Cloudflare Workers? (never
@paykernel/store-sqlite— Workers are multi-isolate with no durable local FS)- Need strong per-key / per-partition serialization (including if you already use D1)? →
@paykernel/store-durable-objectswith explicit sharding. Never one global Durable Object. - Shared relational / greenfield? →
@paykernel/store-d1. Prefer Sessions (first-primary) when read replication is on.
- Need strong per-key / per-partition serialization (including if you already use D1)? →
- Ephemeral serverless FS / no durable local disk (not Workers)? Do not use
store-sqlite. Continue to Turso, optional Redis, or STOP. - Bun, single host, durable local file OK? →
@paykernel/store-sqlite/bun - Node, single host, local file OK? →
/nodeor/better-sqlite3(prefer better-sqlite3 untilnode:sqliteis stable) - Need multi-host remote SQLite-compatible storage? →
@paykernel/store-turso(/serverlessor/libsql). Not interchangeable with local SQLite or D1. - Already operate Redis / Valkey / Upstash? → matching
store-redisbinding; optionally hybrid with SQL for audit. Bun + Cluster/Sentinel → not/bun; use/ioredisor/node-redis. - No Redis today and moderate load? Pick the primary relational / D1 / DO adapter. Do not add Redis only because PayKernel exists.
Fail-closed: if multi-host or multi-isolate coordination is required and the only option is local SQLite → STOP. Choose PostgreSQL, Turso (remote), D1, or sharded Durable Objects. Do not share the SQLite file across hosts.
Real subpaths only
| Package | Exports |
|---|---|
@paykernel/store-postgres |
. /bun-sql /postgres-js /pg /drizzle |
@paykernel/store-redis |
. /bun /upstash /ioredis /node-redis |
@paykernel/store-sqlite |
. /bun /node /better-sqlite3 |
@paykernel/store-turso |
. /serverless /libsql — no /sync |
@paykernel/store-d1 |
. only |
@paykernel/store-durable-objects |
. only |
Root entries never statically import optional drivers.
Related
- Adapter selection guide (same matrix)
- Store contracts
- SQL foundation
- Runtime · Composition