Stores
Every Bursar facade is built on a store — the adapter that owns the
database. PostgresStore is the only bundled credit store;
PostgresBillingStore is the bundled billing store; CreditStore is the
abstract contract a custom backend must implement.
PostgresStore
import { PostgresStore } from "@zonastery/bursar";
const store = new PostgresStore({
postgres: databaseUrl,
tenantId,
providerEnvironment: "test",
connectionTimeoutMs: 10_000,
statementTimeoutMs: 30_000,
onPoolError: (error) => console.error("Bursar pool error", error),
});
Connects through pg and runs every mutation as a canonical SQL function
(RPC). The tenantId option scopes every transaction to one tenant.
| Option | Type | Meaning |
|---|---|---|
postgres | string | pg.Pool | Connection string for an owned pool, or an application pool |
tenantId | string | Required tenant UUID scoping every transaction |
providerEnvironment | "live" | "test" | "sandbox" | Required financial provider namespace |
poolConstructor | PoolConstructor | Optional constructor for testing or custom pg initialization |
The store owns a pool it creates from a postgres connection string: call await store.close()
to drain it. A supplied pool is borrowed and is never ended by the store.
PostgreSQL reliability options
PostgresStoreOptions and PostgresBillingStoreOptions share these controls:
| Option | Default | Meaning |
|---|---|---|
connectionTimeoutMs | 10_000 | Maximum connection establishment or pool-acquisition time for an SDK-owned pool |
statementTimeoutMs | 30_000 | Per-transaction PostgreSQL statement deadline; 0 disables it |
idleTransactionTimeoutMs | 30_000 | Deadline for an abandoned idle transaction; 0 disables it |
idleTimeoutMs | pg default | Idle lifetime for connections in an SDK-owned pool |
maxConnections | pg default | Maximum connections in an SDK-owned pool |
applicationName | bursar-js | Value reported as PostgreSQL application_name |
onPoolError | no-op | Observer for typed errors emitted by idle pool clients |
The statement deadline is applied with transaction-local PostgreSQL settings,
so it also protects operations that use a borrowed pool. Connection, SQLSTATE,
timeout, and rollback failures are normalized into StoreError subclasses
with the original driver error in cause.
PostgresBillingStore
import { PostgresBillingStore } from "@zonastery/bursar";
const billingStore = new PostgresBillingStore({
postgres: databaseUrl,
tenantId,
providerEnvironment: "test",
});
It uses the same object shape: postgres (a pg.Pool or connection string),
tenantId, and required providerEnvironment.
It wraps all billing repositories (offer, topup, customer, subscription,
event, payment, refund, invoice, dispute, config) behind one interface and is
passed as billingStore: to new Bursar({...}) to enable billing. Commerce
additionally requires commerceOptions: with the provider environment and
factories.
PostgresBillingStoreOptions accepts the same reliability controls listed
above plus billingPayloadBackend.
The CreditStore contract
CreditStore is the abstract base class for a custom backend. Implementations
must preserve, for every capability they expose:
- Atomicity — each mutation commits or rolls back as one unit; balances and their ledger rows never diverge.
- Idempotency — a replayed
idempotencyKeyreturns the original result instead of applying a second time. - Append-only ledger — history is immutable; corrections are new entries
(
adjustment/refund), never updates. - Credit-lot allocation — credits are consumed lot-by-lot by priority and
expiry (FEFO), with
sweepExpiredCreditsexpiring eligible lots. - Stable cursor ordering — every list view orders by
(createdAt, entryId)so cursor pagination never skips or duplicates rows. - Typed failures — permanent validation/invariant failures use
non-retryable
StoreError; only known transient conditions useStoreUnavailableErrororStoreTimeoutError. Preserve native failures incauseand mark uncertain mutation outcomes withindeterminate: true.
Optional capabilities (usage analytics, team management, usage-charge lists,
getLedgerEntry) throw CapabilityNotSupportedError by default on the base
class; override them if your backend supports them. Billing is a separate
hierarchy (BillingStore/PostgresBillingStore) with its own repositories.
Migrations
The TypeScript package ships no schema SQL and has no migration command of its own. Apply the bundled migrations with the Python CLI during application setup, before constructing any store:
pip install "bursar[postgres]"
BURSAR_MIGRATION_DATABASE_URL=postgres://... bursar migrate
See CLI for tenant, config, and validation subcommands, and
Storage backends for the Node-only
S3/ClickHouse adapters (@zonastery/bursar/node).