/** * @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 MysqlExecutor { query(sql: string, params?: ReadonlyArray): Promise<[unknown, unknown]>; } interface MysqlConnection extends MysqlExecutor { release(): void; } /** * The pool seam the store rides, structurally matching `mysql2/promise`'s pool. Declared by hand * so `mysql2` stays an optional dependency, and satisfied by any pool with these members: the * pool {@link createMysqlPool} builds, or the pipelining `mariadb` pool from * `@pwngh/economy-lab/engines/mysql-mariadb`, which {@link mysqlStore} rides unchanged. `query` * resolves a tuple whose first slot holds rows for a SELECT or an affected-rows header for a * write. */ export interface MysqlPool extends MysqlExecutor { getConnection(): Promise; end(): Promise; } /** * Build the full MySQL-backed store on a connection pool the caller creates and owns — the * `mysql2` pool from {@link createMysqlPool}, or the pipelining `mariadb` pool from * `@pwngh/economy-lab/engines/mysql-mariadb`; both fill the same {@link MysqlPool} seam and run * the same SQL against the same schema. * * `transaction(work)` borrows one connection, wraps `work` in START TRANSACTION ... COMMIT, and * rolls back if `work` throws. Money transactions run at READ COMMITTED (set once per pooled * connection); correctness comes from explicit `FOR UPDATE` row locks plus a `GET_LOCK` named * lock per account. A transient InnoDB abort — deadlock, lock-wait timeout, named-lock deadlock, * 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. On the way out of a transaction every named lock the connection acquired is * released, so a returned connection carries no leftover locks. Anything outside a transaction * (plain reads/writes, plus the trust and checkpoint stores) runs directly on the pool and * commits on its own. * * The hash service defaults to the deterministic web-standard SHA-256; the clock defaults to * wall-clock time. Pass a fixed clock when reproducible `postedAt` values matter. The velocity * window defaults to one hour. * * @see {@link https://economy-lab-docs.pages.dev/economy/ports/storage/ Storage} for the store and outbox/inbox ports this backs. */ export declare function mysqlStore(deps: { pool: MysqlPool; /** 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; /** * Open-path schema policy: 'assert' verifies the schema_meta stamp before the first operation * of any kind; 'skip' (the default here) is for the staged open that already asserted on the * pool. Migration is {@link applyMysqlSchema} or an external migrate job — never an open * option. */ schema?: 'assert' | 'skip'; /** * 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; }): Store; /** * Reads the database's stamped schema version from `schema_meta`, or `null` when that table is * absent (an un-migrated or pre-versioning database). Any query failure also reads as `null`, so * an unreachable or unauthorized database looks un-migrated rather than throwing here. The * composition layer passes the result to its schema assert to fail fast on a schema that has * drifted from this code. */ export declare function readSchemaVersion(pool: MysqlPool): Promise; /** * Create all tables and stored routines this engine needs, from the canonical schema file * `db/mysql-schema.sql` (the MySQL counterpart to `db/postgresql-schema.sql`). The file drops and * recreates the tables, so running this resets to a clean schema (convenient for tests). Run once * during setup (operations tooling or CI), never automatically at app startup. * * mysql2 sends one statement per `query`, so the file is split into individual statements first * (honoring the mysql CLI's `DELIMITER` directive for routine bodies), then each is run in order. */ export declare function applyMysqlSchema(pool: MysqlPool): Promise; /** * Create a `mysql2` connection pool from a connection URL. `mysql2` is imported here, only when * this function runs, since it's an optional dependency the rest of the code never needs. The pool * returns large integer columns (the money columns, stored as 64-bit integers) as strings, which * the engine then converts to bigint exactly. * * The connection collation is pinned to the schema's utf8mb4 default so the strings the posting * routine derives from JSON join the table columns without collation errors. * * `connectionLimit` caps the pool. Each in-flight transaction holds one connection for its whole * BEGIN..COMMIT, so a caller driving N concurrent submits must size this to at least N. Left unset, * `mysql2`'s default of 10 applies, which is the historical behavior. */ export declare function createMysqlPool(url: string, options?: { connectionLimit?: number; }): Promise; /** * The shared field vocabulary for opening a SQL engine, with the pool type bound to this * driver's {@link MysqlPool}. The composition layer assembles these fields from configuration; * {@link mysqlStore} implements the MySQL subset (it takes a pre-built `pool` rather than * opening by `url`, and has no `schemaName` isolation). */ export type EngineOpenOptions = EngineOpenShape; export {};