/** * @pwngh/economy-lab * * Copyright (c) Preston Neal * * This source code is licensed under the MIT license found in the * LICENSE.md file in the root directory of this source tree. * * @license MIT */ import type { EngineOpenShape } from './sql-shared.js'; import type { Clock, Digest, Logger, Meter, Store } from '../ports.js'; interface PgClient { query(text: string, values?: ReadonlyArray): Promise; release(): void; } interface PgResult { rows: Array>; rowCount?: number | null; } /** * The slice of a `pg` pool this engine calls, declared structurally so nothing here imports the * driver's types. A real `pg.Pool` satisfies it, and so does any caller-built implementation * handed in as {@link PostgresStoreOptions.pool} — see that option for what a replacement pool * must provide (BigInt numeric parsing, and schema resolution when `schemaName` is used). */ export interface PgPool { connect(): Promise; query(text: string, values?: ReadonlyArray): Promise; end(): Promise; } /** * Configuration for {@link postgresStore}. Everything beyond `url` has a working default: `pg`'s * pool size of 10, no connection timeout, the deterministic SHA-256 digest, the wall clock, a * one-hour velocity window, and the 'assert' schema policy. */ export interface PostgresStoreOptions { /** Connection URL the default `pg` pool connects with; unused when `pool` is supplied. */ url: string; /** * Optional dedicated Postgres schema name, created/loaded then dropped on close, so parallel * test runs don't collide. Omit to use the schema the connection already points at. */ schemaName?: string; /** * Table layout the isolated schema is provisioned with: 'partitioned' hash-partitions the * growth tables (a provisioning choice, runtime-identical). Only meaningful with `schemaName`; * a database provisioned externally chooses its layout at migration time. */ layout?: 'standard' | 'partitioned'; /** * Open-path schema policy: 'assert' (the default) requires the schema_meta stamp to match this * build; 'skip' is break-glass. Migration is an external job — never an open option. */ schema?: 'assert' | 'skip'; /** Hash service for chain links; defaults to the deterministic web-standard SHA-256. */ digest?: Digest; /** Time source for `postedAt` and window math; defaults to wall-clock time. */ clock?: Clock; /** * Rolling window (ms) the trust store applies when summing a subject's recent spend for the * velocity check. Defaults to one hour; the composition passes config.velocityWindowMs. */ velocityWindowMs?: number; /** * Max connections in the pool. Each in-flight transaction holds one connection for its whole * BEGIN..COMMIT, so this caps how many submits can run at once: a caller that drives N * concurrent submits must size this to at least N or the extra ones block waiting for a * connection. Left unset, `pg`'s default of 10 applies. */ poolMax?: number; /** * Max time (ms) to wait for a connection before failing. `pg`'s default is no timeout, so a * routable-but-stalled host would otherwise hang indefinitely. */ connectionTimeoutMillis?: number; /** * The driver seam: a caller-built pool takes the place of the default `pg` pool — the same * {@link PgPool} surface with any wire implementation behind it. The caller points it at the * right database and returns int8/numeric columns as BigInt (the default pool's type parsers); * with `schemaName`, its connections must also resolve unqualified names to that schema (the * default pool does this via a search_path startup option). The store owns the pool it is * given and ends it on close(). `poolMax` and `connectionTimeoutMillis` do not apply. */ pool?: PgPool; /** * Optional runtime ports for the engine's own telemetry (transient-retry pressure). The * composition passes the runtime meter and logger; unset emits nothing. */ meter?: Meter; logger?: Logger; } /** * Build a {@link Store} backed by Postgres, using real database transactions. The returned * `transaction(work)` checks out one connection, runs `work` between BEGIN and COMMIT, and rolls * back if `work` throws. Transactions run at Postgres' default READ COMMITTED isolation, with * correctness carried by explicit `FOR UPDATE` row locks rather than a snapshot; a transient * abort — deadlock, serialization failure, or a stale-head chain fork — committed nothing, so the * whole unit of work is re-run in a fresh connection and transaction and callers never see it as * an error. Every posting appends to a per-account hash chain, and the schema's triggers enforce * conservation and chain continuity on every write. The trust and checkpoint stores hang off the * pool directly, not off a transaction, so their writes are never rolled back. * * Opening fails fast rather than serve a mismatched database: the schema_meta stamp must match * this build (unless `schema: 'skip'`), and the vendored money routines are installed and proven * against pinned vectors before any posting trusts their arithmetic. If `schemaName` is given, a * fresh schema with that name is created, loaded with db/postgresql-schema.sql, and used for all * queries; `close()` drops it and ends the pool. The hash dependency defaults to the * deterministic SHA-256; the clock defaults to wall-clock time. Pass a fixed clock when * reproducible `postedAt` values matter. * * @example * const store = await postgresStore({ * url: 'postgres://econ:secret@127.0.0.1:5432/economy', * poolMax: 32, // at least one connection per concurrent submit * connectionTimeoutMillis: 5_000, * }); * const economy = createEconomy({ store, ...runtimePorts }); * // ... on shutdown: * await store.close(); * * @see {@link https://economy-lab-docs.pages.dev/economy/ports/storage/ Storage} for the port contracts this engine implements. */ export declare function postgresStore(options: PostgresStoreOptions): Promise; /** * The shared field vocabulary for opening a SQL engine, with the pool type bound to this * driver's {@link PgPool}. The composition layer assembles these fields from configuration; * {@link postgresStore} implements the Postgres subset (it opens by `url` or takes a pre-built * `pool`, and honors `schemaName` isolation). */ export type EngineOpenOptions = EngineOpenShape; export {};