Skip to main content
Version: 2.x

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.

OptionTypeMeaning
postgresstring | pg.PoolConnection string for an owned pool, or an application pool
tenantIdstringRequired tenant UUID scoping every transaction
providerEnvironment"live" | "test" | "sandbox"Required financial provider namespace
poolConstructorPoolConstructorOptional 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:

OptionDefaultMeaning
connectionTimeoutMs10_000Maximum connection establishment or pool-acquisition time for an SDK-owned pool
statementTimeoutMs30_000Per-transaction PostgreSQL statement deadline; 0 disables it
idleTransactionTimeoutMs30_000Deadline for an abandoned idle transaction; 0 disables it
idleTimeoutMspg defaultIdle lifetime for connections in an SDK-owned pool
maxConnectionspg defaultMaximum connections in an SDK-owned pool
applicationNamebursar-jsValue reported as PostgreSQL application_name
onPoolErrorno-opObserver 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 idempotencyKey returns 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 sweepExpiredCredits expiring 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 use StoreUnavailableError or StoreTimeoutError. Preserve native failures in cause and mark uncertain mutation outcomes with indeterminate: 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:

Terminal
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).