@rotorsoft/act-pg 1.17.0 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/postgres-store.d.ts.map +1 -1
- package/dist/index.cjs +51 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +51 -55
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/postgres-store.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n BlockedLease,\n Committed,\n EventMeta,\n Lease,\n Logger,\n Message,\n NotifyDisposer,\n Query,\n QueryStatsOptions,\n QueryStreams,\n QueryStreamsResult,\n Schema,\n Schemas,\n Store,\n StoreNotification,\n StreamFilter,\n StreamPosition,\n StreamStats,\n SubscribeInput,\n} from \"@rotorsoft/act\";\nimport {\n ConcurrencyError,\n dateReviver,\n log,\n SNAP_EVENT,\n StoreError,\n TOMBSTONE_EVENT,\n ValidationError,\n} from \"@rotorsoft/act\";\nimport {\n decrypt,\n type Encryption,\n encrypt,\n makeKeyResolver,\n} from \"@rotorsoft/act-crypto\";\nimport pg from \"pg\";\n\nconst logger: Logger = log();\n\nconst { Pool, types } = pg;\n\n/**\n * Per-Pool type parser (#1198). Overrides ONLY the JSONB parser to revive\n * ISO-date strings in event payloads to `Date`, delegating every other\n * OID to pg's global default. Passed as the Pool's `types` option so the\n * Date coercion is scoped to this store's connections — it never mutates\n * the process-global `pg.types` registry, so a host app's other pg usage\n * (Drizzle projections, ad-hoc queries) reads jsonb with the stock\n * parser. This is the shape pg threads down to each pooled client.\n */\nconst JSONB_OID = types.builtins.JSONB;\nconst scopedTypes: { getTypeParser: typeof types.getTypeParser } = {\n getTypeParser: ((oid: number, format?: unknown) =>\n oid === JSONB_OID\n ? (val: string) => JSON.parse(val, dateReviver)\n : (types.getTypeParser as (oid: number, format?: unknown) => unknown)(\n oid,\n format\n )) as typeof types.getTypeParser,\n};\n\ntype Config = Readonly<{\n schema: string;\n table: string;\n /**\n * Opt in to cross-process commit notifications via `LISTEN`/`NOTIFY`.\n * Optional — defaults to `false` so existing callers keep their\n * current behavior. Setting it to `true` is the only behavior change\n * an upgrading deployment needs to make to enable cross-process\n * reaction wakeup.\n *\n * When `true`:\n * - `commit()` issues `pg_notify` after each successful insert.\n * - `notify(handler)` checks out a dedicated long-lived `LISTEN`\n * client from the pool and delivers cross-process notifications.\n *\n * When `false` (default):\n * - `commit()` skips the notify SQL entirely — zero per-write\n * overhead.\n * - The `notify` method is **not present on the instance**, so the\n * orchestrator's `if (store.notify)` auto-wire short-circuits and\n * no LISTEN client is allocated.\n *\n * Single-instance deployments should leave this off. Multi-process\n * deployments that need sub-poll reaction latency turn it on\n * **on every store instance** (writers and listeners both).\n */\n notify?: boolean;\n /**\n * Adapter-layer envelope encryption for the `events.pii` column.\n * Optional — when present, every non-null PII payload is encrypted\n * before INSERT and decrypted on every read; when absent, the\n * column is stored and read as plaintext (the framework's default\n * behavior).\n *\n * Cipher and wire format come from `@rotorsoft/act-crypto`:\n * AES-256-GCM with a versioned base64-framed envelope. The\n * jsonb column distinguishes encrypted from plaintext rows by\n * type — encrypted writes land as JSONB strings, plaintext writes\n * as JSONB objects — so existing data continues to read through\n * transparently after enabling encryption on new commits.\n *\n * `forget_pii` semantics are unchanged: the column is set to\n * `NULL` regardless of whether the prior value was plaintext or\n * ciphertext.\n *\n * Encryption at rest at the **storage** layer (`pgcrypto`, RDS\n * TDE, Cloud SQL TDE) composes orthogonally — defense in depth\n * without coordination. See `docs/docs/guides/pii-encryption-at-rest.md`\n * for the full decision matrix.\n */\n pii_encryption?: Encryption;\n}> &\n pg.PoolConfig;\n\nconst SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n// PostgreSQL SQLSTATE for `unique_violation` — surfaces when a concurrent\n// commit beats us between the version SELECT and the INSERT, hitting the\n// unique index on (stream, version). Stable across PG versions per the\n// SQL standard. See: https://www.postgresql.org/docs/current/errcodes-appendix.html\nconst PG_UNIQUE_VIOLATION = \"23505\";\n\n// The two SQLSTATEs Postgres raises for a NUL byte (`\\u0000`) in a committed\n// payload: `22P05` from the jsonb parser (`data` / `meta` / `pii`) and\n// `22021` from the UTF-8 decoder (`stream` / `name`, which are text).\n//\n// A NUL is legal JSON and a legal JS string, so it passes Zod and reaches the\n// store, where InMemory and SQLite (TEXT) round-trip it happily and only\n// Postgres refuses. The framework does not reject it up front — that would\n// mean walking every payload on the framework's hottest path to enforce one\n// adapter's storage limit on all three. What is worth doing is not letting\n// the driver's message (\"unsupported Unicode escape sequence\") be the whole\n// story, since it names neither the stream nor the event.\nconst PG_NUL_BYTE = new Set([\"22P05\", \"22021\"]);\n\n// Channel-name prefix for cross-process commit notifications. The\n// effective channel is namespaced per `(schema, table)` so two\n// PostgresStores pointed at distinct event tables in the same database\n// don't cross-talk. PG channel names are case-folded unless quoted; we\n// stick to lowercase identifiers so a future `LISTEN act_commit_*` from\n// any client (psql, scripts, alternative consumers) matches without\n// surprises.\nconst NOTIFY_CHANNEL_PREFIX = \"act_commit\";\n\n// PG caps NOTIFY payloads at 8000 bytes — `pg_notify` raises\n// \"payload string too long\" (SQLSTATE 54000) at or above the cap, and\n// inside the commit transaction that error would abort the whole INSERT\n// batch. `commit()` measures the serialized payload first and skips the\n// NOTIFY when it would not fit: listeners fall back to the poll path, so\n// delivery degrades to the next poll cycle but the commit never fails.\n// See: https://www.postgresql.org/docs/current/sql-notify.html\nconst NOTIFY_MAX_PAYLOAD_BYTES = 8000;\n\n// Capped exponential backoff for re-establishing the LISTEN subscription\n// after the dedicated client emits `error` (backend restart, failover,\n// network drop — #1189). Between attempts the store degrades to the poll\n// path, so callers never miss events — they just fall back to the next\n// drain cycle for cross-process wakeups until the LISTEN client is back.\nconst NOTIFY_RECONNECT_BASE_MS = 250;\nconst NOTIFY_RECONNECT_MAX_MS = 30_000;\n\n// Keeps a destroyed LISTEN client from re-raising a late socket `error` as an\n// uncaught exception. Attached to the dead client through `release(true)` so it\n// is never listener-less during the reconnect backoff window (#1231).\nconst swallow_error = (): void => {};\n\nfunction notify_channel(schema: string, table: string): string {\n return `${NOTIFY_CHANNEL_PREFIX}_${schema}_${table}`;\n}\nfunction assert_safe_identifier(value: string, label: string) {\n if (!SAFE_IDENTIFIER.test(value))\n throw new Error(`Unsafe SQL identifier for ${label}: \"${value}\"`);\n}\n\nconst DEFAULT_CONFIG: Config = {\n host: \"localhost\",\n port: 5432,\n database: \"postgres\",\n user: \"postgres\",\n password: \"postgres\",\n schema: \"public\",\n table: \"events\",\n notify: false,\n // Opinionated pool defaults (#1119). node-postgres ships `max: 10`\n // with no acquisition timeout and no statement timeout — a saturated\n // pool makes every caller hang indefinitely instead of failing with\n // a diagnosable error. Nearly every store method holds a client for\n // a multi-statement transaction, so multi-lane drains plus API\n // traffic can exhaust a 10-client pool quickly. All four values are\n // plain `pg.PoolConfig` fields — caller config overrides any of them\n // via the constructor spread.\n //\n // - `max: 20` — floor for the default lane's parallel handler budget\n // (streamLimit 10, each commit holds a client) plus API commits,\n // the optional LISTEN client, and headroom. Sizing rule in the\n // README: Σ per-lane streamLimit + API concurrency + notify + 2–4.\n // - `connectionTimeoutMillis: 10_000` — fail acquisition fast (the\n // pg default of 0 waits forever); surfaces as StoreError via\n // `_client()` so operators see *which* operation starved.\n // - `idleTimeoutMillis: 30_000` — keep idle clients warm across\n // drain cycles (cycleMs can exceed the pg default of 10s in\n // low-traffic deployments) without pinning connections for long.\n // - `statement_timeout: 60_000` — per-statement (not per-transaction)\n // ceiling; every statement the store issues (claim CTE, seed DDL,\n // truncate, restore's per-event inserts) legitimately completes\n // orders of magnitude faster, so 60s only fires on a wedged server\n // or a lost lock instead of holding the client hostage.\n max: 20,\n connectionTimeoutMillis: 10_000,\n idleTimeoutMillis: 30_000,\n statement_timeout: 60_000,\n};\n\n/**\n * Production-ready PostgreSQL event store implementation.\n *\n * PostgresStore provides persistent, scalable event storage using PostgreSQL.\n * It implements the full {@link Store} interface with production-grade features:\n *\n * **Features:**\n * - Persistent event storage with ACID guarantees\n * - Optimistic concurrency control via version numbers\n * - Distributed stream processing with leasing\n * - Snapshot support for performance optimization\n * - Connection pooling for scalability\n * - Automatic table and index creation\n *\n * **Database Schema:**\n * - Events table: Stores all committed events\n * - Streams table: Tracks stream metadata and leases\n * - Indexes on stream, version, and timestamps for fast queries\n *\n * @example Basic setup\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * import { PostgresStore } from \"@rotorsoft/act-pg\";\n *\n * store(new PostgresStore({\n * host: \"localhost\",\n * port: 5432,\n * database: \"myapp\",\n * user: \"postgres\",\n * password: \"secret\"\n * }));\n *\n * const app = act()\n * .withState(Counter)\n * .build();\n * ```\n *\n * @example With custom schema and table\n * ```typescript\n * import { PostgresStore } from \"@rotorsoft/act-pg\";\n *\n * const pgStore = new PostgresStore({\n * host: process.env.DB_HOST || \"localhost\",\n * port: parseInt(process.env.DB_PORT || \"5432\"),\n * database: process.env.DB_NAME || \"myapp\",\n * user: process.env.DB_USER || \"postgres\",\n * password: process.env.DB_PASSWORD,\n * schema: \"events\", // Custom schema\n * table: \"act_events\" // Custom table name\n * });\n *\n * // Initialize tables\n * await pgStore.seed();\n * ```\n *\n * @example Connection pooling configuration\n * ```typescript\n * // PostgresStore uses node-postgres (pg) connection pooling with\n * // opinionated defaults: max 20, connectionTimeoutMillis 10s,\n * // idleTimeoutMillis 30s, statement_timeout 60s. Any pg.PoolConfig\n * // field passed to the constructor overrides the default.\n *\n * const pgStore = new PostgresStore({\n * host: \"db.example.com\",\n * port: 5432,\n * database: \"production\",\n * user: \"app_user\",\n * password: process.env.DB_PASSWORD,\n * max: 40 // lanes × streamLimit + API concurrency + headroom\n * });\n * ```\n *\n * @example Multi-tenant setup\n * ```typescript\n * // Use separate schemas per tenant\n * const tenants = [\"tenant1\", \"tenant2\", \"tenant3\"];\n *\n * for (const tenant of tenants) {\n * const tenantStore = new PostgresStore({\n * host: \"localhost\",\n * database: \"multitenant\",\n * schema: tenant, // Each tenant gets own schema\n * table: \"events\"\n * });\n * await tenantStore.seed();\n * }\n * ```\n *\n * @example Querying PostgreSQL directly\n * ```typescript\n * // For advanced queries, you can access pg client\n * const pgStore = new PostgresStore(config);\n * await pgStore.seed();\n *\n * // Use the store's query method for standard queries\n * await pgStore.query(\n * (event) => console.log(event),\n * { stream: \"user-123\", limit: 100 }\n * );\n * ```\n *\n * @see {@link Store} for the interface definition\n * @see {@link InMemoryStore} for development/testing\n * @see {@link store} for injecting stores\n * @see {@link https://node-postgres.com/ | node-postgres documentation}\n *\n * @category Adapters\n */\nexport class PostgresStore implements Store {\n private _pool;\n readonly config: Config;\n private _fqt: string;\n private _fqs: string;\n /** Correlate checkpoint table (#1484) — one row, id 0. */\n private _fqc: string;\n /**\n * Per-instance writer identifier embedded in every NOTIFY payload. The\n * `notify()` LISTEN handler skips payloads where `by === this._by`,\n * giving the `\"notified\"` lifecycle event a clean cross-process\n * semantic — local commits never echo back through this channel.\n */\n private readonly _by: string = randomUUID();\n /**\n * Effective NOTIFY channel for this store. Computed from `(schema,\n * table)` at construction so multiple stores in the same database\n * stay isolated.\n */\n private readonly _channel: string;\n /** Active LISTEN client (one per `notify()` subscription). */\n private _listen_client: pg.PoolClient | undefined;\n /**\n * Notification listener attached to the active LISTEN client. Tracked\n * separately so the re-subscribe / dispose paths can detach it before\n * destroying the client — without this, a pool that reused the\n * connection would re-fire the stale handler.\n */\n private _listen_handler: ((msg: pg.Notification) => void) | undefined;\n /**\n * Error listener attached to the active LISTEN client. node-postgres\n * removes its idle-error guard on checkout, so a checked-out client\n * that emits `error` (backend restart, failover, network drop) with no\n * listener is an uncaught exception — a process crash (#1189). Tracked\n * alongside `_listen_handler` so teardown detaches it in lockstep.\n */\n private _listen_error_handler: ((err: Error) => void) | undefined;\n /**\n * The caller's notification handler for the active subscription, kept\n * so the self-healing reconnect path (#1189) can re-establish LISTEN\n * on a fresh client after the dedicated one emits `error`. Cleared by\n * `_teardown_listen`, which is what makes disposal cancel any pending\n * reconnect.\n */\n private _notify_handler:\n | ((notification: StoreNotification) => void)\n | undefined;\n /**\n * Pending reconnect timer, if a LISTEN client error scheduled one.\n * Tracked so `_teardown_listen` (and therefore `dispose()`) can cancel\n * it — a reconnect must never fire after teardown.\n */\n private _reconnect_timer: ReturnType<typeof setTimeout> | undefined;\n /**\n * Consecutive reconnect attempts since the last healthy LISTEN, used to\n * grow the capped exponential backoff. Reset to 0 once a re-LISTEN\n * succeeds.\n */\n private _reconnect_attempts = 0;\n /**\n * Cross-process commit subscription. **Present only when\n * `config.notify === true`** — the orchestrator's auto-wire path\n * checks `if (store.notify)`, so omitting the method keeps\n * single-instance deployments free of any LISTEN/NOTIFY overhead\n * (no dedicated client, no per-commit `pg_notify`).\n *\n * @see {@link Config.notify} for the rationale and the multi-process\n * contract.\n */\n notify?: (\n handler: (notification: StoreNotification) => void\n ) => Promise<NotifyDisposer>;\n\n /**\n * Memoized key resolver for the optional `pii_encryption` envelope.\n * Initialized in the constructor when encryption is configured;\n * `undefined` otherwise. The resolver caches the operator's key on\n * first use — rotation means restarting the store with a fresh\n * provider.\n */\n private readonly _resolve_pii_key: (() => Promise<Buffer>) | undefined;\n\n /**\n * Create a new PostgresStore instance.\n * @param config Partial configuration (host, port, user, password, schema, table, etc.)\n */\n constructor(config: Partial<Config> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n assert_safe_identifier(this.config.schema, \"schema\");\n assert_safe_identifier(this.config.table, \"table\");\n const {\n schema: _,\n table: __,\n pii_encryption: ___,\n ...poolConfig\n } = this.config;\n // Per-Pool JSONB reviver (#1198): scoped here, never global.\n this._pool = new Pool({ ...poolConfig, types: scopedTypes });\n this._fqt = `\"${this.config.schema}\".\"${this.config.table}\"`;\n this._fqs = `\"${this.config.schema}\".\"${this.config.table}_streams\"`;\n this._fqc = `\"${this.config.schema}\".\"${this.config.table}_correlated\"`;\n this._channel = notify_channel(this.config.schema, this.config.table);\n // Attach the notify subscriber only when the user opted in. With\n // notify off, `this.notify` is `undefined`, the orchestrator skips\n // its auto-wire, and no LISTEN client is ever allocated.\n if (this.config.notify) {\n this.notify = this._subscribe_notifications.bind(this);\n }\n this._resolve_pii_key = this.config.pii_encryption\n ? makeKeyResolver(this.config.pii_encryption)\n : undefined;\n }\n\n /**\n * Acquire a pooled client, translating acquisition failures into\n * {@link StoreError} with the calling operation as context. With the\n * default `connectionTimeoutMillis`, a saturated pool fails here\n * after 10s with `Store operation \"<operation>\" failed` (driver\n * error preserved as `cause`) instead of hanging indefinitely.\n * Every method that checks out a client routes through this helper.\n */\n private async _client(operation: string): Promise<pg.PoolClient> {\n try {\n return await this._pool.connect();\n } catch (error) {\n throw new StoreError(operation, { cause: error });\n }\n }\n\n /**\n * Dispose of the store and close all database connections.\n * Releases any active LISTEN client first so the pool can drain cleanly.\n * @returns Promise that resolves when all connections are closed\n */\n async dispose() {\n await this._teardown_listen();\n await this._pool.end();\n }\n\n /**\n * Tear down the active LISTEN subscription if any: cancel any pending\n * reconnect, forget the caller's handler (so no reconnect can fire\n * after teardown), detach the notification + error listeners, run\n * UNLISTEN, and destroy the dedicated client (do not return it to the\n * pool — its listeners are removed but destroying belt-and-braces\n * guards against any future change in pg-pool semantics that could\n * re-issue a half-clean client).\n *\n * Clearing `_notify_handler` and the reconnect timer here is what makes\n * `dispose()` safe during a pending reconnect (#1189): a scheduled\n * `_reconnect` bails the moment it finds no handler.\n */\n private async _teardown_listen() {\n if (this._reconnect_timer) {\n clearTimeout(this._reconnect_timer);\n this._reconnect_timer = undefined;\n }\n this._notify_handler = undefined;\n this._reconnect_attempts = 0;\n if (!this._listen_client) return;\n // _listen_handler and _listen_error_handler are set in lockstep with\n // _listen_client in _open_listen, so if the client is present, both\n // handlers are too.\n this._listen_client.removeListener(\"notification\", this._listen_handler!);\n this._listen_client.removeListener(\"error\", this._listen_error_handler!);\n this._listen_handler = undefined;\n this._listen_error_handler = undefined;\n try {\n await this._listen_client.query(`UNLISTEN ${this._channel}`);\n } catch {\n // best-effort — pool end (or destroy) tears the connection down\n }\n this._listen_client.release(true);\n this._listen_client = undefined;\n }\n\n /**\n * Seed the database with required tables, indexes, and schema for event storage.\n * @returns Promise that resolves when seeding is complete\n * @throws Error if seeding fails\n */\n async seed() {\n const client = await this._client(\"seed\");\n\n try {\n await client.query(\"BEGIN\");\n\n // Serialize concurrent cold boots. IF NOT EXISTS DDL is not\n // race-safe while the objects are first being created — two\n // connections creating the same table simultaneously can trip\n // catalog unique-key errors — so N workers booting an empty\n // schema at once serialize here instead. Transaction-scoped:\n // the lock releases at COMMIT/ROLLBACK, and steady-state\n // re-seeds pass through in microseconds.\n // Two locks, because two different scopes are being guarded. The\n // schema-scoped one covers `CREATE SCHEMA` below: keying only on\n // `schema.table` let stores sharing a schema but using different table\n // names hash to different keys, so their CREATE SCHEMA calls raced the\n // catalog and all but one failed with a duplicate-key error on\n // pg_namespace — `IF NOT EXISTS` is not atomic against a concurrent\n // creator (#1421). The table-scoped one keeps the per-table DDL below\n // concurrent across tables in the same schema.\n await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [\n this.config.schema,\n ]);\n await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [\n `${this.config.schema}.${this.config.table}`,\n ]);\n\n // Create schema\n await client.query(\n `CREATE SCHEMA IF NOT EXISTS \"${this.config.schema}\";`\n );\n\n // Events table\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqt} (\n id serial PRIMARY KEY,\n name text COLLATE pg_catalog.\"default\" NOT NULL,\n data jsonb,\n stream text COLLATE pg_catalog.\"default\" NOT NULL,\n version int NOT NULL,\n created timestamptz NOT NULL DEFAULT now(),\n meta jsonb,\n pii jsonb\n ) TABLESPACE pg_default;`\n );\n // Migration for tables created before pii_isolation (#870).\n // Variable-length encoding skips NULL columns entirely, so events\n // without sensitive declarations pay zero extra bytes on disk.\n await client.query(\n `ALTER TABLE ${this._fqt} ADD COLUMN IF NOT EXISTS pii jsonb;`\n );\n\n // Indexes on events\n await client.query(\n `CREATE UNIQUE INDEX IF NOT EXISTS \"${this.config.table}_stream_ix\" \n ON ${this._fqt} (stream COLLATE pg_catalog.\"default\", version);`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_name_ix\" \n ON ${this._fqt} (name COLLATE pg_catalog.\"default\");`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_created_id_ix\" \n ON ${this._fqt} (created, id);`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_correlation_ix\"\n ON ${this._fqt} ((meta ->> 'correlation') COLLATE pg_catalog.\"default\");`\n );\n // Partial index over snapshot rows only, so the with_snaps \"resume\n // at the latest snapshot\" floor (MAX(id) WHERE stream=? AND\n // name='__snapshot__') is an O(log) lookup and costs nothing for\n // streams that have no snapshot (the index has no rows for them).\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_snapshot_ix\"\n ON ${this._fqt} (stream COLLATE pg_catalog.\"default\", id)\n WHERE name = '${SNAP_EVENT}';`\n );\n // The complement of the snapshot index, and the one `claim`'s has-work\n // probe seeks on: \"does this source stream have a non-snapshot event\n // past the watermark?\" (#1448). Partial over non-snapshot rows so the\n // two indexes partition the table rather than overlapping.\n //\n // Without it the probe fell back to the pk and scanned forward from\n // each stream's watermark — and because a dormant aggregate's\n // watermark is old, that tail is long and grows with the table. At 10k\n // subscribed streams a single claim cost 5,792 ms; with this index and\n // the source-class split in `claim`, 10.3 ms.\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_stream_id_ix\"\n ON ${this._fqt} (stream COLLATE pg_catalog.\"default\", id)\n WHERE name <> '${SNAP_EVENT}';`\n );\n\n // Streams table\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqs} (\n stream text COLLATE pg_catalog.\"default\" PRIMARY KEY,\n source text COLLATE pg_catalog.\"default\",\n at int NOT NULL DEFAULT -1,\n retry int NOT NULL DEFAULT -1,\n blocked boolean NOT NULL DEFAULT false,\n error text,\n leased_by text,\n leased_until timestamptz,\n priority int NOT NULL DEFAULT 0,\n lane text NOT NULL DEFAULT 'default',\n deferred_at timestamptz,\n correlated_at int\n ) TABLESPACE pg_default;`\n );\n // Correlate checkpoint (#1484). Its own single-row relation rather\n // than a reserved subscription: a subscription row is counted by every\n // stream-scoped operator surface (`prioritize`, `reset`, `unblock`,\n // `query_streams`, `blocked_streams`) and would inflate the counts\n // they report. No lease columns: the write is a monotonic `MAX`, so\n // concurrent correlators converge without holding anything.\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqc} (\n id int PRIMARY KEY DEFAULT 0,\n at int NOT NULL DEFAULT -1,\n CONSTRAINT ${this.config.table}_correlated_singleton CHECK (id = 0)\n ) TABLESPACE pg_default;`\n );\n await client.query(\n `INSERT INTO ${this._fqc} (id) VALUES (0) ON CONFLICT (id) DO NOTHING;`\n );\n\n // Migration for tables created before priority lanes (ACT-102).\n // `ADD COLUMN IF NOT EXISTS` is a no-op when the column is\n // already present, so this is safe on every seed call.\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS priority int NOT NULL DEFAULT 0;`\n );\n // Migration for tables created before drain lanes (ACT-1103).\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS lane text NOT NULL DEFAULT 'default';`\n );\n // Migration for tables created before deferred reactions (#1090).\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS deferred_at timestamptz;`\n );\n // Migration for tables created before the work set (#1485).\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS correlated_at int;`\n );\n // Rows that predate the mark get one, at the log's head (#1488).\n // `claim` is mark-only now, so a row left at NULL would silently stop\n // being served, and correlate cannot rescue it — its checkpoint is\n // long past those events.\n //\n // This is the one mark the system writes without having resolved an\n // event to the target, and it is deliberately an over-estimate: it\n // says \"worth one look\", not \"there is work here\". The first drain\n // pass claims each such stream once, fetches its window, handles\n // whatever is really there, and acks — after which the watermark\n // catches up to the mark and the stream leaves the claimable set with\n // an honest position. One extra cycle per pre-existing subscription,\n // paid once, in exchange for a schema step with no per-row probing.\n //\n // NULL rows created *after* the upgrade (a static target subscribed\n // but not yet correlated) are picked up by the same statement on a\n // later seed, which costs them the same single empty cycle.\n await client.query(\n `UPDATE ${this._fqs}\n SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM ${this._fqt})\n WHERE correlated_at IS NULL;`\n );\n // Migration for tables created before the retry widening (#1190).\n // `claim()` increments `retry` on every acquisition and never\n // resets it for a zero-progress `blockOnError: false` stream, so a\n // poison stream marches the counter up without bound. The original\n // `smallint` column overflowed at 32768, throwing \"smallint out of\n // range\" and killing every claim in the lane. Widen to `int` so PG\n // matches the unbounded SQLite/InMemory adapters, preserving every\n // existing value (smallint ⊂ int). Guarded on the current type so\n // steady-state re-seeds skip the DDL — and its brief ACCESS\n // EXCLUSIVE lock — entirely once the column is already `integer`.\n await client.query(\n `DO $$\n BEGIN\n IF EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = '${this.config.schema}'\n AND table_name = '${this.config.table}_streams'\n AND column_name = 'retry'\n AND data_type = 'smallint'\n ) THEN\n EXECUTE 'ALTER TABLE ${this._fqs} ALTER COLUMN retry TYPE integer';\n END IF;\n END\n $$;`\n );\n\n // Migration for tables created before the identifier widening (#1420).\n // `stream` / `source` / `name` were `varchar(100)`, while InMemory is\n // unbounded and SQLite uses TEXT. The framework DERIVES identifiers that\n // can exceed the cap — `.autocloses` synthesizes a target of\n // `\"__autoclose__:\" + stream` (14 chars), so an 87-char stream commits\n // fine and then its subscribe fails. `correlate-cycle` only advances its\n // checkpoint after subscribe succeeds, so that throw pins the checkpoint\n // and stalls EVERY dynamic-resolver reaction app-wide, restart included.\n // `text` and `varchar` are byte-identical in PG storage and indexing, so\n // this costs nothing. Guarded on the current type so steady-state\n // re-seeds skip the DDL and its brief ACCESS EXCLUSIVE lock.\n for (const [table, columns] of [\n [this.config.table, [\"stream\", \"name\"]],\n [`${this.config.table}_streams`, [\"stream\", \"source\"]],\n ] as const) {\n for (const column of columns) {\n await client.query(\n `DO $$\n BEGIN\n IF EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = '${this.config.schema}'\n AND table_name = '${table}'\n AND column_name = '${column}'\n AND data_type = 'character varying'\n ) THEN\n EXECUTE 'ALTER TABLE \"${this.config.schema}\".\"${table}\" ALTER COLUMN ${column} TYPE text';\n END IF;\n END\n $$;`\n );\n }\n }\n\n // Composite index for `claim()` — `(blocked, priority DESC, at)`\n // matches the lagging-frontier ORDER BY exactly so the planner\n // can serve the lag CTE from the index without a sort. The\n // `_streams_fetch_ix` index is dropped because the new one\n // supersedes it (`(blocked, at)` is a prefix of the new key\n // when the planner reads `priority` as fixed).\n await client.query(\n `DROP INDEX IF EXISTS \"${this.config.schema}\".\"${this.config.table}_streams_fetch_ix\"`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_claim_ix\"\n ON ${this._fqs} (blocked, priority DESC, at);`\n );\n // Lane filter index (ACT-1103).\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_lane_ix\"\n ON ${this._fqs} (lane);`\n );\n // The correlated set IS the index (#1485). `at < correlated_at` is a legal\n // partial-index predicate — immutable, single-row, no cross-row\n // reference — so the index holds only streams with work: `LIMIT` pushes\n // into it, a stream leaves when `ack` advances `at` to `correlated_at`,\n // and re-enters when correlate raises the mark. That makes `claim` an\n // index scan of at most `lagging + leading` rows, independent of how\n // many streams are subscribed.\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_correlated_at_ix\"\n ON ${this._fqs} (lane, priority DESC, at)\n WHERE blocked = false AND at < correlated_at;`\n );\n\n await client.query(\"COMMIT\");\n logger.info(\n `Seeded schema \"${this.config.schema}\" with table \"${this.config.table}\"`\n );\n } catch (error) {\n await client.query(\"ROLLBACK\");\n logger.error(error);\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Drop all tables and schema created by the store (for testing or cleanup).\n * @returns Promise that resolves when the schema is dropped\n */\n async drop() {\n await this._pool.query(\n `\n DO $$\n BEGIN\n IF EXISTS (SELECT 1 FROM information_schema.schemata\n WHERE schema_name = '${this.config.schema}'\n ) THEN\n EXECUTE 'DROP TABLE IF EXISTS ${this._fqt}';\n EXECUTE 'DROP TABLE IF EXISTS ${this._fqc}, ${this._fqs}';\n IF '${this.config.schema}' <> 'public' THEN\n EXECUTE 'DROP SCHEMA \"${this.config.schema}\" CASCADE';\n END IF;\n END IF;\n END\n $$;\n `\n );\n }\n\n /**\n * Query events from the store, optionally filtered by stream, event name, time, etc.\n *\n * @param callback Function called for each event found\n * @param query (Optional) Query filter (stream, names, before, after, etc.)\n * @returns The number of events found\n *\n * @example\n * await store.query((event) => console.log(event), { stream: \"A\" });\n */\n async query<E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ) {\n const {\n stream,\n names,\n before,\n after,\n limit,\n created_before,\n created_after,\n backward,\n correlation,\n with_snaps = false,\n } = query || {};\n\n let sql = `SELECT * FROM ${this._fqt}`;\n const conditions: string[] = [];\n const values: any[] = [];\n\n if (query) {\n if (typeof after !== \"undefined\") {\n values.push(after);\n conditions.push(`id>$${values.length}`);\n } else if (with_snaps && query.stream_exact && stream) {\n // Resume at the latest snapshot for this stream so pre-snapshot\n // events aren't scanned. No snapshot → MAX is NULL → -1 → full\n // stream. An explicit `after` (above) wins. The orchestrator only\n // sets `with_snaps` for an unbounded current-state load — it\n // suppresses the flag under any `asOf` bound (RFC 1274) — so the\n // floor never needs to re-check `before`/`created_*`/`limit` here.\n values.push(stream);\n conditions.push(\n `id >= (SELECT COALESCE(MAX(id), -1) FROM ${this._fqt} WHERE stream=$${values.length} AND name='${SNAP_EVENT}')`\n );\n } else {\n conditions.push(\"id>-1\");\n }\n if (stream) {\n values.push(stream);\n conditions.push(\n query.stream_exact\n ? `stream = $${values.length}`\n : `stream ~ $${values.length}`\n );\n }\n if (names !== undefined) {\n // #1199: `names: []` means \"match no event names\" — an empty\n // allow-list. `name = ANY('{}')` is always false, matching the\n // InMemory/SQLite semantics. Historically a truthy `names?.length`\n // guard dropped the empty filter and returned ALL — the opposite.\n values.push(names);\n conditions.push(`name = ANY($${values.length})`);\n }\n if (before !== undefined) {\n // #1199: `!== undefined` so a falsy-zero `before: 0` (strictly\n // \"id < 0\", i.e. match nothing) is honored, not dropped.\n values.push(before);\n conditions.push(`id<$${values.length}`);\n }\n if (created_after) {\n values.push(created_after.toISOString());\n conditions.push(`created>$${values.length}`);\n }\n if (created_before) {\n values.push(created_before.toISOString());\n conditions.push(`created<$${values.length}`);\n }\n if (correlation) {\n values.push(correlation);\n conditions.push(`meta->>'correlation'=$${values.length}`);\n }\n if (!with_snaps) {\n conditions.push(`name <> '${SNAP_EVENT}'`);\n }\n }\n if (conditions.length) {\n sql += \" WHERE \" + conditions.join(\" AND \");\n }\n sql += ` ORDER BY id ${backward ? \"DESC\" : \"ASC\"}`;\n if (limit) {\n values.push(limit);\n sql += ` LIMIT $${values.length}`;\n }\n\n const result = await this._pool.query<Committed<E, keyof E>>(sql, values);\n for (const row of result.rows) {\n // Decrypt the pii column when encryption is configured and the\n // stored value is a string (encrypted writes land as JSONB\n // strings; plaintext rows land as JSONB objects, including\n // legacy data committed before encryption was enabled). The\n // type-based discriminator means mixed-data rollouts read\n // through transparently. The cast is local — `Committed.pii`\n // is `readonly` on the public type, but rows materialized from\n // the driver are mutable in-flight before they cross back to\n // the framework.\n if (this._resolve_pii_key && typeof row.pii === \"string\") {\n const decrypted = await decrypt(\n row.pii,\n this._resolve_pii_key,\n dateReviver\n );\n (row as { pii: unknown }).pii = decrypted;\n }\n await Promise.resolve(callback(row));\n }\n\n return result.rowCount ?? 0;\n }\n\n /**\n * Commit new events to the store for a given stream, with concurrency control.\n *\n * @param stream The stream name\n * @param msgs Array of messages (event name and data)\n * @param meta Event metadata (correlation, causation, etc.)\n * @param expectedVersion (Optional) Expected stream version for concurrency control\n * @returns Array of committed events\n * @throws ConcurrencyError if the expected version does not match\n */\n async commit<E extends Schemas>(\n stream: string,\n msgs: Message<E, keyof E>[],\n meta: EventMeta,\n expectedVersion?: number\n ) {\n if (msgs.length === 0) return [];\n // Serialize commit VISIBILITY, not just id assignment. `id` is a\n // serial: it is assigned at INSERT time but the row appears at\n // COMMIT time, and every watermark consumer (the claim has-work\n // probe, fetch's `after`, the correlate checkpoint) assumes id\n // order equals visibility order. Without a fence two concurrent\n // commits to different streams can surface out of id order, and a\n // reader that acks past the higher id permanently skips the lower\n // one — the classic event-store gap problem. Same-stream commits\n // were already serialized by the (stream, version) unique index;\n // the advisory lock below extends the guarantee across streams.\n //\n // The whole commit is TWO round trips with NO client round trip\n // inside the lock window: an unlocked head probe (its own implicit\n // transaction — optimistic concurrency is guarded by the unique\n // index, not the probe), then ONE autocommit statement that\n // acquires the xact-scoped lock in a CTE, inserts the batch, and\n // (when enabled) raises the NOTIFY — the lock is held only for\n // server-side execution plus the implicit COMMIT, never across a\n // client round trip. The pooled client is checked out through\n // `_client` so acquisition failures keep their StoreError context\n // (#1119); checkout itself is in-process, not a round trip.\n const client = await this._client(\"commit\");\n try {\n const last = await client.query<{ version: number }>(\n `SELECT version FROM ${this._fqt}\n WHERE stream=$1 ORDER BY version DESC LIMIT 1`,\n [stream]\n );\n let version = last.rows.at(0)?.version ?? -1;\n if (typeof expectedVersion === \"number\" && version !== expectedVersion)\n throw new ConcurrencyError(\n stream,\n version,\n msgs as unknown as Message<Schemas, string>[],\n expectedVersion\n );\n\n // Encrypt the pii payloads when encryption is configured and\n // there's anything to encrypt — `null` passes through verbatim so\n // `forget_pii` semantics survive intact (a NULL stays NULL).\n // Encrypted output is `JSON.stringify`-ed so the bare base64\n // string casts to jsonb as a JSON string literal. Data/pii travel\n // as text[] and cast to jsonb in SQL — the pg driver can't\n // serialize object elements inside a jsonb[] parameter.\n const base_version = version;\n const names: string[] = [];\n const datas: string[] = [];\n const piis: (string | null)[] = [];\n const versions: number[] = [];\n for (const { name, data, pii } of msgs) {\n version++;\n names.push(name as string);\n datas.push(JSON.stringify(data));\n piis.push(\n this._resolve_pii_key && pii != null\n ? JSON.stringify(await encrypt(pii, this._resolve_pii_key))\n : pii != null\n ? JSON.stringify(pii)\n : null\n );\n versions.push(version);\n }\n\n // The cross join on the lock CTE forces the advisory lock to be\n // acquired before any row (and therefore any serial id) is\n // produced. Single-event commits (the overwhelmingly common\n // shape) skip the unnest machinery for a leaner plan. The NOTIFY\n // rides the same statement as a CTE: one notification per commit\n // transaction with the full batch, delivered at the implicit\n // COMMIT, skipped in SQL when the payload would exceed PG's cap\n // (listeners fall back to the poll path — degraded latency,\n // never lost events), and skipped entirely when\n // `config.notify === false` (the default). The final select LEFT\n // JOINs the notify CTE so it is referenced (a bare SELECT CTE\n // would otherwise be skipped) without changing row multiplicity —\n // it yields at most one row.\n const insert_select =\n msgs.length === 1\n ? `SELECT $1, $2::jsonb, $3::jsonb, $5, $4::int, $6 FROM l`\n : `SELECT u.name, u.data::jsonb, u.pii::jsonb, $5, u.version, $6\n FROM l, unnest($1::text[], $2::text[], $3::text[], $4::int[])\n WITH ORDINALITY AS u(name, data, pii, version, ord)\n ORDER BY u.ord`;\n const notify_ctes = this.config.notify\n ? `,\n payload AS (\n SELECT json_build_object(\n 'stream', $5::text,\n 'events', json_agg(json_build_object('id', ins.id, 'name', ins.name) ORDER BY ins.version),\n 'by', $9::text\n )::text AS p\n FROM ins\n ),\n n AS (\n SELECT pg_notify($8, payload.p) FROM payload\n WHERE octet_length(payload.p) < $10\n )`\n : \"\";\n const final_select = this.config.notify\n ? \"SELECT ins.* FROM ins LEFT JOIN n ON true ORDER BY ins.version\"\n : \"SELECT * FROM ins ORDER BY version\";\n const sql = `WITH l AS (SELECT pg_advisory_xact_lock(hashtext($7))),\n ins AS (\n INSERT INTO ${this._fqt}(name, data, pii, stream, version, meta)\n ${insert_select}\n RETURNING *\n )${notify_ctes}\n ${final_select}`;\n const base_params =\n msgs.length === 1\n ? [names[0], datas[0], piis[0], versions[0], stream, meta, this._fqt]\n : [names, datas, piis, versions, stream, meta, this._fqt];\n const params = this.config.notify\n ? [...base_params, this._channel, this._by, NOTIFY_MAX_PAYLOAD_BYTES]\n : base_params;\n\n try {\n const { rows } = await client.query<Committed<E, keyof E>>(sql, params);\n // Decrypt before handing back to the caller — the committed\n // event the framework returns must carry the cleartext payload\n // (the reducer chain runs against `event.pii`). The encrypted\n // value only lives at rest in the column; never in memory past\n // this point.\n if (this._resolve_pii_key) {\n for (const row of rows) {\n if (typeof row.pii === \"string\") {\n const decrypted = await decrypt(\n row.pii,\n this._resolve_pii_key,\n dateReviver\n );\n (row as { pii: unknown }).pii = decrypted;\n }\n }\n }\n return rows;\n } catch (error) {\n // PG unique-violation on (stream, version) — a concurrent commit\n // beat us between the head probe and this INSERT. Surface as\n // ConcurrencyError so callers retry on the framework signal\n // instead of an adapter-specific error. The statement is its own\n // transaction, so there is nothing to roll back.\n if ((error as { code?: string })?.code === PG_UNIQUE_VIOLATION) {\n throw new ConcurrencyError(\n stream,\n base_version,\n msgs as unknown as Message<Schemas, string>[],\n expectedVersion ?? -1\n );\n }\n if (PG_NUL_BYTE.has((error as { code?: string })?.code ?? \"\")) {\n throw new ValidationError(\n stream,\n msgs as unknown as Readonly<unknown>,\n `Postgres cannot store a NUL byte (\\\\u0000). One of the ${msgs.length} event(s) committed to \"${stream}\" (${msgs.map((m) => String(m.name)).join(\", \")}) carries one in its name, data, meta or pii. InMemory and SQLite accept it, so this commit would have succeeded there — strip NUL bytes at the edge where the data enters. Driver: ${(error as Error).message}`\n );\n }\n throw error;\n }\n } finally {\n client.release();\n }\n }\n\n /**\n * Atomically discovers and leases streams for reaction processing.\n *\n * Uses `FOR UPDATE SKIP LOCKED` to implement zero-contention competing consumers:\n * - Workers never block each other — locked rows are silently skipped\n * - Discovery and locking happen in a single atomic transaction\n * - No wasted polls — every returned stream is exclusively owned\n *\n * @param lagging - Max streams from lagging frontier (ascending watermark)\n * @param leading - Max streams from leading frontier (descending watermark)\n * @param by - Lease holder identifier (UUID)\n * @param millis - Lease duration in milliseconds\n * @returns Leased streams with metadata\n */\n async claim(\n lagging: number,\n leading: number,\n by: string,\n millis: number,\n lane?: string\n ): Promise<Lease[]> {\n const client = await this._client(\"claim\");\n try {\n await client.query(\"BEGIN\");\n const lane_clause = lane !== undefined ? `AND s.lane = $6` : \"\";\n // Fairness reserve (ACT-1223): carve `fair` slots off the lagging\n // budget for pure watermark-order claims so a default-priority\n // lagging stream is never starved out by sustained higher-priority\n // load. `fair` is always $5; the optional `lane` bind is last ($6).\n const fair = lagging >= 2 ? Math.max(1, Math.floor(lagging / 4)) : 0;\n const params: unknown[] =\n lane !== undefined\n ? [lagging, leading, by, millis, fair, lane]\n : [lagging, leading, by, millis, fair];\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n retry: number;\n lagging: boolean;\n lane: string;\n }>(\n `\n WITH\n -- Plain read of the eligible frontier — no row lock here. A CTE\n -- carrying FOR UPDATE never inlines, so locking it would materialize\n -- and lock EVERY claimable stream for this transaction, starving\n -- overlapping competing consumers. We only lock the small\n -- lagging+leading candidate slice, down in the \"locked\" CTE.\n --\n -- Eligibility is a pure subscription-table predicate (#1488).\n -- claim does not read the event log at all: correlate marks the\n -- highest event id that resolves to a target, and at <\n -- correlated_at is the whole question. The probe this replaced ran\n -- an EXISTS against the events table once per eligible row, which\n -- cost O(subscribed streams) per claim per worker no matter how\n -- little work was pending.\n --\n -- Read from the base table so the planner can use the partial index\n -- built for exactly this predicate:\n -- (lane, priority DESC, at) WHERE blocked = false\n -- AND at < correlated_at\n -- It contains only streams with work, so LIMIT pushes into it and a\n -- claim scans at most lagging + leading rows. A stream leaves the\n -- index when ack advances at to the mark, and re-enters when\n -- correlate raises it.\n --\n -- The comparison is NULL-safe by SQL's own rules: an unmarked row\n -- compares unknown and is excluded. That is definitional now\n -- (#1446) — a subscription is claimable iff a mark says so — where\n -- before #1488 it meant \"unknown, fall through to the probe\". An\n -- install upgrading from before the column needs one correlate pass\n -- from a rewound checkpoint to mark its rows; see the runbook in\n -- docs/docs/guides/production-checklist.md.\n available AS (\n SELECT stream, source, at, priority, lane\n FROM ${this._fqs} s\n WHERE s.blocked = false\n AND s.at < s.correlated_at\n ${lane_clause}\n AND (s.leased_by IS NULL OR s.leased_until <= NOW())\n AND (s.deferred_at IS NULL OR s.deferred_at <= NOW())\n ),\n -- Priority lanes (ACT-102): higher priority first, then\n -- lagging-watermark order. With everyone at priority=0 the\n -- ORDER BY collapses to plain at ASC so existing workloads\n -- see no behavior change.\n --\n -- The lagging frontier is a UNION of two portions (ACT-1223): the\n -- priority-ordered portion takes the first (lagging - fair) slots\n -- by priority DESC, at ASC; a fairness reserve then fills fair more\n -- slots by pure at ASC (priority ignored), excluding the ones\n -- already chosen, so a default-priority lagging stream is never\n -- starved out by sustained higher-priority load. With all\n -- priorities equal both portions order by at, a no-op merge.\n lag AS (\n (\n SELECT stream, source, at, lane, TRUE AS lagging\n FROM available\n ORDER BY priority DESC, at ASC\n LIMIT ($1::int - $5::int)\n )\n UNION\n (\n SELECT stream, source, at, lane, TRUE AS lagging\n FROM available\n WHERE stream NOT IN (\n SELECT stream FROM available\n ORDER BY priority DESC, at ASC\n LIMIT ($1::int - $5::int)\n )\n ORDER BY at ASC\n LIMIT $5\n )\n ),\n lead AS (\n SELECT stream, source, at, lane, FALSE AS lagging\n FROM available\n ORDER BY at DESC\n LIMIT $2\n ),\n combined AS (\n SELECT DISTINCT ON (stream) stream, source, at, lane, lagging\n FROM (SELECT * FROM lag UNION ALL SELECT * FROM lead) t\n ORDER BY stream, at\n ),\n -- Lock ONLY the <= lagging+leading candidate rows. The\n -- lease-eligibility predicate is re-asserted here under the lock so a\n -- lease acquired by a competing worker between the unlocked read and\n -- this lock is never stolen. Competing workers SKIP-LOCK just this\n -- slice, not the whole frontier, and claim other eligible streams.\n locked AS (\n SELECT s2.stream\n FROM ${this._fqs} s2\n WHERE s2.stream IN (SELECT stream FROM combined)\n AND s2.blocked = false\n AND (s2.leased_by IS NULL OR s2.leased_until <= NOW())\n AND (s2.deferred_at IS NULL OR s2.deferred_at <= NOW())\n FOR UPDATE OF s2 SKIP LOCKED\n )\n UPDATE ${this._fqs} s\n SET\n leased_by = $3,\n leased_until = NOW() + ($4::integer || ' milliseconds')::interval,\n retry = s.retry + 1\n FROM combined c\n WHERE s.stream = c.stream\n AND s.stream IN (SELECT stream FROM locked)\n RETURNING s.stream, s.source, s.at, s.retry, c.lagging, s.lane\n `,\n params\n );\n await client.query(\"COMMIT\");\n\n return rows.map(({ stream, source, at, retry, lagging, lane }) => ({\n stream,\n source: source ?? undefined,\n at,\n by,\n retry,\n lagging,\n lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"claim\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Registers streams for event processing.\n * Upserts stream entries so they become visible to claim().\n * Also returns the current max watermark across all subscriptions.\n * @param streams - Streams to register with optional source.\n * @returns subscribed count and current max watermark.\n */\n async subscribe(\n streams: SubscribeInput[],\n correlated_at?: number\n ): Promise<{ subscribed: number; watermark: number; correlated_at: number }> {\n const client = await this._client(\"subscribe\");\n try {\n await client.query(\"BEGIN\");\n let subscribed = 0;\n if (streams.length) {\n // Two statements, because `subscribed` means \"newly registered\n // streams\" and not \"rows touched\":\n // 1. INSERT ... ON CONFLICT DO NOTHING — rowCount = inserts.\n // 2. One UPDATE over the existing rows, carrying all three\n // mutable columns. Correlate re-subscribes every target it\n // marks (#1487), so this is a per-scan round trip on the\n // steady-state path — worth one statement rather than three.\n const { rowCount: inserted } = await client.query(\n `\n INSERT INTO ${this._fqs} (stream, source, priority, lane, retry)\n SELECT s->>'stream',\n s->>'source',\n COALESCE((s->>'priority')::int, 0),\n COALESCE(s->>'lane', 'default'),\n -1\n FROM jsonb_array_elements($1::jsonb) AS s\n ON CONFLICT (stream) DO NOTHING\n `,\n [JSON.stringify(streams)]\n );\n subscribed = inserted ?? 0;\n // Priority keeps the max (ACT-102: the highest-priority registered\n // reaction wins; operator overrides, which may *decrease*, go through\n // `prioritize()`), lane is last-writer-wins (ACT-1103), and the work\n // mark never regresses (#1485) — `GREATEST` reads through a NULL on\n // either side, so a first mark lands and an omitted one leaves the\n // stored value alone. The WHERE keeps the no-op case free of dead\n // tuples: a row is rewritten only when one of the three would change.\n await client.query(\n `\n UPDATE ${this._fqs} t\n SET priority = GREATEST(t.priority, COALESCE((s->>'priority')::int, 0)),\n lane = COALESCE(s->>'lane', 'default'),\n correlated_at = GREATEST(t.correlated_at, (s->>'correlated_at')::int)\n FROM jsonb_array_elements($1::jsonb) AS s\n WHERE t.stream = s->>'stream'\n AND (COALESCE((s->>'priority')::int, 0) > t.priority\n OR t.lane <> COALESCE(s->>'lane', 'default')\n OR (s->>'correlated_at' IS NOT NULL\n AND (t.correlated_at IS NULL\n OR t.correlated_at < (s->>'correlated_at')::int)))\n `,\n [JSON.stringify(streams)]\n );\n }\n // The correlate checkpoint is written by its own producer, in the call\n // correlate already makes (#1484). GREATEST keeps it monotonic.\n if (correlated_at !== undefined)\n await client.query(\n `UPDATE ${this._fqc} SET at = GREATEST(at, $1::int) WHERE id = 0`,\n [correlated_at]\n );\n // Watermark and checkpoint in one round trip — correlate needs both.\n const { rows } = await client.query<{\n max: number | null;\n correlated_at: string | null;\n }>(\n `SELECT (SELECT COALESCE(MAX(at), -1) FROM ${this._fqs}) AS max,\n (SELECT at FROM ${this._fqc} WHERE id = 0) AS correlated_at`\n );\n await client.query(\"COMMIT\");\n return {\n subscribed,\n watermark: rows[0]?.max ?? -1,\n correlated_at: Number(rows[0]?.correlated_at ?? -1),\n };\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"subscribe\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Acknowledge and release leases after processing, updating stream positions.\n *\n * @param leases - Leases to acknowledge, including last processed watermark and lease holder.\n * @returns Acked leases.\n */\n async ack(leases: Lease[]): Promise<Lease[]> {\n const client = await this._client(\"ack\");\n try {\n await client.query(\"BEGIN\");\n // One statement finalizes the whole batch, so acks and defer\n // schedules land all-or-nothing per the Store.ack contract. Every\n // entry advances the watermark to its `at` (the last event handled\n // this cycle); an entry without `due` also clears retry + schedule,\n // while an entry with `due` additionally sets the schedule and the\n // entry's own `retry` — advance and defer are independent legs\n // (#1278), so a partial-progress defer keeps the handled prefix.\n // An explicit defer passes retry -1 (not a failure); a backoff retry\n // passes the climbing counter so the budget keeps accruing across\n // windows (#1262). Deferred rows are filtered out of the returned acks.\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n by: string;\n retry: number;\n lagging: boolean;\n lane: string;\n due: string | null;\n }>(\n `\n WITH input AS (\n SELECT * FROM jsonb_to_recordset($1::jsonb)\n AS x(stream text, by text, at int, lagging boolean, due bigint, retry int)\n )\n UPDATE ${this._fqs} AS s\n SET\n at = i.at,\n retry = CASE WHEN i.due IS NULL THEN -1 ELSE i.retry END,\n leased_by = NULL,\n leased_until = NULL,\n deferred_at = CASE WHEN i.due IS NULL THEN NULL\n ELSE to_timestamp(i.due / 1000.0) END\n FROM input i\n WHERE s.stream = i.stream AND s.leased_by = i.by\n RETURNING s.stream, s.source, s.at, i.by, s.retry, i.lagging, s.lane, i.due\n `,\n [JSON.stringify(leases)]\n );\n await client.query(\"COMMIT\");\n\n return rows\n .filter((row) => row.due === null)\n .map((row) => ({\n stream: row.stream,\n source: row.source ?? undefined,\n at: row.at,\n by: row.by,\n retry: row.retry,\n lagging: row.lagging,\n lane: row.lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"ack\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Block a stream for processing after failing to process and reaching max retries with blocking enabled.\n * @param leases - Leases to block, including lease holder and last error message.\n * @returns Blocked leases.\n */\n async block(leases: BlockedLease[]): Promise<BlockedLease[]> {\n const client = await this._client(\"block\");\n try {\n await client.query(\"BEGIN\");\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n by: string;\n retry: number;\n lagging: boolean;\n error: string;\n lane: string;\n }>(\n `\n WITH input AS (\n SELECT * FROM jsonb_to_recordset($1::jsonb)\n AS x(stream text, by text, error text, lagging boolean)\n )\n UPDATE ${this._fqs} AS s\n SET blocked = true, error = i.error, deferred_at = NULL\n FROM input i\n WHERE s.stream = i.stream AND s.leased_by = i.by AND s.blocked = false\n RETURNING s.stream, s.source, s.at, i.by, s.retry, s.error, i.lagging, s.lane\n `,\n [JSON.stringify(leases)]\n );\n await client.query(\"COMMIT\");\n\n return rows.map((row) => ({\n stream: row.stream,\n source: row.source ?? undefined,\n at: row.at,\n by: row.by,\n retry: row.retry,\n lagging: row.lagging,\n error: row.error,\n lane: row.lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"block\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Hold the matched streams out of {@link claim} until `deferred_at`\n * (ms since epoch) — see {@link Store.defer}. Persists `deferred_at`\n * (as a `timestamptz`) so the skip is honored by every competing\n * worker; `claim` filters on `deferred_at <= NOW()`. Accepts an\n * explicit list of names or a {@link StreamFilter}, mirroring\n * {@link reset}/{@link prioritize}. Cleared by ack/block/reset/unblock.\n *\n * @returns Count of streams whose `deferred_at` was set.\n */\n async defer(\n input: string[] | StreamFilter,\n deferred_at: number\n ): Promise<number> {\n // Reset retry too: a defer is a deliberate \"come back later,\" not a\n // failure, so the redelivery after the due-time is a fresh attempt.\n const set_clause = `SET deferred_at = to_timestamp($1 / 1000.0), retry = -1`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($2)`,\n [deferred_at, input]\n );\n return rowCount ?? 0;\n }\n const { clause, values } = this._filter_clause(input, 2);\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,\n [deferred_at, ...values]\n );\n return rowCount ?? 0;\n }\n\n /**\n * Reset watermarks for the given streams to -1, clearing retry, blocked,\n * error, and lease state so they can be replayed from the beginning.\n * @param streams - Stream names to reset.\n * @returns Count of streams that were actually reset.\n */\n /**\n * Translate a {@link StreamFilter} to a `WHERE` clause fragment and\n * the corresponding parameter values. The fragment never starts with\n * `WHERE` — callers compose it with any other predicates they need.\n * Returns an always-true clause (`true`) when the filter is empty.\n */\n private _filter_clause(\n filter: StreamFilter,\n start: number\n ): { clause: string; values: unknown[] } {\n const conditions: string[] = [];\n const values: unknown[] = [];\n if (filter.stream !== undefined) {\n values.push(filter.stream);\n conditions.push(\n filter.stream_exact\n ? `stream = $${start + values.length - 1}`\n : `stream ~ $${start + values.length - 1}`\n );\n }\n if (filter.source !== undefined) {\n conditions.push(`source IS NOT NULL`);\n values.push(filter.source);\n conditions.push(\n filter.source_exact\n ? `source = $${start + values.length - 1}`\n : `source ~ $${start + values.length - 1}`\n );\n }\n if (filter.blocked !== undefined) {\n values.push(filter.blocked);\n conditions.push(`blocked = $${start + values.length - 1}`);\n }\n if (filter.lane !== undefined) {\n values.push(filter.lane);\n conditions.push(`lane = $${start + values.length - 1}`);\n }\n return {\n clause: conditions.length ? conditions.join(\" AND \") : \"TRUE\",\n values,\n };\n }\n\n async reset(input: string[] | StreamFilter): Promise<number> {\n const set_clause = `SET at = -1, retry = -1, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL, deferred_at = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($1)`,\n [input]\n );\n return rowCount ?? 0;\n }\n const { clause, values } = this._filter_clause(input, 1);\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,\n values\n );\n return rowCount ?? 0;\n }\n\n /**\n * Clear blocked flag (and retry / error / lease state) on streams\n * without touching the `at` watermark. `blocked = true` is always\n * applied, so the return count reflects only streams that were\n * actually flipped — already-unblocked rows, unknown streams, and\n * filter matches that aren't blocked are silently skipped.\n *\n * `retry = -1` matches the InMemoryStore convention: claim() bumps\n * retry on every acquisition, so storing -1 means the first claim\n * after unblock returns retry=0 (\"first attempt\"). Storing 0 would\n * mis-report the post-recovery attempt as a continuation of the\n * failed sequence. See {@link Store.unblock}.\n *\n * @returns Count of streams that were actually flipped (were blocked).\n */\n async unblock(input: string[] | StreamFilter): Promise<number> {\n const set_clause = `SET retry = -1, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL, deferred_at = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause}\n WHERE stream = ANY($1) AND blocked = true`,\n [input]\n );\n return rowCount ?? 0;\n }\n // Filter form: force `blocked = true` regardless of what the\n // caller passed — there is no use case for \"unblock unblocked\n // streams.\" A no-op overlay is the right shape here.\n const { clause, values } = this._filter_clause(\n { ...input, blocked: true },\n 1\n );\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,\n values\n );\n return rowCount ?? 0;\n }\n\n /**\n * Bulk-update priority of streams matching `filter` (ACT-102).\n *\n * Filter semantics mirror {@link query_streams}: regex on `stream` /\n * `source` by default, exact match with the `_exact` flags,\n * `blocked` restricts to blocked or unblocked rows. Empty filter\n * (`{}`) updates every registered stream.\n *\n * Unlike {@link subscribe} (which keeps `max()` of registered\n * priorities), this sets the priority outright — operator override\n * for the build-time scheduling policy.\n *\n * @returns Count of streams whose priority changed.\n */\n async prioritize(filter: StreamFilter, priority: number): Promise<number> {\n const { clause, values } = this._filter_clause(filter, 2);\n const sql = `UPDATE ${this._fqs} SET priority = $1\n WHERE priority <> $1 AND ${clause}`;\n const { rowCount } = await this._pool.query(sql, [priority, ...values]);\n return rowCount ?? 0;\n }\n\n /**\n * Streams subscription positions to a callback, ordered by stream name,\n * along with the highest event id in the store.\n *\n * Filters (`stream`, `source`, `blocked`, `after`, `limit`) are applied\n * server-side. `stream`/`source` are regex by default (`~`), or exact\n * with `*_exact: true` — same convention as {@link Store.query}.\n *\n * @returns `maxEventId` and the `count` of positions emitted.\n */\n async query_streams(\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ): Promise<QueryStreamsResult> {\n const limit = query?.limit ?? 100;\n const conditions: string[] = [];\n const values: unknown[] = [];\n\n if (query?.stream !== undefined) {\n values.push(query.stream);\n conditions.push(\n query.stream_exact\n ? `stream = $${values.length}`\n : `stream ~ $${values.length}`\n );\n }\n if (query?.source !== undefined) {\n conditions.push(`source IS NOT NULL`);\n values.push(query.source);\n conditions.push(\n query.source_exact\n ? `source = $${values.length}`\n : `source ~ $${values.length}`\n );\n }\n if (query?.source_matches?.length) {\n // Reverse-match narrowing: the inverse of the `source` filter.\n // The stored `source` is treated as the regex pattern, and a row\n // qualifies when any supplied candidate name matches it (`n ~ source`).\n // A NULL/empty source has no source constraint — it consumes from\n // every stream, so it always qualifies. Composes (AND) with others.\n values.push(query.source_matches);\n conditions.push(\n `(source IS NULL OR source = '' OR EXISTS (\n SELECT 1 FROM unnest($${values.length}::text[]) AS n WHERE n ~ source\n ))`\n );\n }\n if (query?.blocked !== undefined) {\n values.push(query.blocked);\n conditions.push(`blocked = $${values.length}`);\n }\n if (query?.lane !== undefined) {\n values.push(query.lane);\n conditions.push(`lane = $${values.length}`);\n }\n if (query?.after !== undefined) {\n values.push(query.after);\n conditions.push(`stream > $${values.length}`);\n }\n let sql = `SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane, deferred_at, correlated_at FROM ${this._fqs}`;\n if (conditions.length) sql += \" WHERE \" + conditions.join(\" AND \");\n values.push(limit);\n sql += ` ORDER BY stream LIMIT $${values.length}`;\n\n const client = await this._client(\"query_streams\");\n try {\n const [streamsResult, maxResult] = await Promise.all([\n client.query<{\n stream: string;\n source: string | null;\n at: number;\n retry: number;\n blocked: boolean;\n error: string | null;\n leased_by: string | null;\n leased_until: Date | null;\n priority: number;\n lane: string;\n deferred_at: Date | null;\n correlated_at: number | null;\n }>(sql, values),\n client.query<{ m: number | null }>(\n `SELECT COALESCE(MAX(id), -1) AS m FROM ${this._fqt}`\n ),\n ]);\n\n let count = 0;\n for (const row of streamsResult.rows) {\n callback({\n stream: row.stream,\n source: row.source ?? undefined,\n at: row.at,\n retry: row.retry,\n blocked: row.blocked,\n error: row.error ?? \"\",\n priority: row.priority,\n leased_by: row.leased_by ?? undefined,\n leased_until: row.leased_until ?? undefined,\n lane: row.lane,\n // Persisted as timestamptz; surface as ms since epoch (#1221) so\n // the cold-start re-seed can re-arm the drain at the due-time.\n deferred_at: row.deferred_at ? row.deferred_at.getTime() : undefined,\n // NULL means \"no mark yet\" — unknown, not \"no work\" (#1485).\n correlated_at: row.correlated_at ?? undefined,\n });\n count++;\n }\n\n return { maxEventId: Number(maxResult.rows[0].m), count };\n } finally {\n client.release();\n }\n }\n\n /**\n * Per-stream aggregated stats — see {@link Store.query_stats}.\n *\n * Two code paths chosen by the requested stats:\n *\n * - **Heads-only path** (no `count`, no `names`): one or two\n * `SELECT DISTINCT ON (stream) ... ORDER BY stream, version DESC|ASC`\n * queries, executed in parallel when `tail: true`. The\n * `(stream, version)` unique index gives index-only access — K rows\n * touched per query (K = matched streams), not N (events).\n * Ordering by `version` (not `id`) is equivalent within a stream\n * (versions are monotonic per stream and events are committed\n * sequentially) and is the column actually indexed.\n *\n * - **Full-scan path** (`count` or `names` set): one CTE materializes\n * the filtered events, then `GROUP BY stream, name` →\n * `jsonb_object_agg(name, n)` for the `names` map plus per-stream\n * `COUNT(*)` for `count`. Heads (and `tails` when requested) come\n * from `DISTINCT ON` over the same CTE — they ride free on the\n * already-paid scan.\n *\n * The stream universe is derived from the events table: filter form\n * matches event-bearing streams (not subscription rows). When the\n * filter sets `source` or `blocked`, the events table is joined\n * against the streams subscription table since those concepts only\n * exist for subscribed streams.\n */\n async query_stats<E extends Schemas>(\n input: string[] | Pick<StreamFilter, \"stream\" | \"stream_exact\">,\n options?: QueryStatsOptions<E>\n ): Promise<Map<string, StreamStats<E>>> {\n const exclude = options?.exclude ?? [];\n const want_tail = options?.tail ?? false;\n const want_count = options?.count ?? false;\n const want_names = options?.names ?? false;\n const before = options?.before;\n const after = options?.after;\n const stats_limit = options?.limit;\n const full_scan = want_count || want_names;\n\n // Empty array short-circuit — saves a round trip on a no-op.\n if (Array.isArray(input) && input.length === 0) {\n return new Map<string, StreamStats<E>>();\n }\n\n // Build WHERE clause + parameter list. Subscription-level filters\n // (source, blocked) are intentionally not accepted — events live in\n // the events table; subscription state in the streams table. For\n // \"stats for blocked subscriptions\" callers compose with\n // query_streams. So no JOIN here.\n const where: string[] = [];\n const params: unknown[] = [];\n\n if (Array.isArray(input)) {\n params.push(input);\n where.push(`e.stream = ANY($${params.length})`);\n } else if (input.stream !== undefined) {\n params.push(input.stream);\n where.push(\n input.stream_exact\n ? `e.stream = $${params.length}`\n : `e.stream ~ $${params.length}`\n );\n }\n if (exclude.length) {\n params.push(exclude);\n where.push(`e.name <> ALL($${params.length})`);\n }\n if (before !== undefined) {\n params.push(before);\n where.push(`e.id < $${params.length}`);\n }\n if (after !== undefined) {\n // Keyset pagination cursor — exclusive on stream name. Results are\n // ordered by stream ascending so callers chain\n // `[...map.keys()].at(-1)` as the next cursor.\n params.push(after);\n where.push(`e.stream > $${params.length}`);\n }\n\n const from_clause = `${this._fqt} e`;\n // Always emit a WHERE clause — `WHERE TRUE` short-circuits the\n // empty-filter case without a conditional branch on the generation\n // side. PG optimizes the trivial predicate out.\n const where_clause = `WHERE ${where.length ? where.join(\" AND \") : \"TRUE\"}`;\n\n return full_scan\n ? this._query_stats_full_scan<E>(\n from_clause,\n where_clause,\n params,\n want_tail,\n want_count,\n want_names,\n stats_limit\n )\n : this._query_stats_heads_only<E>(\n from_clause,\n where_clause,\n params,\n want_tail,\n stats_limit\n );\n }\n\n /**\n * Cheap path: index-only DISTINCT ON for the head per stream, plus an\n * optional second query (in parallel) for the tail. K rows touched\n * per query, not N events.\n */\n private async _query_stats_heads_only<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n params: unknown[],\n want_tail: boolean,\n stats_limit?: number\n ): Promise<Map<string, StreamStats<E>>> {\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;\n // `DISTINCT ON (e.stream) ... ORDER BY e.stream` already yields one row\n // per stream in stream-name order, so a trailing LIMIT caps the number\n // of streams returned. The head and tail queries share the same\n // ordering, so the same LIMIT selects the identical first-N streams.\n const limit_clause =\n stats_limit !== undefined ? ` LIMIT ${stats_limit}` : \"\";\n const head_sql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version DESC${limit_clause}`;\n const tail_sql = want_tail\n ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version ASC${limit_clause}`\n : null;\n\n const [headRes, tailRes] = await Promise.all([\n this._pool.query<Committed<E, keyof E>>(head_sql, params),\n tail_sql\n ? this._pool.query<Committed<E, keyof E>>(tail_sql, params)\n : Promise.resolve(null),\n ]);\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of headRes.rows) {\n out.set(row.stream, { head: row });\n }\n if (tailRes) {\n for (const row of tailRes.rows) {\n // Head and tail share the same WHERE, so any stream returning a\n // tail must also have returned a head — no null check needed.\n (\n out.get(row.stream) as {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n }\n ).tail = row;\n }\n }\n return out;\n }\n\n /**\n * Full-scan path: one CTE-based query computes the per-stream\n * `COUNT(*)` and `jsonb_object_agg(name, n)` map alongside the head\n * (and tail when requested). All extras share the single events scan.\n */\n private async _query_stats_full_scan<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n params: unknown[],\n want_tail: boolean,\n want_count: boolean,\n want_names: boolean,\n stats_limit?: number\n ): Promise<Map<string, StreamStats<E>>> {\n const tail_cte = want_tail\n ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)`\n : \"\";\n const tail_join = want_tail\n ? `LEFT JOIN tails t ON t.stream = h.stream`\n : \"\";\n const tail_cols = want_tail\n ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,\n t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta`\n : \"\";\n\n const sql = `\n WITH ef AS (\n SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta\n FROM ${from_clause}\n ${where_clause}\n ),\n agg AS (\n SELECT stream,\n SUM(n)::int AS cnt,\n jsonb_object_agg(name, n) AS names\n FROM (\n SELECT stream, name, COUNT(*)::int AS n\n FROM ef\n GROUP BY stream, name\n ) t\n GROUP BY stream\n ),\n heads AS (\n SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version DESC\n )\n ${tail_cte}\n SELECT\n h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,\n a.cnt AS agg_count,\n a.names AS agg_names\n ${tail_cols}\n FROM heads h\n LEFT JOIN agg a ON a.stream = h.stream\n ${tail_join}\n ORDER BY h.stream\n ${stats_limit !== undefined ? `LIMIT ${stats_limit}` : \"\"}\n `;\n\n const res = await this._pool.query<\n Committed<E, keyof E> & {\n agg_count: number;\n agg_names: Record<string, number> | null;\n t_id?: number;\n t_stream?: string;\n t_version?: number;\n t_name?: string;\n t_data?: object;\n t_created?: Date;\n t_meta?: object;\n }\n >(sql, params);\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of res.rows) {\n const stats: {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n count?: number;\n names?: Record<string, number>;\n } = {\n head: {\n id: row.id,\n stream: row.stream,\n version: row.version,\n name: row.name,\n data: row.data,\n created: row.created,\n meta: row.meta,\n } as Committed<E, keyof E>,\n };\n if (want_tail && row.t_id !== undefined && row.t_id !== null) {\n stats.tail = {\n id: row.t_id,\n stream: row.t_stream,\n version: row.t_version,\n name: row.t_name,\n data: row.t_data,\n created: row.t_created,\n meta: row.t_meta,\n } as unknown as Committed<E, keyof E>;\n }\n if (want_count) stats.count = row.agg_count;\n // `agg_names` is non-null when this row exists: heads and agg are\n // both built from the same `ef` CTE, so any stream in heads has\n // at least one matching event and `jsonb_object_agg` returns an\n // object (never null) for that group.\n if (want_names) stats.names = row.agg_names as Record<string, number>;\n out.set(row.stream, stats as StreamStats<E>);\n }\n return out;\n }\n\n /**\n * Implementation of the optional `Store.notify` hook. Bound onto\n * `this.notify` in the constructor when `config.notify === true`,\n * left detached otherwise — see {@link Config.notify}.\n *\n * Checks out a dedicated long-lived client from the pool, runs\n * `LISTEN act_commit_<schema>_<table>`, and parses each incoming\n * notification payload. The handler is invoked exactly once per\n * **remote** commit — payloads originating from this same store\n * instance (matched by the per-instance `_by` UUID) are silently\n * skipped, giving callers a clean cross-process semantic.\n *\n * Multiple subscriptions on the same store instance are not supported —\n * this method releases any prior LISTEN client before opening a new one.\n * The returned disposer cleanly UNLISTENs and releases the dedicated\n * client; pool disposal also tears the subscription down as a safety\n * net.\n *\n * The subscription is **self-healing** (#1189): the dedicated client\n * has an `error` listener that, on a connection blip (backend restart,\n * failover, network drop), tears the dead client down and re-LISTENs\n * on a fresh one with capped exponential backoff — degrading to the\n * poll path in between. A pending reconnect is cancelled by disposal,\n * so no reconnect fires after teardown.\n *\n * @param handler Called for each cross-process commit notification.\n * @returns Disposer that releases the LISTEN client.\n */\n private async _subscribe_notifications(\n handler: (notification: StoreNotification) => void\n ): Promise<NotifyDisposer> {\n // Close any prior subscription so callers don't silently double-listen.\n await this._teardown_listen();\n\n // Remember the caller's handler so the self-healing reconnect path\n // (#1189) can re-establish LISTEN on a fresh client after a\n // connection blip without the caller re-subscribing.\n this._notify_handler = handler;\n try {\n await this._open_listen(handler);\n } catch (err) {\n // Initial LISTEN failed — leave no half-set state behind so the\n // orchestrator's wireNotify sees a clean rejection.\n this._notify_handler = undefined;\n throw err;\n }\n\n return async () => {\n // No-op when this disposer is stale (a later notify() call already\n // tore the subscription down and replaced the handler).\n if (this._notify_handler !== handler) return;\n await this._teardown_listen();\n };\n }\n\n /**\n * Check out a dedicated client, attach the notification + error\n * listeners, and run `LISTEN`. Shared by the initial subscription and\n * every reconnect (#1189). On any failure before `LISTEN` succeeds the\n * client is detached and destroyed so nothing leaks — the caller\n * decides whether to propagate (initial subscribe) or reschedule\n * (reconnect).\n */\n private async _open_listen(\n handler: (notification: StoreNotification) => void\n ): Promise<void> {\n const client = await this._client(\"notify\");\n const on_notification = (msg: pg.Notification) => {\n // Channel filter: this client only `LISTEN`s on `this._channel`,\n // but pg-pool can in theory deliver buffered notifications when a\n // connection is reused — guard rather than trust.\n if (msg.channel !== this._channel) return;\n if (!msg.payload) return;\n let parsed: {\n stream?: unknown;\n events?: unknown;\n by?: unknown;\n };\n try {\n parsed = JSON.parse(msg.payload);\n } catch (err) {\n // A malformed payload is a bug somewhere upstream — log and skip\n // instead of tearing down the listener.\n logger.error(\n { err, payload: msg.payload },\n \"act_commit: malformed payload, skipping\"\n );\n return;\n }\n // Self-filter: skip notifications that originated from this same\n // store instance. This is what gives `notified` its cross-process\n // semantic — local commits already arm the drain via `do()`.\n if (parsed.by === this._by) return;\n if (typeof parsed.stream !== \"string\" || !Array.isArray(parsed.events)) {\n logger.error(\n { payload: msg.payload },\n \"act_commit: payload missing required fields, skipping\"\n );\n return;\n }\n const events: Array<{ id: number; name: string }> = [];\n for (const raw of parsed.events) {\n if (\n raw &&\n typeof raw === \"object\" &&\n typeof (raw as { id?: unknown }).id === \"number\" &&\n typeof (raw as { name?: unknown }).name === \"string\"\n ) {\n events.push({\n id: (raw as { id: number }).id,\n name: (raw as { name: string }).name,\n });\n }\n }\n if (events.length === 0) return;\n // Adapter-level robustness: a throwing handler must not tear\n // down the dedicated LISTEN client. The orchestrator wraps its\n // own `notified` emit + drain wakeup separately\n // (`Act._wire_notify`) — defense in depth, with each layer\n // protecting its own resources. Direct callers of\n // `store.notify(handler)` (tests, custom integrations) inherit\n // the adapter wrap.\n try {\n handler({ stream: parsed.stream, events });\n } catch (err) {\n logger.error(err, \"act_commit: handler threw, listener preserved\");\n }\n };\n // The dedicated LISTEN client loses node-postgres's idle-error guard\n // on checkout, so an unhandled `error` (backend restart, failover,\n // network drop) would crash the process (#1189). Handle it: log,\n // tear the dead client down, and schedule a re-LISTEN with capped\n // backoff. Between attempts the store degrades to the poll path.\n const on_error = (err: Error) => {\n logger.error(err, \"act_commit: LISTEN client errored, reconnecting\");\n this._reconnect();\n };\n client.on(\"notification\", on_notification);\n client.on(\"error\", on_error);\n try {\n await client.query(`LISTEN ${this._channel}`);\n } catch (err) {\n client.removeListener(\"notification\", on_notification);\n client.removeListener(\"error\", on_error);\n client.release(true);\n throw err;\n }\n this._listen_client = client;\n this._listen_handler = on_notification;\n this._listen_error_handler = on_error;\n // A healthy LISTEN resets the backoff so the next blip starts fresh.\n this._reconnect_attempts = 0;\n }\n\n /**\n * Self-heal the LISTEN subscription after the dedicated client emitted\n * `error` (#1189). Detaches and destroys the dead client, then\n * reconnects on a fresh one with capped exponential backoff. Bails\n * immediately if the subscription was disposed while a reconnect was\n * pending (`_notify_handler` cleared by `_teardown_listen`), so no\n * reconnect ever fires after teardown.\n */\n private _reconnect(): void {\n const handler = this._notify_handler;\n // Disposed (or torn down by a re-subscribe) while the error fired —\n // nothing to reconnect.\n if (!handler) return;\n // Detach and destroy the dead client. Its listeners are gone once\n // `_teardown_listen` runs, but the error already fired, so just drop\n // it — do not run UNLISTEN on a broken connection.\n if (this._listen_client) {\n const dead = this._listen_client;\n dead.removeListener(\"notification\", this._listen_handler!);\n dead.removeListener(\"error\", this._listen_error_handler!);\n // A node-postgres socket routinely emits `error` more than once on\n // teardown (in-flight LISTEN rejection, then the ECONNRESET/end that\n // follows). `release(true)` destroys the connection but does not\n // synchronously silence the socket, so the client must never be\n // listener-less: an unhandled second `error` re-raises as an uncaught\n // exception — the exact process crash #1189 fixed (#1231). Attach a\n // swallow listener that lives until the destroyed client is GC'd.\n dead.on(\"error\", swallow_error);\n this._listen_handler = undefined;\n this._listen_error_handler = undefined;\n this._listen_client = undefined;\n dead.release(true);\n }\n const delay = Math.min(\n NOTIFY_RECONNECT_MAX_MS,\n NOTIFY_RECONNECT_BASE_MS * 2 ** this._reconnect_attempts\n );\n this._reconnect_attempts++;\n // A second error (or the recursive `.catch` reconnect) must not leave two\n // live timers racing to re-LISTEN — cancel any pending one before we\n // reassign. `_teardown_listen` clears it on disposal.\n if (this._reconnect_timer) clearTimeout(this._reconnect_timer);\n this._reconnect_timer = setTimeout(() => {\n this._reconnect_timer = undefined;\n // Re-check: disposal may have won the race after the timer fired.\n const current = this._notify_handler;\n if (!current) return;\n this._open_listen(current).catch((err) => {\n logger.error(err, \"act_commit: LISTEN reconnect failed, retrying\");\n this._reconnect();\n });\n }, delay);\n // Don't keep the event loop alive purely for a reconnect attempt —\n // a process that has nothing else to do should still be able to exit.\n this._reconnect_timer.unref?.();\n }\n\n /**\n * Atomically truncates streams and seeds each with a snapshot or tombstone.\n * Windowed targets (`before` set) prune the prefix below the closest safe\n * `__snapshot__` instead — no seed, subscriptions untouched, no-op when no\n * snapshot qualifies.\n * @param targets - Streams to truncate with optional snapshot state and meta,\n * or a `before`/`max_id` boundary for a windowed prefix delete.\n * @returns Map keyed by stream name, each entry with `deleted` count and `committed` event.\n */\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Schema;\n meta?: EventMeta;\n before?: Date;\n max_id?: number;\n }>\n ): Promise<\n Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >\n > {\n if (!targets.length) return new Map();\n const full = targets.filter((t) => t.before === undefined);\n const windowed = targets.filter((t) => t.before !== undefined);\n const client = await this._client(\"truncate\");\n try {\n await client.query(\"BEGIN\");\n // Seeds (snapshots/tombstones) produce watermark-relevant ids, so\n // truncate takes the same visibility lock as commit — see the\n // commit path for the id-order-vs-visibility-order rationale.\n await client.query(\"SELECT pg_advisory_xact_lock(hashtext($1))\", [\n this._fqt,\n ]);\n const result = new Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >();\n // A restart target carries a seed snapshot: the stream lives on, so\n // its subscription row must survive. Deleting it silently stops\n // reactions whose target is named after the stream — the documented\n // per-aggregate shape `.to(e => ({target: e.stream}))` (#1398).\n const retired = full\n .filter((t) => t.snapshot === undefined)\n .map((t) => t.stream);\n if (retired.length) {\n await client.query(`DELETE FROM ${this._fqs} WHERE stream = ANY($1)`, [\n retired,\n ]);\n }\n for (const { stream, snapshot, meta } of full) {\n const { rowCount } = await client.query(\n `DELETE FROM ${this._fqt} WHERE stream = $1`,\n [stream]\n );\n const name = snapshot !== undefined ? SNAP_EVENT : TOMBSTONE_EVENT;\n const { rows } = await client.query(\n `INSERT INTO ${this._fqt}(name, data, stream, version, created, meta)\n VALUES($1, $2, $3, 0, now(), $4) RETURNING *`,\n [\n name,\n snapshot ?? {},\n stream,\n meta ?? { correlation: \"\", causation: {} },\n ]\n );\n result.set(stream, {\n deleted: rowCount ?? 0,\n committed: rows[0] as Committed<Schemas, keyof Schemas>,\n });\n }\n for (const { stream, before, max_id } of windowed) {\n // Closest safe boundary: latest snapshot older than the cutoff and\n // at/below the consumer watermark cap. No qualifying snapshot →\n // no-op, stream absent from the result.\n const { rows } = await client.query(\n `SELECT id, stream, version, name, data, created, meta\n FROM ${this._fqt}\n WHERE stream = $1 AND name = $2 AND created < $3\n AND ($4::int IS NULL OR id <= $4)\n ORDER BY id DESC LIMIT 1`,\n [stream, SNAP_EVENT, before, max_id ?? null]\n );\n if (!rows.length) continue;\n const boundary = rows[0] as Committed<Schemas, keyof Schemas>;\n const { rowCount } = await client.query(\n `DELETE FROM ${this._fqt} WHERE stream = $1 AND id < $2`,\n [stream, boundary.id]\n );\n result.set(stream, {\n deleted: rowCount ?? 0,\n committed: boundary,\n before,\n });\n }\n await client.query(\"COMMIT\");\n return result;\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Atomically wipe-and-rebuild the store inside a single\n * `BEGIN`/`COMMIT` transaction.\n *\n * On any throw inside the driver the transaction rolls back and the\n * store ends byte-for-byte unchanged. `TRUNCATE ... RESTART\n * IDENTITY CASCADE` wipes events + resets the serial sequence to 1;\n * the streams table is cleared in the same statement via\n * `CASCADE`-like `DELETE`. Events are inserted one at a time with\n * explicit columns (skipping `id`) so the serial assigns dense ids\n * from 1. `created` is preserved verbatim from the source.\n */\n async restore(\n driver: (\n callback: (event: Committed<Schemas, keyof Schemas>) => Promise<number>\n ) => Promise<void>\n ): Promise<void> {\n const client = await this._client(\"restore\");\n try {\n await client.query(\"BEGIN\");\n // RESTART IDENTITY resets the id sequence; CASCADE handles any\n // future FK refs (none today, but cheap insurance).\n await client.query(\n `TRUNCATE TABLE ${this._fqt} RESTART IDENTITY CASCADE`\n );\n await client.query(`TRUNCATE TABLE ${this._fqs}`);\n await driver(async (event) => {\n // Restore mirrors commit: encrypt the pii payload when\n // encryption is configured. The source iterator yields\n // plaintext events (restore is the rebuild path — the driver\n // already presents data in the framework's native shape),\n // so this is symmetric with the commit-path call above —\n // including the JSON.stringify wrapper that turns the bare\n // base64 string into a jsonb-acceptable JSON string literal.\n const pii_for_write =\n this._resolve_pii_key && event.pii != null\n ? JSON.stringify(await encrypt(event.pii, this._resolve_pii_key))\n : (event.pii ?? null);\n const { rows } = await client.query<{ id: number }>(\n `INSERT INTO ${this._fqt}(name, data, pii, stream, version, created, meta)\n VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING id`,\n [\n event.name,\n event.data,\n pii_for_write,\n event.stream,\n event.version,\n event.created,\n event.meta,\n ]\n );\n return rows[0]!.id;\n });\n await client.query(\"COMMIT\");\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Wipe the sensitive-data payload for every event on the stream — the\n * physical-erasure side of the sensitive-data epic (#566). Sets\n * `events.pii` to `NULL` for the stream's events; `events.data` and\n * the rest of the row are never touched.\n *\n * Row-level locks (no table lock), bounded by events-per-stream.\n * Idempotent — a second call on an already-wiped stream returns `0`.\n *\n * Disk reclamation is autovacuum-driven; for strict-deletion\n * jurisdictions the production checklist documents `VACUUM FULL` as\n * the operator step.\n *\n * @param stream Target stream\n * @returns Count of events whose `pii` was set to `NULL`\n */\n async forget_pii(stream: string): Promise<number> {\n const r = await this._pool.query(\n `UPDATE ${this._fqt} SET pii = NULL WHERE stream = $1 AND pii IS NOT NULL`,\n [stream]\n );\n return r.rowCount ?? 0;\n }\n}\n"],"mappings":"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","names":["lagging","lane","rowCount"]}
|
|
1
|
+
{"version":3,"sources":["../src/postgres-store.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type {\n BlockedLease,\n Committed,\n EventMeta,\n Lease,\n Logger,\n Message,\n NotifyDisposer,\n Query,\n QueryStatsOptions,\n QueryStreams,\n QueryStreamsResult,\n Schema,\n Schemas,\n Store,\n StoreNotification,\n StreamFilter,\n StreamPosition,\n StreamStats,\n SubscribeInput,\n} from \"@rotorsoft/act\";\nimport {\n ConcurrencyError,\n dateReviver,\n log,\n SNAP_EVENT,\n StoreError,\n TOMBSTONE_EVENT,\n ValidationError,\n} from \"@rotorsoft/act\";\nimport {\n decrypt,\n type Encryption,\n encrypt,\n makeKeyResolver,\n} from \"@rotorsoft/act-crypto\";\nimport pg from \"pg\";\n\nconst logger: Logger = log();\n\nconst { Pool, types } = pg;\n\n/**\n * Per-Pool type parser (#1198). Overrides ONLY the JSONB parser to revive\n * ISO-date strings in event payloads to `Date`, delegating every other\n * OID to pg's global default. Passed as the Pool's `types` option so the\n * Date coercion is scoped to this store's connections — it never mutates\n * the process-global `pg.types` registry, so a host app's other pg usage\n * (Drizzle projections, ad-hoc queries) reads jsonb with the stock\n * parser. This is the shape pg threads down to each pooled client.\n */\nconst JSONB_OID = types.builtins.JSONB;\nconst scopedTypes: { getTypeParser: typeof types.getTypeParser } = {\n getTypeParser: ((oid: number, format?: unknown) =>\n oid === JSONB_OID\n ? (val: string) => JSON.parse(val, dateReviver)\n : (types.getTypeParser as (oid: number, format?: unknown) => unknown)(\n oid,\n format\n )) as typeof types.getTypeParser,\n};\n\ntype Config = Readonly<{\n schema: string;\n table: string;\n /**\n * Opt in to cross-process commit notifications via `LISTEN`/`NOTIFY`.\n * Optional — defaults to `false` so existing callers keep their\n * current behavior. Setting it to `true` is the only behavior change\n * an upgrading deployment needs to make to enable cross-process\n * reaction wakeup.\n *\n * When `true`:\n * - `commit()` issues `pg_notify` after each successful insert.\n * - `notify(handler)` checks out a dedicated long-lived `LISTEN`\n * client from the pool and delivers cross-process notifications.\n *\n * When `false` (default):\n * - `commit()` skips the notify SQL entirely — zero per-write\n * overhead.\n * - The `notify` method is **not present on the instance**, so the\n * orchestrator's `if (store.notify)` auto-wire short-circuits and\n * no LISTEN client is allocated.\n *\n * Single-instance deployments should leave this off. Multi-process\n * deployments that need sub-poll reaction latency turn it on\n * **on every store instance** (writers and listeners both).\n */\n notify?: boolean;\n /**\n * Adapter-layer envelope encryption for the `events.pii` column.\n * Optional — when present, every non-null PII payload is encrypted\n * before INSERT and decrypted on every read; when absent, the\n * column is stored and read as plaintext (the framework's default\n * behavior).\n *\n * Cipher and wire format come from `@rotorsoft/act-crypto`:\n * AES-256-GCM with a versioned base64-framed envelope. The\n * jsonb column distinguishes encrypted from plaintext rows by\n * type — encrypted writes land as JSONB strings, plaintext writes\n * as JSONB objects — so existing data continues to read through\n * transparently after enabling encryption on new commits.\n *\n * `forget_pii` semantics are unchanged: the column is set to\n * `NULL` regardless of whether the prior value was plaintext or\n * ciphertext.\n *\n * Encryption at rest at the **storage** layer (`pgcrypto`, RDS\n * TDE, Cloud SQL TDE) composes orthogonally — defense in depth\n * without coordination. See `docs/docs/guides/pii-encryption-at-rest.md`\n * for the full decision matrix.\n */\n pii_encryption?: Encryption;\n}> &\n pg.PoolConfig;\n\nconst SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n// PostgreSQL SQLSTATE for `unique_violation` — surfaces when a concurrent\n// commit beats us between the version SELECT and the INSERT, hitting the\n// unique index on (stream, version). Stable across PG versions per the\n// SQL standard. See: https://www.postgresql.org/docs/current/errcodes-appendix.html\nconst PG_UNIQUE_VIOLATION = \"23505\";\n\n// The two SQLSTATEs Postgres raises for a NUL byte (`\\u0000`) in a committed\n// payload: `22P05` from the jsonb parser (`data` / `meta` / `pii`) and\n// `22021` from the UTF-8 decoder (`stream` / `name`, which are text).\n//\n// A NUL is legal JSON and a legal JS string, so it passes Zod and reaches the\n// store, where InMemory and SQLite (TEXT) round-trip it happily and only\n// Postgres refuses. The framework does not reject it up front — that would\n// mean walking every payload on the framework's hottest path to enforce one\n// adapter's storage limit on all three. What is worth doing is not letting\n// the driver's message (\"unsupported Unicode escape sequence\") be the whole\n// story, since it names neither the stream nor the event.\nconst PG_NUL_BYTE = new Set([\"22P05\", \"22021\"]);\n\n// Channel-name prefix for cross-process commit notifications. The\n// effective channel is namespaced per `(schema, table)` so two\n// PostgresStores pointed at distinct event tables in the same database\n// don't cross-talk. PG channel names are case-folded unless quoted; we\n// stick to lowercase identifiers so a future `LISTEN act_commit_*` from\n// any client (psql, scripts, alternative consumers) matches without\n// surprises.\nconst NOTIFY_CHANNEL_PREFIX = \"act_commit\";\n\n// PG caps NOTIFY payloads at 8000 bytes — `pg_notify` raises\n// \"payload string too long\" (SQLSTATE 54000) at or above the cap, and\n// inside the commit transaction that error would abort the whole INSERT\n// batch. `commit()` measures the serialized payload first and skips the\n// NOTIFY when it would not fit: listeners fall back to the poll path, so\n// delivery degrades to the next poll cycle but the commit never fails.\n// See: https://www.postgresql.org/docs/current/sql-notify.html\nconst NOTIFY_MAX_PAYLOAD_BYTES = 8000;\n\n// Capped exponential backoff for re-establishing the LISTEN subscription\n// after the dedicated client emits `error` (backend restart, failover,\n// network drop — #1189). Between attempts the store degrades to the poll\n// path, so callers never miss events — they just fall back to the next\n// drain cycle for cross-process wakeups until the LISTEN client is back.\nconst NOTIFY_RECONNECT_BASE_MS = 250;\nconst NOTIFY_RECONNECT_MAX_MS = 30_000;\n\n// Keeps a destroyed LISTEN client from re-raising a late socket `error` as an\n// uncaught exception. Attached to the dead client through `release(true)` so it\n// is never listener-less during the reconnect backoff window (#1231).\nconst swallow_error = (): void => {};\n\nfunction notify_channel(schema: string, table: string): string {\n return `${NOTIFY_CHANNEL_PREFIX}_${schema}_${table}`;\n}\nfunction assert_safe_identifier(value: string, label: string) {\n if (!SAFE_IDENTIFIER.test(value))\n throw new Error(`Unsafe SQL identifier for ${label}: \"${value}\"`);\n}\n\nconst DEFAULT_CONFIG: Config = {\n host: \"localhost\",\n port: 5432,\n database: \"postgres\",\n user: \"postgres\",\n password: \"postgres\",\n schema: \"public\",\n table: \"events\",\n notify: false,\n // Opinionated pool defaults (#1119). node-postgres ships `max: 10`\n // with no acquisition timeout and no statement timeout — a saturated\n // pool makes every caller hang indefinitely instead of failing with\n // a diagnosable error. Nearly every store method holds a client for\n // a multi-statement transaction, so multi-lane drains plus API\n // traffic can exhaust a 10-client pool quickly. All four values are\n // plain `pg.PoolConfig` fields — caller config overrides any of them\n // via the constructor spread.\n //\n // - `max: 20` — floor for the default lane's parallel handler budget\n // (streamLimit 10, each commit holds a client) plus API commits,\n // the optional LISTEN client, and headroom. Sizing rule in the\n // README: Σ per-lane streamLimit + API concurrency + notify + 2–4.\n // - `connectionTimeoutMillis: 10_000` — fail acquisition fast (the\n // pg default of 0 waits forever); surfaces as StoreError via\n // `_client()` so operators see *which* operation starved.\n // - `idleTimeoutMillis: 30_000` — keep idle clients warm across\n // drain cycles (cycleMs can exceed the pg default of 10s in\n // low-traffic deployments) without pinning connections for long.\n // - `statement_timeout: 60_000` — per-statement (not per-transaction)\n // ceiling; every statement the store issues (claim CTE, seed DDL,\n // truncate, restore's per-event inserts) legitimately completes\n // orders of magnitude faster, so 60s only fires on a wedged server\n // or a lost lock instead of holding the client hostage.\n max: 20,\n connectionTimeoutMillis: 10_000,\n idleTimeoutMillis: 30_000,\n statement_timeout: 60_000,\n};\n\n/**\n * Production-ready PostgreSQL event store implementation.\n *\n * PostgresStore provides persistent, scalable event storage using PostgreSQL.\n * It implements the full {@link Store} interface with production-grade features:\n *\n * **Features:**\n * - Persistent event storage with ACID guarantees\n * - Optimistic concurrency control via version numbers\n * - Distributed stream processing with leasing\n * - Snapshot support for performance optimization\n * - Connection pooling for scalability\n * - Automatic table and index creation\n *\n * **Database Schema:**\n * - Events table: Stores all committed events\n * - Streams table: Tracks stream metadata and leases\n * - Indexes on stream, version, and timestamps for fast queries\n *\n * @example Basic setup\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * import { PostgresStore } from \"@rotorsoft/act-pg\";\n *\n * store(new PostgresStore({\n * host: \"localhost\",\n * port: 5432,\n * database: \"myapp\",\n * user: \"postgres\",\n * password: \"secret\"\n * }));\n *\n * const app = act()\n * .withState(Counter)\n * .build();\n * ```\n *\n * @example With custom schema and table\n * ```typescript\n * import { PostgresStore } from \"@rotorsoft/act-pg\";\n *\n * const pgStore = new PostgresStore({\n * host: process.env.DB_HOST || \"localhost\",\n * port: parseInt(process.env.DB_PORT || \"5432\"),\n * database: process.env.DB_NAME || \"myapp\",\n * user: process.env.DB_USER || \"postgres\",\n * password: process.env.DB_PASSWORD,\n * schema: \"events\", // Custom schema\n * table: \"act_events\" // Custom table name\n * });\n *\n * // Initialize tables\n * await pgStore.seed();\n * ```\n *\n * @example Connection pooling configuration\n * ```typescript\n * // PostgresStore uses node-postgres (pg) connection pooling with\n * // opinionated defaults: max 20, connectionTimeoutMillis 10s,\n * // idleTimeoutMillis 30s, statement_timeout 60s. Any pg.PoolConfig\n * // field passed to the constructor overrides the default.\n *\n * const pgStore = new PostgresStore({\n * host: \"db.example.com\",\n * port: 5432,\n * database: \"production\",\n * user: \"app_user\",\n * password: process.env.DB_PASSWORD,\n * max: 40 // lanes × streamLimit + API concurrency + headroom\n * });\n * ```\n *\n * @example Multi-tenant setup\n * ```typescript\n * // Use separate schemas per tenant\n * const tenants = [\"tenant1\", \"tenant2\", \"tenant3\"];\n *\n * for (const tenant of tenants) {\n * const tenantStore = new PostgresStore({\n * host: \"localhost\",\n * database: \"multitenant\",\n * schema: tenant, // Each tenant gets own schema\n * table: \"events\"\n * });\n * await tenantStore.seed();\n * }\n * ```\n *\n * @example Querying PostgreSQL directly\n * ```typescript\n * // For advanced queries, you can access pg client\n * const pgStore = new PostgresStore(config);\n * await pgStore.seed();\n *\n * // Use the store's query method for standard queries\n * await pgStore.query(\n * (event) => console.log(event),\n * { stream: \"user-123\", limit: 100 }\n * );\n * ```\n *\n * @see {@link Store} for the interface definition\n * @see {@link InMemoryStore} for development/testing\n * @see {@link store} for injecting stores\n * @see {@link https://node-postgres.com/ | node-postgres documentation}\n *\n * @category Adapters\n */\nexport class PostgresStore implements Store {\n private _pool;\n readonly config: Config;\n private _fqt: string;\n private _fqs: string;\n /** Correlate checkpoint table (#1484) — one row, id 0. */\n private _fqc: string;\n /**\n * Per-instance writer identifier embedded in every NOTIFY payload. The\n * `notify()` LISTEN handler skips payloads where `by === this._by`,\n * giving the `\"notified\"` lifecycle event a clean cross-process\n * semantic — local commits never echo back through this channel.\n */\n private readonly _by: string = randomUUID();\n /**\n * Effective NOTIFY channel for this store. Computed from `(schema,\n * table)` at construction so multiple stores in the same database\n * stay isolated.\n */\n private readonly _channel: string;\n /** Active LISTEN client (one per `notify()` subscription). */\n private _listen_client: pg.PoolClient | undefined;\n /**\n * Notification listener attached to the active LISTEN client. Tracked\n * separately so the re-subscribe / dispose paths can detach it before\n * destroying the client — without this, a pool that reused the\n * connection would re-fire the stale handler.\n */\n private _listen_handler: ((msg: pg.Notification) => void) | undefined;\n /**\n * Error listener attached to the active LISTEN client. node-postgres\n * removes its idle-error guard on checkout, so a checked-out client\n * that emits `error` (backend restart, failover, network drop) with no\n * listener is an uncaught exception — a process crash (#1189). Tracked\n * alongside `_listen_handler` so teardown detaches it in lockstep.\n */\n private _listen_error_handler: ((err: Error) => void) | undefined;\n /**\n * The caller's notification handler for the active subscription, kept\n * so the self-healing reconnect path (#1189) can re-establish LISTEN\n * on a fresh client after the dedicated one emits `error`. Cleared by\n * `_teardown_listen`, which is what makes disposal cancel any pending\n * reconnect.\n */\n private _notify_handler:\n | ((notification: StoreNotification) => void)\n | undefined;\n /**\n * Pending reconnect timer, if a LISTEN client error scheduled one.\n * Tracked so `_teardown_listen` (and therefore `dispose()`) can cancel\n * it — a reconnect must never fire after teardown.\n */\n private _reconnect_timer: ReturnType<typeof setTimeout> | undefined;\n /**\n * Consecutive reconnect attempts since the last healthy LISTEN, used to\n * grow the capped exponential backoff. Reset to 0 once a re-LISTEN\n * succeeds.\n */\n private _reconnect_attempts = 0;\n /**\n * Cross-process commit subscription. **Present only when\n * `config.notify === true`** — the orchestrator's auto-wire path\n * checks `if (store.notify)`, so omitting the method keeps\n * single-instance deployments free of any LISTEN/NOTIFY overhead\n * (no dedicated client, no per-commit `pg_notify`).\n *\n * @see {@link Config.notify} for the rationale and the multi-process\n * contract.\n */\n notify?: (\n handler: (notification: StoreNotification) => void\n ) => Promise<NotifyDisposer>;\n\n /**\n * Memoized key resolver for the optional `pii_encryption` envelope.\n * Initialized in the constructor when encryption is configured;\n * `undefined` otherwise. The resolver caches the operator's key on\n * first use — rotation means restarting the store with a fresh\n * provider.\n */\n private readonly _resolve_pii_key: (() => Promise<Buffer>) | undefined;\n\n /**\n * Create a new PostgresStore instance.\n * @param config Partial configuration (host, port, user, password, schema, table, etc.)\n */\n constructor(config: Partial<Config> = {}) {\n this.config = { ...DEFAULT_CONFIG, ...config };\n assert_safe_identifier(this.config.schema, \"schema\");\n assert_safe_identifier(this.config.table, \"table\");\n const {\n schema: _,\n table: __,\n pii_encryption: ___,\n ...poolConfig\n } = this.config;\n // Per-Pool JSONB reviver (#1198): scoped here, never global.\n this._pool = new Pool({ ...poolConfig, types: scopedTypes });\n this._fqt = `\"${this.config.schema}\".\"${this.config.table}\"`;\n this._fqs = `\"${this.config.schema}\".\"${this.config.table}_streams\"`;\n this._fqc = `\"${this.config.schema}\".\"${this.config.table}_correlated\"`;\n this._channel = notify_channel(this.config.schema, this.config.table);\n // Attach the notify subscriber only when the user opted in. With\n // notify off, `this.notify` is `undefined`, the orchestrator skips\n // its auto-wire, and no LISTEN client is ever allocated.\n if (this.config.notify) {\n this.notify = this._subscribe_notifications.bind(this);\n }\n this._resolve_pii_key = this.config.pii_encryption\n ? makeKeyResolver(this.config.pii_encryption)\n : undefined;\n }\n\n /**\n * Acquire a pooled client, translating acquisition failures into\n * {@link StoreError} with the calling operation as context. With the\n * default `connectionTimeoutMillis`, a saturated pool fails here\n * after 10s with `Store operation \"<operation>\" failed` (driver\n * error preserved as `cause`) instead of hanging indefinitely.\n * Every method that checks out a client routes through this helper.\n */\n private async _client(operation: string): Promise<pg.PoolClient> {\n try {\n return await this._pool.connect();\n } catch (error) {\n throw new StoreError(operation, { cause: error });\n }\n }\n\n /**\n * Dispose of the store and close all database connections.\n * Releases any active LISTEN client first so the pool can drain cleanly.\n * @returns Promise that resolves when all connections are closed\n */\n async dispose() {\n await this._teardown_listen();\n await this._pool.end();\n }\n\n /**\n * Tear down the active LISTEN subscription if any: cancel any pending\n * reconnect, forget the caller's handler (so no reconnect can fire\n * after teardown), detach the notification + error listeners, run\n * UNLISTEN, and destroy the dedicated client (do not return it to the\n * pool — its listeners are removed but destroying belt-and-braces\n * guards against any future change in pg-pool semantics that could\n * re-issue a half-clean client).\n *\n * Clearing `_notify_handler` and the reconnect timer here is what makes\n * `dispose()` safe during a pending reconnect (#1189): a scheduled\n * `_reconnect` bails the moment it finds no handler.\n */\n private async _teardown_listen() {\n if (this._reconnect_timer) {\n clearTimeout(this._reconnect_timer);\n this._reconnect_timer = undefined;\n }\n this._notify_handler = undefined;\n this._reconnect_attempts = 0;\n if (!this._listen_client) return;\n // _listen_handler and _listen_error_handler are set in lockstep with\n // _listen_client in _open_listen, so if the client is present, both\n // handlers are too.\n this._listen_client.removeListener(\"notification\", this._listen_handler!);\n this._listen_client.removeListener(\"error\", this._listen_error_handler!);\n this._listen_handler = undefined;\n this._listen_error_handler = undefined;\n try {\n await this._listen_client.query(`UNLISTEN ${this._channel}`);\n } catch {\n // best-effort — pool end (or destroy) tears the connection down\n }\n this._listen_client.release(true);\n this._listen_client = undefined;\n }\n\n /**\n * Seed the database with required tables, indexes, and schema for event storage.\n * @returns Promise that resolves when seeding is complete\n * @throws Error if seeding fails\n */\n async seed() {\n const client = await this._client(\"seed\");\n\n try {\n await client.query(\"BEGIN\");\n\n // Serialize concurrent cold boots. IF NOT EXISTS DDL is not\n // race-safe while the objects are first being created — two\n // connections creating the same table simultaneously can trip\n // catalog unique-key errors — so N workers booting an empty\n // schema at once serialize here instead. Transaction-scoped:\n // the lock releases at COMMIT/ROLLBACK, and steady-state\n // re-seeds pass through in microseconds.\n // Two locks, because two different scopes are being guarded. The\n // schema-scoped one covers `CREATE SCHEMA` below: keying only on\n // `schema.table` let stores sharing a schema but using different table\n // names hash to different keys, so their CREATE SCHEMA calls raced the\n // catalog and all but one failed with a duplicate-key error on\n // pg_namespace — `IF NOT EXISTS` is not atomic against a concurrent\n // creator (#1421). The table-scoped one keeps the per-table DDL below\n // concurrent across tables in the same schema.\n await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [\n this.config.schema,\n ]);\n await client.query(`SELECT pg_advisory_xact_lock(hashtext($1))`, [\n `${this.config.schema}.${this.config.table}`,\n ]);\n\n // Create schema\n await client.query(\n `CREATE SCHEMA IF NOT EXISTS \"${this.config.schema}\";`\n );\n\n // Events table\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqt} (\n id serial PRIMARY KEY,\n name text COLLATE pg_catalog.\"default\" NOT NULL,\n data jsonb,\n stream text COLLATE pg_catalog.\"default\" NOT NULL,\n version int NOT NULL,\n created timestamptz NOT NULL DEFAULT now(),\n meta jsonb,\n pii jsonb\n ) TABLESPACE pg_default;`\n );\n // Migration for tables created before pii_isolation (#870).\n // Variable-length encoding skips NULL columns entirely, so events\n // without sensitive declarations pay zero extra bytes on disk.\n await client.query(\n `ALTER TABLE ${this._fqt} ADD COLUMN IF NOT EXISTS pii jsonb;`\n );\n\n // Indexes on events\n await client.query(\n `CREATE UNIQUE INDEX IF NOT EXISTS \"${this.config.table}_stream_ix\" \n ON ${this._fqt} (stream COLLATE pg_catalog.\"default\", version);`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_name_ix\" \n ON ${this._fqt} (name COLLATE pg_catalog.\"default\");`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_created_id_ix\" \n ON ${this._fqt} (created, id);`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_correlation_ix\"\n ON ${this._fqt} ((meta ->> 'correlation') COLLATE pg_catalog.\"default\");`\n );\n // Partial index over snapshot rows only, so the with_snaps \"resume\n // at the latest snapshot\" floor (MAX(id) WHERE stream=? AND\n // name='__snapshot__') is an O(log) lookup and costs nothing for\n // streams that have no snapshot (the index has no rows for them).\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_snapshot_ix\"\n ON ${this._fqt} (stream COLLATE pg_catalog.\"default\", id)\n WHERE name = '${SNAP_EVENT}';`\n );\n // The complement of the snapshot index, and the one `claim`'s has-work\n // probe seeks on: \"does this source stream have a non-snapshot event\n // past the watermark?\" (#1448). Partial over non-snapshot rows so the\n // two indexes partition the table rather than overlapping.\n //\n // Without it the probe fell back to the pk and scanned forward from\n // each stream's watermark — and because a dormant aggregate's\n // watermark is old, that tail is long and grows with the table. At 10k\n // subscribed streams a single claim cost 5,792 ms; with this index and\n // the source-class split in `claim`, 10.3 ms.\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_stream_id_ix\"\n ON ${this._fqt} (stream COLLATE pg_catalog.\"default\", id)\n WHERE name <> '${SNAP_EVENT}';`\n );\n\n // Streams table\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqs} (\n stream text COLLATE pg_catalog.\"default\" PRIMARY KEY,\n source text COLLATE pg_catalog.\"default\",\n at int NOT NULL DEFAULT -1,\n retry int NOT NULL DEFAULT -1,\n blocked boolean NOT NULL DEFAULT false,\n error text,\n leased_by text,\n leased_until timestamptz,\n priority int NOT NULL DEFAULT 0,\n lane text NOT NULL DEFAULT 'default',\n deferred_at timestamptz,\n correlated_at int\n ) TABLESPACE pg_default;`\n );\n // Correlate checkpoint (#1484). Its own single-row relation rather\n // than a reserved subscription: a subscription row is counted by every\n // stream-scoped operator surface (`prioritize`, `reset`, `unblock`,\n // `query_streams`, `blocked_streams`) and would inflate the counts\n // they report. No lease columns: the write is a monotonic `MAX`, so\n // concurrent correlators converge without holding anything.\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqc} (\n id int PRIMARY KEY DEFAULT 0,\n at int NOT NULL DEFAULT -1,\n CONSTRAINT ${this.config.table}_correlated_singleton CHECK (id = 0)\n ) TABLESPACE pg_default;`\n );\n await client.query(\n `INSERT INTO ${this._fqc} (id) VALUES (0) ON CONFLICT (id) DO NOTHING;`\n );\n\n // Migration for tables created before priority lanes (ACT-102).\n // `ADD COLUMN IF NOT EXISTS` is a no-op when the column is\n // already present, so this is safe on every seed call.\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS priority int NOT NULL DEFAULT 0;`\n );\n // Migration for tables created before drain lanes (ACT-1103).\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS lane text NOT NULL DEFAULT 'default';`\n );\n // Migration for tables created before deferred reactions (#1090).\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS deferred_at timestamptz;`\n );\n // Migration for tables created before the work set (#1485).\n await client.query(\n `ALTER TABLE ${this._fqs}\n ADD COLUMN IF NOT EXISTS correlated_at int;`\n );\n // Rows that predate the mark get one, at the log's head (#1488).\n // `claim` is mark-only now, so a row left at NULL would silently stop\n // being served, and correlate cannot rescue it — its checkpoint is\n // long past those events.\n //\n // This is the one mark the system writes without having resolved an\n // event to the target, and it is deliberately an over-estimate: it\n // says \"worth one look\", not \"there is work here\". The first drain\n // pass claims each such stream once, fetches its window, handles\n // whatever is really there, and acks — after which the watermark\n // catches up to the mark and the stream leaves the claimable set with\n // an honest position. One extra cycle per pre-existing subscription,\n // paid once, in exchange for a schema step with no per-row probing.\n //\n // NULL rows created *after* the upgrade (a static target subscribed\n // but not yet correlated) are picked up by the same statement on a\n // later seed, which costs them the same single empty cycle.\n await client.query(\n `UPDATE ${this._fqs}\n SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM ${this._fqt})\n WHERE correlated_at IS NULL;`\n );\n // Migration for tables created before the retry widening (#1190).\n // `claim()` increments `retry` on every acquisition and never\n // resets it for a zero-progress `blockOnError: false` stream, so a\n // poison stream marches the counter up without bound. The original\n // `smallint` column overflowed at 32768, throwing \"smallint out of\n // range\" and killing every claim in the lane. Widen to `int` so PG\n // matches the unbounded SQLite/InMemory adapters, preserving every\n // existing value (smallint ⊂ int). Guarded on the current type so\n // steady-state re-seeds skip the DDL — and its brief ACCESS\n // EXCLUSIVE lock — entirely once the column is already `integer`.\n await client.query(\n `DO $$\n BEGIN\n IF EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = '${this.config.schema}'\n AND table_name = '${this.config.table}_streams'\n AND column_name = 'retry'\n AND data_type = 'smallint'\n ) THEN\n EXECUTE 'ALTER TABLE ${this._fqs} ALTER COLUMN retry TYPE integer';\n END IF;\n END\n $$;`\n );\n\n // Migration for tables created before the identifier widening (#1420).\n // `stream` / `source` / `name` were `varchar(100)`, while InMemory is\n // unbounded and SQLite uses TEXT. The framework DERIVES identifiers that\n // can exceed the cap — `.autocloses` synthesizes a target of\n // `\"__autoclose__:\" + stream` (14 chars), so an 87-char stream commits\n // fine and then its subscribe fails. `correlate-cycle` only advances its\n // checkpoint after subscribe succeeds, so that throw pins the checkpoint\n // and stalls EVERY dynamic-resolver reaction app-wide, restart included.\n // `text` and `varchar` are byte-identical in PG storage and indexing, so\n // this costs nothing. Guarded on the current type so steady-state\n // re-seeds skip the DDL and its brief ACCESS EXCLUSIVE lock.\n for (const [table, columns] of [\n [this.config.table, [\"stream\", \"name\"]],\n [`${this.config.table}_streams`, [\"stream\", \"source\"]],\n ] as const) {\n for (const column of columns) {\n await client.query(\n `DO $$\n BEGIN\n IF EXISTS (\n SELECT 1 FROM information_schema.columns\n WHERE table_schema = '${this.config.schema}'\n AND table_name = '${table}'\n AND column_name = '${column}'\n AND data_type = 'character varying'\n ) THEN\n EXECUTE 'ALTER TABLE \"${this.config.schema}\".\"${table}\" ALTER COLUMN ${column} TYPE text';\n END IF;\n END\n $$;`\n );\n }\n }\n\n // Composite index for `claim()` — `(blocked, priority DESC, at)`\n // matches the lagging-frontier ORDER BY exactly so the planner\n // can serve the lag CTE from the index without a sort. The\n // `_streams_fetch_ix` index is dropped because the new one\n // supersedes it (`(blocked, at)` is a prefix of the new key\n // when the planner reads `priority` as fixed).\n await client.query(\n `DROP INDEX IF EXISTS \"${this.config.schema}\".\"${this.config.table}_streams_fetch_ix\"`\n );\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_claim_ix\"\n ON ${this._fqs} (blocked, priority DESC, at);`\n );\n // Lane filter index (ACT-1103).\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_lane_ix\"\n ON ${this._fqs} (lane);`\n );\n // The correlated set IS the index (#1485). `at < correlated_at` is a legal\n // partial-index predicate — immutable, single-row, no cross-row\n // reference — so the index holds only streams with work: `LIMIT` pushes\n // into it, a stream leaves when `ack` advances `at` to `correlated_at`,\n // and re-enters when correlate raises the mark. That makes `claim` an\n // index scan of at most `lagging + leading` rows, independent of how\n // many streams are subscribed.\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_correlated_at_ix\"\n ON ${this._fqs} (lane, priority DESC, at)\n WHERE blocked = false AND at < correlated_at;`\n );\n // The same set ordered by watermark alone (#1510). `claim`'s fairness\n // reserve and its leading frontier both order by `at` with the priority\n // column ignored, which the index above cannot serve — its leading key\n // is `lane`, and `priority` sits between that and `at`. Without this,\n // those two arms fall back to sorting the whole eligible set: 19.9 ms\n // for a claim returning 8 rows at 100k subscriptions, against 2.0 ms\n // with it.\n //\n // It costs a second index entry per `ack`, on a table whose write churn\n // is already the measured pathology — worth it because a claim happens\n // once per cycle and pays tens of milliseconds, while the extra\n // maintenance is tens of microseconds on the same cycle.\n await client.query(\n `CREATE INDEX IF NOT EXISTS \"${this.config.table}_streams_at_ix\"\n ON ${this._fqs} (at)\n WHERE blocked = false AND at < correlated_at;`\n );\n\n await client.query(\"COMMIT\");\n logger.info(\n `Seeded schema \"${this.config.schema}\" with table \"${this.config.table}\"`\n );\n } catch (error) {\n await client.query(\"ROLLBACK\");\n logger.error(error);\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Drop all tables and schema created by the store (for testing or cleanup).\n * @returns Promise that resolves when the schema is dropped\n */\n async drop() {\n await this._pool.query(\n `\n DO $$\n BEGIN\n IF EXISTS (SELECT 1 FROM information_schema.schemata\n WHERE schema_name = '${this.config.schema}'\n ) THEN\n EXECUTE 'DROP TABLE IF EXISTS ${this._fqt}';\n EXECUTE 'DROP TABLE IF EXISTS ${this._fqc}, ${this._fqs}';\n IF '${this.config.schema}' <> 'public' THEN\n EXECUTE 'DROP SCHEMA \"${this.config.schema}\" CASCADE';\n END IF;\n END IF;\n END\n $$;\n `\n );\n }\n\n /**\n * Query events from the store, optionally filtered by stream, event name, time, etc.\n *\n * @param callback Function called for each event found\n * @param query (Optional) Query filter (stream, names, before, after, etc.)\n * @returns The number of events found\n *\n * @example\n * await store.query((event) => console.log(event), { stream: \"A\" });\n */\n async query<E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ) {\n const {\n stream,\n names,\n before,\n after,\n limit,\n created_before,\n created_after,\n backward,\n correlation,\n with_snaps = false,\n } = query || {};\n\n let sql = `SELECT * FROM ${this._fqt}`;\n const conditions: string[] = [];\n const values: any[] = [];\n\n if (query) {\n if (typeof after !== \"undefined\") {\n values.push(after);\n conditions.push(`id>$${values.length}`);\n } else if (with_snaps && query.stream_exact && stream) {\n // Resume at the latest snapshot for this stream so pre-snapshot\n // events aren't scanned. No snapshot → MAX is NULL → -1 → full\n // stream. An explicit `after` (above) wins. The orchestrator only\n // sets `with_snaps` for an unbounded current-state load — it\n // suppresses the flag under any `asOf` bound (RFC 1274) — so the\n // floor never needs to re-check `before`/`created_*`/`limit` here.\n values.push(stream);\n conditions.push(\n `id >= (SELECT COALESCE(MAX(id), -1) FROM ${this._fqt} WHERE stream=$${values.length} AND name='${SNAP_EVENT}')`\n );\n } else {\n conditions.push(\"id>-1\");\n }\n if (stream) {\n values.push(stream);\n conditions.push(\n query.stream_exact\n ? `stream = $${values.length}`\n : `stream ~ $${values.length}`\n );\n }\n if (names !== undefined) {\n // #1199: `names: []` means \"match no event names\" — an empty\n // allow-list. `name = ANY('{}')` is always false, matching the\n // InMemory/SQLite semantics. Historically a truthy `names?.length`\n // guard dropped the empty filter and returned ALL — the opposite.\n values.push(names);\n conditions.push(`name = ANY($${values.length})`);\n }\n if (before !== undefined) {\n // #1199: `!== undefined` so a falsy-zero `before: 0` (strictly\n // \"id < 0\", i.e. match nothing) is honored, not dropped.\n values.push(before);\n conditions.push(`id<$${values.length}`);\n }\n if (created_after) {\n values.push(created_after.toISOString());\n conditions.push(`created>$${values.length}`);\n }\n if (created_before) {\n values.push(created_before.toISOString());\n conditions.push(`created<$${values.length}`);\n }\n if (correlation) {\n values.push(correlation);\n conditions.push(`meta->>'correlation'=$${values.length}`);\n }\n if (!with_snaps) {\n conditions.push(`name <> '${SNAP_EVENT}'`);\n }\n }\n if (conditions.length) {\n sql += \" WHERE \" + conditions.join(\" AND \");\n }\n sql += ` ORDER BY id ${backward ? \"DESC\" : \"ASC\"}`;\n if (limit) {\n values.push(limit);\n sql += ` LIMIT $${values.length}`;\n }\n\n const result = await this._pool.query<Committed<E, keyof E>>(sql, values);\n for (const row of result.rows) {\n // Decrypt the pii column when encryption is configured and the\n // stored value is a string (encrypted writes land as JSONB\n // strings; plaintext rows land as JSONB objects, including\n // legacy data committed before encryption was enabled). The\n // type-based discriminator means mixed-data rollouts read\n // through transparently. The cast is local — `Committed.pii`\n // is `readonly` on the public type, but rows materialized from\n // the driver are mutable in-flight before they cross back to\n // the framework.\n if (this._resolve_pii_key && typeof row.pii === \"string\") {\n const decrypted = await decrypt(\n row.pii,\n this._resolve_pii_key,\n dateReviver\n );\n (row as { pii: unknown }).pii = decrypted;\n }\n await Promise.resolve(callback(row));\n }\n\n return result.rowCount ?? 0;\n }\n\n /**\n * Commit new events to the store for a given stream, with concurrency control.\n *\n * @param stream The stream name\n * @param msgs Array of messages (event name and data)\n * @param meta Event metadata (correlation, causation, etc.)\n * @param expectedVersion (Optional) Expected stream version for concurrency control\n * @returns Array of committed events\n * @throws ConcurrencyError if the expected version does not match\n */\n async commit<E extends Schemas>(\n stream: string,\n msgs: Message<E, keyof E>[],\n meta: EventMeta,\n expectedVersion?: number\n ) {\n if (msgs.length === 0) return [];\n // Serialize commit VISIBILITY, not just id assignment. `id` is a\n // serial: it is assigned at INSERT time but the row appears at\n // COMMIT time, and every watermark consumer (the claim has-work\n // probe, fetch's `after`, the correlate checkpoint) assumes id\n // order equals visibility order. Without a fence two concurrent\n // commits to different streams can surface out of id order, and a\n // reader that acks past the higher id permanently skips the lower\n // one — the classic event-store gap problem. Same-stream commits\n // were already serialized by the (stream, version) unique index;\n // the advisory lock below extends the guarantee across streams.\n //\n // The whole commit is TWO round trips with NO client round trip\n // inside the lock window: an unlocked head probe (its own implicit\n // transaction — optimistic concurrency is guarded by the unique\n // index, not the probe), then ONE autocommit statement that\n // acquires the xact-scoped lock in a CTE, inserts the batch, and\n // (when enabled) raises the NOTIFY — the lock is held only for\n // server-side execution plus the implicit COMMIT, never across a\n // client round trip. The pooled client is checked out through\n // `_client` so acquisition failures keep their StoreError context\n // (#1119); checkout itself is in-process, not a round trip.\n const client = await this._client(\"commit\");\n try {\n const last = await client.query<{ version: number }>(\n `SELECT version FROM ${this._fqt}\n WHERE stream=$1 ORDER BY version DESC LIMIT 1`,\n [stream]\n );\n let version = last.rows.at(0)?.version ?? -1;\n if (typeof expectedVersion === \"number\" && version !== expectedVersion)\n throw new ConcurrencyError(\n stream,\n version,\n msgs as unknown as Message<Schemas, string>[],\n expectedVersion\n );\n\n // Encrypt the pii payloads when encryption is configured and\n // there's anything to encrypt — `null` passes through verbatim so\n // `forget_pii` semantics survive intact (a NULL stays NULL).\n // Encrypted output is `JSON.stringify`-ed so the bare base64\n // string casts to jsonb as a JSON string literal. Data/pii travel\n // as text[] and cast to jsonb in SQL — the pg driver can't\n // serialize object elements inside a jsonb[] parameter.\n const base_version = version;\n const names: string[] = [];\n const datas: string[] = [];\n const piis: (string | null)[] = [];\n const versions: number[] = [];\n for (const { name, data, pii } of msgs) {\n version++;\n names.push(name as string);\n datas.push(JSON.stringify(data));\n piis.push(\n this._resolve_pii_key && pii != null\n ? JSON.stringify(await encrypt(pii, this._resolve_pii_key))\n : pii != null\n ? JSON.stringify(pii)\n : null\n );\n versions.push(version);\n }\n\n // The cross join on the lock CTE forces the advisory lock to be\n // acquired before any row (and therefore any serial id) is\n // produced. Single-event commits (the overwhelmingly common\n // shape) skip the unnest machinery for a leaner plan. The NOTIFY\n // rides the same statement as a CTE: one notification per commit\n // transaction with the full batch, delivered at the implicit\n // COMMIT, skipped in SQL when the payload would exceed PG's cap\n // (listeners fall back to the poll path — degraded latency,\n // never lost events), and skipped entirely when\n // `config.notify === false` (the default). The final select LEFT\n // JOINs the notify CTE so it is referenced (a bare SELECT CTE\n // would otherwise be skipped) without changing row multiplicity —\n // it yields at most one row.\n const insert_select =\n msgs.length === 1\n ? `SELECT $1, $2::jsonb, $3::jsonb, $5, $4::int, $6 FROM l`\n : `SELECT u.name, u.data::jsonb, u.pii::jsonb, $5, u.version, $6\n FROM l, unnest($1::text[], $2::text[], $3::text[], $4::int[])\n WITH ORDINALITY AS u(name, data, pii, version, ord)\n ORDER BY u.ord`;\n const notify_ctes = this.config.notify\n ? `,\n payload AS (\n SELECT json_build_object(\n 'stream', $5::text,\n 'events', json_agg(json_build_object('id', ins.id, 'name', ins.name) ORDER BY ins.version),\n 'by', $9::text\n )::text AS p\n FROM ins\n ),\n n AS (\n SELECT pg_notify($8, payload.p) FROM payload\n WHERE octet_length(payload.p) < $10\n )`\n : \"\";\n const final_select = this.config.notify\n ? \"SELECT ins.* FROM ins LEFT JOIN n ON true ORDER BY ins.version\"\n : \"SELECT * FROM ins ORDER BY version\";\n const sql = `WITH l AS (SELECT pg_advisory_xact_lock(hashtext($7))),\n ins AS (\n INSERT INTO ${this._fqt}(name, data, pii, stream, version, meta)\n ${insert_select}\n RETURNING *\n )${notify_ctes}\n ${final_select}`;\n const base_params =\n msgs.length === 1\n ? [names[0], datas[0], piis[0], versions[0], stream, meta, this._fqt]\n : [names, datas, piis, versions, stream, meta, this._fqt];\n const params = this.config.notify\n ? [...base_params, this._channel, this._by, NOTIFY_MAX_PAYLOAD_BYTES]\n : base_params;\n\n try {\n const { rows } = await client.query<Committed<E, keyof E>>(sql, params);\n // Decrypt before handing back to the caller — the committed\n // event the framework returns must carry the cleartext payload\n // (the reducer chain runs against `event.pii`). The encrypted\n // value only lives at rest in the column; never in memory past\n // this point.\n if (this._resolve_pii_key) {\n for (const row of rows) {\n if (typeof row.pii === \"string\") {\n const decrypted = await decrypt(\n row.pii,\n this._resolve_pii_key,\n dateReviver\n );\n (row as { pii: unknown }).pii = decrypted;\n }\n }\n }\n return rows;\n } catch (error) {\n // PG unique-violation on (stream, version) — a concurrent commit\n // beat us between the head probe and this INSERT. Surface as\n // ConcurrencyError so callers retry on the framework signal\n // instead of an adapter-specific error. The statement is its own\n // transaction, so there is nothing to roll back.\n if ((error as { code?: string })?.code === PG_UNIQUE_VIOLATION) {\n throw new ConcurrencyError(\n stream,\n base_version,\n msgs as unknown as Message<Schemas, string>[],\n expectedVersion ?? -1\n );\n }\n if (PG_NUL_BYTE.has((error as { code?: string })?.code ?? \"\")) {\n throw new ValidationError(\n stream,\n msgs as unknown as Readonly<unknown>,\n `Postgres cannot store a NUL byte (\\\\u0000). One of the ${msgs.length} event(s) committed to \"${stream}\" (${msgs.map((m) => String(m.name)).join(\", \")}) carries one in its name, data, meta or pii. InMemory and SQLite accept it, so this commit would have succeeded there — strip NUL bytes at the edge where the data enters. Driver: ${(error as Error).message}`\n );\n }\n throw error;\n }\n } finally {\n client.release();\n }\n }\n\n /**\n * Atomically discovers and leases streams for reaction processing.\n *\n * Uses `FOR UPDATE SKIP LOCKED` to implement zero-contention competing consumers:\n * - Workers never block each other — locked rows are silently skipped\n * - Discovery and locking happen in a single atomic transaction\n * - No wasted polls — every returned stream is exclusively owned\n *\n * @param lagging - Max streams from lagging frontier (ascending watermark)\n * @param leading - Max streams from leading frontier (descending watermark)\n * @param by - Lease holder identifier (UUID)\n * @param millis - Lease duration in milliseconds\n * @returns Leased streams with metadata\n */\n async claim(\n lagging: number,\n leading: number,\n by: string,\n millis: number,\n lane?: string\n ): Promise<Lease[]> {\n const client = await this._client(\"claim\");\n try {\n await client.query(\"BEGIN\");\n const lane_clause = lane !== undefined ? `AND s.lane = $6` : \"\";\n // Fairness reserve (ACT-1223): carve `fair` slots off the lagging\n // budget for pure watermark-order claims so a default-priority\n // lagging stream is never starved out by sustained higher-priority\n // load. `fair` is always $5; the optional `lane` bind is last ($6).\n const fair = lagging >= 2 ? Math.max(1, Math.floor(lagging / 4)) : 0;\n const params: unknown[] =\n lane !== undefined\n ? [lagging, leading, by, millis, fair, lane]\n : [lagging, leading, by, millis, fair];\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n retry: number;\n lagging: boolean;\n lane: string;\n }>(\n `\n WITH\n -- Plain read of the eligible frontier — no row lock here. A CTE\n -- carrying FOR UPDATE never inlines, so locking it would materialize\n -- and lock EVERY claimable stream for this transaction, starving\n -- overlapping competing consumers. We only lock the small\n -- lagging+leading candidate slice, down in the \"locked\" CTE.\n --\n -- Eligibility is a pure subscription-table predicate (#1488).\n -- claim does not read the event log at all: correlate marks the\n -- highest event id that resolves to a target, and at <\n -- correlated_at is the whole question. The probe this replaced ran\n -- an EXISTS against the events table once per eligible row, which\n -- cost O(subscribed streams) per claim per worker no matter how\n -- little work was pending.\n --\n -- The predicate is repeated in each arm below rather than factored\n -- into a shared CTE, and that repetition is load-bearing (#1510). A\n -- CTE referenced more than once is materialized, so LIMIT 8 was\n -- applied to a fully-built 100,000-row result instead of pushing into\n -- the index: measured at 75.6 ms for a claim that returns 8 rows.\n -- Reading the base table in each arm lets the planner stop after the\n -- limit — 19.9 ms with the existing index, 2.0 ms once the\n -- at-ordered partial index below serves the two watermark arms.\n --\n -- A row with no mark compares unknown and is excluded by SQL's own\n -- rules. That is definitional (#1446): a subscription is claimable iff\n -- a mark says so. seed() marks rows that predate the column.\n --\n -- Priority lanes (ACT-102): higher priority first, then\n -- lagging-watermark order. With everyone at priority=0 the ORDER BY\n -- collapses to plain at ASC, so existing workloads see no change.\n --\n -- The lagging frontier is a UNION of two portions (ACT-1223): the\n -- priority portion takes the first (lagging - fair) slots by\n -- priority DESC, at ASC; a fairness reserve then fills fair more\n -- by pure at ASC (priority ignored), excluding the ones already\n -- chosen, so a default-priority lagging stream is never starved out\n -- by sustained higher-priority load.\n prio AS (\n SELECT stream, source, at, lane, TRUE AS lagging\n FROM ${this._fqs} s\n WHERE s.blocked = false\n AND s.at < s.correlated_at\n ${lane_clause}\n AND (s.leased_by IS NULL OR s.leased_until <= NOW())\n AND (s.deferred_at IS NULL OR s.deferred_at <= NOW())\n ORDER BY s.priority DESC, s.at ASC\n LIMIT ($1::int - $5::int)\n ),\n fair AS (\n SELECT stream, source, at, lane, TRUE AS lagging\n FROM ${this._fqs} s\n WHERE s.blocked = false\n AND s.at < s.correlated_at\n ${lane_clause}\n AND (s.leased_by IS NULL OR s.leased_until <= NOW())\n AND (s.deferred_at IS NULL OR s.deferred_at <= NOW())\n AND s.stream NOT IN (SELECT stream FROM prio)\n ORDER BY s.at ASC\n LIMIT $5\n ),\n lag AS (\n SELECT * FROM prio UNION SELECT * FROM fair\n ),\n lead AS (\n SELECT stream, source, at, lane, FALSE AS lagging\n FROM ${this._fqs} s\n WHERE s.blocked = false\n AND s.at < s.correlated_at\n ${lane_clause}\n AND (s.leased_by IS NULL OR s.leased_until <= NOW())\n AND (s.deferred_at IS NULL OR s.deferred_at <= NOW())\n ORDER BY s.at DESC\n LIMIT $2\n ),\n combined AS (\n SELECT DISTINCT ON (stream) stream, source, at, lane, lagging\n FROM (SELECT * FROM lag UNION ALL SELECT * FROM lead) t\n ORDER BY stream, at\n ),\n -- Lock ONLY the <= lagging+leading candidate rows. The\n -- lease-eligibility predicate is re-asserted here under the lock so a\n -- lease acquired by a competing worker between the unlocked read and\n -- this lock is never stolen. Competing workers SKIP-LOCK just this\n -- slice, not the whole frontier, and claim other eligible streams.\n locked AS (\n SELECT s2.stream\n FROM ${this._fqs} s2\n WHERE s2.stream IN (SELECT stream FROM combined)\n AND s2.blocked = false\n AND (s2.leased_by IS NULL OR s2.leased_until <= NOW())\n AND (s2.deferred_at IS NULL OR s2.deferred_at <= NOW())\n FOR UPDATE OF s2 SKIP LOCKED\n )\n UPDATE ${this._fqs} s\n SET\n leased_by = $3,\n leased_until = NOW() + ($4::integer || ' milliseconds')::interval,\n retry = s.retry + 1\n FROM combined c\n WHERE s.stream = c.stream\n AND s.stream IN (SELECT stream FROM locked)\n RETURNING s.stream, s.source, s.at, s.retry, c.lagging, s.lane\n `,\n params\n );\n await client.query(\"COMMIT\");\n\n return rows.map(({ stream, source, at, retry, lagging, lane }) => ({\n stream,\n source: source ?? undefined,\n at,\n by,\n retry,\n lagging,\n lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"claim\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Registers streams for event processing.\n * Upserts stream entries so they become visible to claim().\n * Also returns the current max watermark across all subscriptions.\n * @param streams - Streams to register with optional source.\n * @returns subscribed count and current max watermark.\n */\n async subscribe(\n streams: SubscribeInput[],\n correlated_at?: number\n ): Promise<{ subscribed: number; watermark: number; correlated_at: number }> {\n const client = await this._client(\"subscribe\");\n try {\n await client.query(\"BEGIN\");\n let subscribed = 0;\n if (streams.length) {\n // Two statements, because `subscribed` means \"newly registered\n // streams\" and not \"rows touched\":\n // 1. INSERT ... ON CONFLICT DO NOTHING — rowCount = inserts.\n // 2. One UPDATE over the existing rows, carrying all three\n // mutable columns. Correlate re-subscribes every target it\n // marks (#1487), so this is a per-scan round trip on the\n // steady-state path — worth one statement rather than three.\n const { rowCount: inserted } = await client.query(\n `\n INSERT INTO ${this._fqs} (stream, source, priority, lane, retry)\n SELECT s->>'stream',\n s->>'source',\n COALESCE((s->>'priority')::int, 0),\n COALESCE(s->>'lane', 'default'),\n -1\n FROM jsonb_array_elements($1::jsonb) AS s\n ON CONFLICT (stream) DO NOTHING\n `,\n [JSON.stringify(streams)]\n );\n subscribed = inserted ?? 0;\n // Priority keeps the max (ACT-102: the highest-priority registered\n // reaction wins; operator overrides, which may *decrease*, go through\n // `prioritize()`), lane is last-writer-wins (ACT-1103), and the work\n // mark never regresses (#1485) — `GREATEST` reads through a NULL on\n // either side, so a first mark lands and an omitted one leaves the\n // stored value alone. The WHERE keeps the no-op case free of dead\n // tuples: a row is rewritten only when one of the three would change.\n await client.query(\n `\n UPDATE ${this._fqs} t\n SET priority = GREATEST(t.priority, COALESCE((s->>'priority')::int, 0)),\n lane = COALESCE(s->>'lane', 'default'),\n correlated_at = GREATEST(t.correlated_at, (s->>'correlated_at')::int)\n FROM jsonb_array_elements($1::jsonb) AS s\n WHERE t.stream = s->>'stream'\n AND (COALESCE((s->>'priority')::int, 0) > t.priority\n OR t.lane <> COALESCE(s->>'lane', 'default')\n OR (s->>'correlated_at' IS NOT NULL\n AND (t.correlated_at IS NULL\n OR t.correlated_at < (s->>'correlated_at')::int)))\n `,\n [JSON.stringify(streams)]\n );\n }\n // The correlate checkpoint is written by its own producer, in the call\n // correlate already makes (#1484). GREATEST keeps it monotonic.\n if (correlated_at !== undefined)\n await client.query(\n `UPDATE ${this._fqc} SET at = GREATEST(at, $1::int) WHERE id = 0`,\n [correlated_at]\n );\n // Watermark and checkpoint in one round trip — correlate needs both.\n const { rows } = await client.query<{\n max: number | null;\n correlated_at: string | null;\n }>(\n `SELECT (SELECT COALESCE(MAX(at), -1) FROM ${this._fqs}) AS max,\n (SELECT at FROM ${this._fqc} WHERE id = 0) AS correlated_at`\n );\n await client.query(\"COMMIT\");\n return {\n subscribed,\n watermark: rows[0]?.max ?? -1,\n correlated_at: Number(rows[0]?.correlated_at ?? -1),\n };\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"subscribe\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Acknowledge and release leases after processing, updating stream positions.\n *\n * @param leases - Leases to acknowledge, including last processed watermark and lease holder.\n * @returns Acked leases.\n */\n async ack(leases: Lease[]): Promise<Lease[]> {\n const client = await this._client(\"ack\");\n try {\n await client.query(\"BEGIN\");\n // One statement finalizes the whole batch, so acks and defer\n // schedules land all-or-nothing per the Store.ack contract. Every\n // entry advances the watermark to its `at` (the last event handled\n // this cycle); an entry without `due` also clears retry + schedule,\n // while an entry with `due` additionally sets the schedule and the\n // entry's own `retry` — advance and defer are independent legs\n // (#1278), so a partial-progress defer keeps the handled prefix.\n // An explicit defer passes retry -1 (not a failure); a backoff retry\n // passes the climbing counter so the budget keeps accruing across\n // windows (#1262). Deferred rows are filtered out of the returned acks.\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n by: string;\n retry: number;\n lagging: boolean;\n lane: string;\n due: string | null;\n }>(\n `\n WITH input AS (\n SELECT * FROM jsonb_to_recordset($1::jsonb)\n AS x(stream text, by text, at int, lagging boolean, due bigint, retry int)\n )\n UPDATE ${this._fqs} AS s\n SET\n at = i.at,\n retry = CASE WHEN i.due IS NULL THEN -1 ELSE i.retry END,\n leased_by = NULL,\n leased_until = NULL,\n deferred_at = CASE WHEN i.due IS NULL THEN NULL\n ELSE to_timestamp(i.due / 1000.0) END\n FROM input i\n WHERE s.stream = i.stream AND s.leased_by = i.by\n RETURNING s.stream, s.source, s.at, i.by, s.retry, i.lagging, s.lane, i.due\n `,\n [JSON.stringify(leases)]\n );\n await client.query(\"COMMIT\");\n\n return rows\n .filter((row) => row.due === null)\n .map((row) => ({\n stream: row.stream,\n source: row.source ?? undefined,\n at: row.at,\n by: row.by,\n retry: row.retry,\n lagging: row.lagging,\n lane: row.lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"ack\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Block a stream for processing after failing to process and reaching max retries with blocking enabled.\n * @param leases - Leases to block, including lease holder and last error message.\n * @returns Blocked leases.\n */\n async block(leases: BlockedLease[]): Promise<BlockedLease[]> {\n const client = await this._client(\"block\");\n try {\n await client.query(\"BEGIN\");\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n by: string;\n retry: number;\n lagging: boolean;\n error: string;\n lane: string;\n }>(\n `\n WITH input AS (\n SELECT * FROM jsonb_to_recordset($1::jsonb)\n AS x(stream text, by text, error text, lagging boolean)\n )\n UPDATE ${this._fqs} AS s\n SET blocked = true, error = i.error, deferred_at = NULL\n FROM input i\n WHERE s.stream = i.stream AND s.leased_by = i.by AND s.blocked = false\n RETURNING s.stream, s.source, s.at, i.by, s.retry, s.error, i.lagging, s.lane\n `,\n [JSON.stringify(leases)]\n );\n await client.query(\"COMMIT\");\n\n return rows.map((row) => ({\n stream: row.stream,\n source: row.source ?? undefined,\n at: row.at,\n by: row.by,\n retry: row.retry,\n lagging: row.lagging,\n error: row.error,\n lane: row.lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw new StoreError(\"block\", { cause: error });\n } finally {\n client.release();\n }\n }\n\n /**\n * Hold the matched streams out of {@link claim} until `deferred_at`\n * (ms since epoch) — see {@link Store.defer}. Persists `deferred_at`\n * (as a `timestamptz`) so the skip is honored by every competing\n * worker; `claim` filters on `deferred_at <= NOW()`. Accepts an\n * explicit list of names or a {@link StreamFilter}, mirroring\n * {@link reset}/{@link prioritize}. Cleared by ack/block/reset/unblock.\n *\n * @returns Count of streams whose `deferred_at` was set.\n */\n async defer(\n input: string[] | StreamFilter,\n deferred_at: number\n ): Promise<number> {\n // Reset retry too: a defer is a deliberate \"come back later,\" not a\n // failure, so the redelivery after the due-time is a fresh attempt.\n const set_clause = `SET deferred_at = to_timestamp($1 / 1000.0), retry = -1`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($2)`,\n [deferred_at, input]\n );\n return rowCount ?? 0;\n }\n const { clause, values } = this._filter_clause(input, 2);\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,\n [deferred_at, ...values]\n );\n return rowCount ?? 0;\n }\n\n /**\n * Reset watermarks for the given streams to -1, clearing retry, blocked,\n * error, and lease state so they can be replayed from the beginning.\n * @param streams - Stream names to reset.\n * @returns Count of streams that were actually reset.\n */\n /**\n * Translate a {@link StreamFilter} to a `WHERE` clause fragment and\n * the corresponding parameter values. The fragment never starts with\n * `WHERE` — callers compose it with any other predicates they need.\n * Returns an always-true clause (`true`) when the filter is empty.\n */\n private _filter_clause(\n filter: StreamFilter,\n start: number\n ): { clause: string; values: unknown[] } {\n const conditions: string[] = [];\n const values: unknown[] = [];\n if (filter.stream !== undefined) {\n values.push(filter.stream);\n conditions.push(\n filter.stream_exact\n ? `stream = $${start + values.length - 1}`\n : `stream ~ $${start + values.length - 1}`\n );\n }\n if (filter.source !== undefined) {\n conditions.push(`source IS NOT NULL`);\n values.push(filter.source);\n conditions.push(\n filter.source_exact\n ? `source = $${start + values.length - 1}`\n : `source ~ $${start + values.length - 1}`\n );\n }\n if (filter.blocked !== undefined) {\n values.push(filter.blocked);\n conditions.push(`blocked = $${start + values.length - 1}`);\n }\n if (filter.lane !== undefined) {\n values.push(filter.lane);\n conditions.push(`lane = $${start + values.length - 1}`);\n }\n return {\n clause: conditions.length ? conditions.join(\" AND \") : \"TRUE\",\n values,\n };\n }\n\n async reset(input: string[] | StreamFilter): Promise<number> {\n const set_clause = `SET at = -1, retry = -1, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL, deferred_at = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($1)`,\n [input]\n );\n return rowCount ?? 0;\n }\n const { clause, values } = this._filter_clause(input, 1);\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,\n values\n );\n return rowCount ?? 0;\n }\n\n /**\n * Clear blocked flag (and retry / error / lease state) on streams\n * without touching the `at` watermark. `blocked = true` is always\n * applied, so the return count reflects only streams that were\n * actually flipped — already-unblocked rows, unknown streams, and\n * filter matches that aren't blocked are silently skipped.\n *\n * `retry = -1` matches the InMemoryStore convention: claim() bumps\n * retry on every acquisition, so storing -1 means the first claim\n * after unblock returns retry=0 (\"first attempt\"). Storing 0 would\n * mis-report the post-recovery attempt as a continuation of the\n * failed sequence. See {@link Store.unblock}.\n *\n * @returns Count of streams that were actually flipped (were blocked).\n */\n async unblock(input: string[] | StreamFilter): Promise<number> {\n const set_clause = `SET retry = -1, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL, deferred_at = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause}\n WHERE stream = ANY($1) AND blocked = true`,\n [input]\n );\n return rowCount ?? 0;\n }\n // Filter form: force `blocked = true` regardless of what the\n // caller passed — there is no use case for \"unblock unblocked\n // streams.\" A no-op overlay is the right shape here.\n const { clause, values } = this._filter_clause(\n { ...input, blocked: true },\n 1\n );\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,\n values\n );\n return rowCount ?? 0;\n }\n\n /**\n * Bulk-update priority of streams matching `filter` (ACT-102).\n *\n * Filter semantics mirror {@link query_streams}: regex on `stream` /\n * `source` by default, exact match with the `_exact` flags,\n * `blocked` restricts to blocked or unblocked rows. Empty filter\n * (`{}`) updates every registered stream.\n *\n * Unlike {@link subscribe} (which keeps `max()` of registered\n * priorities), this sets the priority outright — operator override\n * for the build-time scheduling policy.\n *\n * @returns Count of streams whose priority changed.\n */\n async prioritize(filter: StreamFilter, priority: number): Promise<number> {\n const { clause, values } = this._filter_clause(filter, 2);\n const sql = `UPDATE ${this._fqs} SET priority = $1\n WHERE priority <> $1 AND ${clause}`;\n const { rowCount } = await this._pool.query(sql, [priority, ...values]);\n return rowCount ?? 0;\n }\n\n /**\n * Streams subscription positions to a callback, ordered by stream name,\n * along with the highest event id in the store.\n *\n * Filters (`stream`, `source`, `blocked`, `after`, `limit`) are applied\n * server-side. `stream`/`source` are regex by default (`~`), or exact\n * with `*_exact: true` — same convention as {@link Store.query}.\n *\n * @returns `maxEventId` and the `count` of positions emitted.\n */\n async query_streams(\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ): Promise<QueryStreamsResult> {\n const limit = query?.limit ?? 100;\n const conditions: string[] = [];\n const values: unknown[] = [];\n\n if (query?.stream !== undefined) {\n values.push(query.stream);\n conditions.push(\n query.stream_exact\n ? `stream = $${values.length}`\n : `stream ~ $${values.length}`\n );\n }\n if (query?.source !== undefined) {\n conditions.push(`source IS NOT NULL`);\n values.push(query.source);\n conditions.push(\n query.source_exact\n ? `source = $${values.length}`\n : `source ~ $${values.length}`\n );\n }\n if (query?.source_matches?.length) {\n // Reverse-match narrowing: the inverse of the `source` filter.\n // The stored `source` is treated as the regex pattern, and a row\n // qualifies when any supplied candidate name matches it (`n ~ source`).\n // A NULL/empty source has no source constraint — it consumes from\n // every stream, so it always qualifies. Composes (AND) with others.\n values.push(query.source_matches);\n conditions.push(\n `(source IS NULL OR source = '' OR EXISTS (\n SELECT 1 FROM unnest($${values.length}::text[]) AS n WHERE n ~ source\n ))`\n );\n }\n if (query?.blocked !== undefined) {\n values.push(query.blocked);\n conditions.push(`blocked = $${values.length}`);\n }\n if (query?.lane !== undefined) {\n values.push(query.lane);\n conditions.push(`lane = $${values.length}`);\n }\n if (query?.after !== undefined) {\n values.push(query.after);\n conditions.push(`stream > $${values.length}`);\n }\n let sql = `SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane, deferred_at, correlated_at FROM ${this._fqs}`;\n if (conditions.length) sql += \" WHERE \" + conditions.join(\" AND \");\n values.push(limit);\n sql += ` ORDER BY stream LIMIT $${values.length}`;\n\n const client = await this._client(\"query_streams\");\n try {\n const [streamsResult, maxResult] = await Promise.all([\n client.query<{\n stream: string;\n source: string | null;\n at: number;\n retry: number;\n blocked: boolean;\n error: string | null;\n leased_by: string | null;\n leased_until: Date | null;\n priority: number;\n lane: string;\n deferred_at: Date | null;\n correlated_at: number | null;\n }>(sql, values),\n client.query<{ m: number | null }>(\n `SELECT COALESCE(MAX(id), -1) AS m FROM ${this._fqt}`\n ),\n ]);\n\n let count = 0;\n for (const row of streamsResult.rows) {\n callback({\n stream: row.stream,\n source: row.source ?? undefined,\n at: row.at,\n retry: row.retry,\n blocked: row.blocked,\n error: row.error ?? \"\",\n priority: row.priority,\n leased_by: row.leased_by ?? undefined,\n leased_until: row.leased_until ?? undefined,\n lane: row.lane,\n // Persisted as timestamptz; surface as ms since epoch (#1221) so\n // the cold-start re-seed can re-arm the drain at the due-time.\n deferred_at: row.deferred_at ? row.deferred_at.getTime() : undefined,\n // NULL means \"no mark yet\" — unknown, not \"no work\" (#1485).\n correlated_at: row.correlated_at ?? undefined,\n });\n count++;\n }\n\n return { maxEventId: Number(maxResult.rows[0].m), count };\n } finally {\n client.release();\n }\n }\n\n /**\n * Per-stream aggregated stats — see {@link Store.query_stats}.\n *\n * Two code paths chosen by the requested stats:\n *\n * - **Heads-only path** (no `count`, no `names`): one or two\n * `SELECT DISTINCT ON (stream) ... ORDER BY stream, version DESC|ASC`\n * queries, executed in parallel when `tail: true`. The\n * `(stream, version)` unique index gives index-only access — K rows\n * touched per query (K = matched streams), not N (events).\n * Ordering by `version` (not `id`) is equivalent within a stream\n * (versions are monotonic per stream and events are committed\n * sequentially) and is the column actually indexed.\n *\n * - **Full-scan path** (`count` or `names` set): one CTE materializes\n * the filtered events, then `GROUP BY stream, name` →\n * `jsonb_object_agg(name, n)` for the `names` map plus per-stream\n * `COUNT(*)` for `count`. Heads (and `tails` when requested) come\n * from `DISTINCT ON` over the same CTE — they ride free on the\n * already-paid scan.\n *\n * The stream universe is derived from the events table: filter form\n * matches event-bearing streams (not subscription rows). When the\n * filter sets `source` or `blocked`, the events table is joined\n * against the streams subscription table since those concepts only\n * exist for subscribed streams.\n */\n async query_stats<E extends Schemas>(\n input: string[] | Pick<StreamFilter, \"stream\" | \"stream_exact\">,\n options?: QueryStatsOptions<E>\n ): Promise<Map<string, StreamStats<E>>> {\n const exclude = options?.exclude ?? [];\n const want_tail = options?.tail ?? false;\n const want_count = options?.count ?? false;\n const want_names = options?.names ?? false;\n const before = options?.before;\n const after = options?.after;\n const stats_limit = options?.limit;\n const full_scan = want_count || want_names;\n\n // Empty array short-circuit — saves a round trip on a no-op.\n if (Array.isArray(input) && input.length === 0) {\n return new Map<string, StreamStats<E>>();\n }\n\n // Build WHERE clause + parameter list. Subscription-level filters\n // (source, blocked) are intentionally not accepted — events live in\n // the events table; subscription state in the streams table. For\n // \"stats for blocked subscriptions\" callers compose with\n // query_streams. So no JOIN here.\n const where: string[] = [];\n const params: unknown[] = [];\n\n if (Array.isArray(input)) {\n params.push(input);\n where.push(`e.stream = ANY($${params.length})`);\n } else if (input.stream !== undefined) {\n params.push(input.stream);\n where.push(\n input.stream_exact\n ? `e.stream = $${params.length}`\n : `e.stream ~ $${params.length}`\n );\n }\n if (exclude.length) {\n params.push(exclude);\n where.push(`e.name <> ALL($${params.length})`);\n }\n if (before !== undefined) {\n params.push(before);\n where.push(`e.id < $${params.length}`);\n }\n if (after !== undefined) {\n // Keyset pagination cursor — exclusive on stream name. Results are\n // ordered by stream ascending so callers chain\n // `[...map.keys()].at(-1)` as the next cursor.\n params.push(after);\n where.push(`e.stream > $${params.length}`);\n }\n\n const from_clause = `${this._fqt} e`;\n // Always emit a WHERE clause — `WHERE TRUE` short-circuits the\n // empty-filter case without a conditional branch on the generation\n // side. PG optimizes the trivial predicate out.\n const where_clause = `WHERE ${where.length ? where.join(\" AND \") : \"TRUE\"}`;\n\n return full_scan\n ? this._query_stats_full_scan<E>(\n from_clause,\n where_clause,\n params,\n want_tail,\n want_count,\n want_names,\n stats_limit\n )\n : this._query_stats_heads_only<E>(\n from_clause,\n where_clause,\n params,\n want_tail,\n stats_limit\n );\n }\n\n /**\n * Cheap path: index-only DISTINCT ON for the head per stream, plus an\n * optional second query (in parallel) for the tail. K rows touched\n * per query, not N events.\n */\n private async _query_stats_heads_only<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n params: unknown[],\n want_tail: boolean,\n stats_limit?: number\n ): Promise<Map<string, StreamStats<E>>> {\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;\n // `DISTINCT ON (e.stream) ... ORDER BY e.stream` already yields one row\n // per stream in stream-name order, so a trailing LIMIT caps the number\n // of streams returned. The head and tail queries share the same\n // ordering, so the same LIMIT selects the identical first-N streams.\n const limit_clause =\n stats_limit !== undefined ? ` LIMIT ${stats_limit}` : \"\";\n const head_sql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version DESC${limit_clause}`;\n const tail_sql = want_tail\n ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version ASC${limit_clause}`\n : null;\n\n const [headRes, tailRes] = await Promise.all([\n this._pool.query<Committed<E, keyof E>>(head_sql, params),\n tail_sql\n ? this._pool.query<Committed<E, keyof E>>(tail_sql, params)\n : Promise.resolve(null),\n ]);\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of headRes.rows) {\n out.set(row.stream, { head: row });\n }\n if (tailRes) {\n for (const row of tailRes.rows) {\n // Head and tail share the same WHERE, so any stream returning a\n // tail must also have returned a head — no null check needed.\n (\n out.get(row.stream) as {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n }\n ).tail = row;\n }\n }\n return out;\n }\n\n /**\n * Full-scan path: one CTE-based query computes the per-stream\n * `COUNT(*)` and `jsonb_object_agg(name, n)` map alongside the head\n * (and tail when requested). All extras share the single events scan.\n */\n private async _query_stats_full_scan<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n params: unknown[],\n want_tail: boolean,\n want_count: boolean,\n want_names: boolean,\n stats_limit?: number\n ): Promise<Map<string, StreamStats<E>>> {\n const tail_cte = want_tail\n ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)`\n : \"\";\n const tail_join = want_tail\n ? `LEFT JOIN tails t ON t.stream = h.stream`\n : \"\";\n const tail_cols = want_tail\n ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,\n t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta`\n : \"\";\n\n const sql = `\n WITH ef AS (\n SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta\n FROM ${from_clause}\n ${where_clause}\n ),\n agg AS (\n SELECT stream,\n SUM(n)::int AS cnt,\n jsonb_object_agg(name, n) AS names\n FROM (\n SELECT stream, name, COUNT(*)::int AS n\n FROM ef\n GROUP BY stream, name\n ) t\n GROUP BY stream\n ),\n heads AS (\n SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version DESC\n )\n ${tail_cte}\n SELECT\n h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,\n a.cnt AS agg_count,\n a.names AS agg_names\n ${tail_cols}\n FROM heads h\n LEFT JOIN agg a ON a.stream = h.stream\n ${tail_join}\n ORDER BY h.stream\n ${stats_limit !== undefined ? `LIMIT ${stats_limit}` : \"\"}\n `;\n\n const res = await this._pool.query<\n Committed<E, keyof E> & {\n agg_count: number;\n agg_names: Record<string, number> | null;\n t_id?: number;\n t_stream?: string;\n t_version?: number;\n t_name?: string;\n t_data?: object;\n t_created?: Date;\n t_meta?: object;\n }\n >(sql, params);\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of res.rows) {\n const stats: {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n count?: number;\n names?: Record<string, number>;\n } = {\n head: {\n id: row.id,\n stream: row.stream,\n version: row.version,\n name: row.name,\n data: row.data,\n created: row.created,\n meta: row.meta,\n } as Committed<E, keyof E>,\n };\n if (want_tail && row.t_id !== undefined && row.t_id !== null) {\n stats.tail = {\n id: row.t_id,\n stream: row.t_stream,\n version: row.t_version,\n name: row.t_name,\n data: row.t_data,\n created: row.t_created,\n meta: row.t_meta,\n } as unknown as Committed<E, keyof E>;\n }\n if (want_count) stats.count = row.agg_count;\n // `agg_names` is non-null when this row exists: heads and agg are\n // both built from the same `ef` CTE, so any stream in heads has\n // at least one matching event and `jsonb_object_agg` returns an\n // object (never null) for that group.\n if (want_names) stats.names = row.agg_names as Record<string, number>;\n out.set(row.stream, stats as StreamStats<E>);\n }\n return out;\n }\n\n /**\n * Implementation of the optional `Store.notify` hook. Bound onto\n * `this.notify` in the constructor when `config.notify === true`,\n * left detached otherwise — see {@link Config.notify}.\n *\n * Checks out a dedicated long-lived client from the pool, runs\n * `LISTEN act_commit_<schema>_<table>`, and parses each incoming\n * notification payload. The handler is invoked exactly once per\n * **remote** commit — payloads originating from this same store\n * instance (matched by the per-instance `_by` UUID) are silently\n * skipped, giving callers a clean cross-process semantic.\n *\n * Multiple subscriptions on the same store instance are not supported —\n * this method releases any prior LISTEN client before opening a new one.\n * The returned disposer cleanly UNLISTENs and releases the dedicated\n * client; pool disposal also tears the subscription down as a safety\n * net.\n *\n * The subscription is **self-healing** (#1189): the dedicated client\n * has an `error` listener that, on a connection blip (backend restart,\n * failover, network drop), tears the dead client down and re-LISTENs\n * on a fresh one with capped exponential backoff — degrading to the\n * poll path in between. A pending reconnect is cancelled by disposal,\n * so no reconnect fires after teardown.\n *\n * @param handler Called for each cross-process commit notification.\n * @returns Disposer that releases the LISTEN client.\n */\n private async _subscribe_notifications(\n handler: (notification: StoreNotification) => void\n ): Promise<NotifyDisposer> {\n // Close any prior subscription so callers don't silently double-listen.\n await this._teardown_listen();\n\n // Remember the caller's handler so the self-healing reconnect path\n // (#1189) can re-establish LISTEN on a fresh client after a\n // connection blip without the caller re-subscribing.\n this._notify_handler = handler;\n try {\n await this._open_listen(handler);\n } catch (err) {\n // Initial LISTEN failed — leave no half-set state behind so the\n // orchestrator's wireNotify sees a clean rejection.\n this._notify_handler = undefined;\n throw err;\n }\n\n return async () => {\n // No-op when this disposer is stale (a later notify() call already\n // tore the subscription down and replaced the handler).\n if (this._notify_handler !== handler) return;\n await this._teardown_listen();\n };\n }\n\n /**\n * Check out a dedicated client, attach the notification + error\n * listeners, and run `LISTEN`. Shared by the initial subscription and\n * every reconnect (#1189). On any failure before `LISTEN` succeeds the\n * client is detached and destroyed so nothing leaks — the caller\n * decides whether to propagate (initial subscribe) or reschedule\n * (reconnect).\n */\n private async _open_listen(\n handler: (notification: StoreNotification) => void\n ): Promise<void> {\n const client = await this._client(\"notify\");\n const on_notification = (msg: pg.Notification) => {\n // Channel filter: this client only `LISTEN`s on `this._channel`,\n // but pg-pool can in theory deliver buffered notifications when a\n // connection is reused — guard rather than trust.\n if (msg.channel !== this._channel) return;\n if (!msg.payload) return;\n let parsed: {\n stream?: unknown;\n events?: unknown;\n by?: unknown;\n };\n try {\n parsed = JSON.parse(msg.payload);\n } catch (err) {\n // A malformed payload is a bug somewhere upstream — log and skip\n // instead of tearing down the listener.\n logger.error(\n { err, payload: msg.payload },\n \"act_commit: malformed payload, skipping\"\n );\n return;\n }\n // Self-filter: skip notifications that originated from this same\n // store instance. This is what gives `notified` its cross-process\n // semantic — local commits already arm the drain via `do()`.\n if (parsed.by === this._by) return;\n if (typeof parsed.stream !== \"string\" || !Array.isArray(parsed.events)) {\n logger.error(\n { payload: msg.payload },\n \"act_commit: payload missing required fields, skipping\"\n );\n return;\n }\n const events: Array<{ id: number; name: string }> = [];\n for (const raw of parsed.events) {\n if (\n raw &&\n typeof raw === \"object\" &&\n typeof (raw as { id?: unknown }).id === \"number\" &&\n typeof (raw as { name?: unknown }).name === \"string\"\n ) {\n events.push({\n id: (raw as { id: number }).id,\n name: (raw as { name: string }).name,\n });\n }\n }\n if (events.length === 0) return;\n // Adapter-level robustness: a throwing handler must not tear\n // down the dedicated LISTEN client. The orchestrator wraps its\n // own `notified` emit + drain wakeup separately\n // (`Act._wire_notify`) — defense in depth, with each layer\n // protecting its own resources. Direct callers of\n // `store.notify(handler)` (tests, custom integrations) inherit\n // the adapter wrap.\n try {\n handler({ stream: parsed.stream, events });\n } catch (err) {\n logger.error(err, \"act_commit: handler threw, listener preserved\");\n }\n };\n // The dedicated LISTEN client loses node-postgres's idle-error guard\n // on checkout, so an unhandled `error` (backend restart, failover,\n // network drop) would crash the process (#1189). Handle it: log,\n // tear the dead client down, and schedule a re-LISTEN with capped\n // backoff. Between attempts the store degrades to the poll path.\n const on_error = (err: Error) => {\n logger.error(err, \"act_commit: LISTEN client errored, reconnecting\");\n this._reconnect();\n };\n client.on(\"notification\", on_notification);\n client.on(\"error\", on_error);\n try {\n await client.query(`LISTEN ${this._channel}`);\n } catch (err) {\n client.removeListener(\"notification\", on_notification);\n client.removeListener(\"error\", on_error);\n client.release(true);\n throw err;\n }\n this._listen_client = client;\n this._listen_handler = on_notification;\n this._listen_error_handler = on_error;\n // A healthy LISTEN resets the backoff so the next blip starts fresh.\n this._reconnect_attempts = 0;\n }\n\n /**\n * Self-heal the LISTEN subscription after the dedicated client emitted\n * `error` (#1189). Detaches and destroys the dead client, then\n * reconnects on a fresh one with capped exponential backoff. Bails\n * immediately if the subscription was disposed while a reconnect was\n * pending (`_notify_handler` cleared by `_teardown_listen`), so no\n * reconnect ever fires after teardown.\n */\n private _reconnect(): void {\n const handler = this._notify_handler;\n // Disposed (or torn down by a re-subscribe) while the error fired —\n // nothing to reconnect.\n if (!handler) return;\n // Detach and destroy the dead client. Its listeners are gone once\n // `_teardown_listen` runs, but the error already fired, so just drop\n // it — do not run UNLISTEN on a broken connection.\n if (this._listen_client) {\n const dead = this._listen_client;\n dead.removeListener(\"notification\", this._listen_handler!);\n dead.removeListener(\"error\", this._listen_error_handler!);\n // A node-postgres socket routinely emits `error` more than once on\n // teardown (in-flight LISTEN rejection, then the ECONNRESET/end that\n // follows). `release(true)` destroys the connection but does not\n // synchronously silence the socket, so the client must never be\n // listener-less: an unhandled second `error` re-raises as an uncaught\n // exception — the exact process crash #1189 fixed (#1231). Attach a\n // swallow listener that lives until the destroyed client is GC'd.\n dead.on(\"error\", swallow_error);\n this._listen_handler = undefined;\n this._listen_error_handler = undefined;\n this._listen_client = undefined;\n dead.release(true);\n }\n const delay = Math.min(\n NOTIFY_RECONNECT_MAX_MS,\n NOTIFY_RECONNECT_BASE_MS * 2 ** this._reconnect_attempts\n );\n this._reconnect_attempts++;\n // A second error (or the recursive `.catch` reconnect) must not leave two\n // live timers racing to re-LISTEN — cancel any pending one before we\n // reassign. `_teardown_listen` clears it on disposal.\n if (this._reconnect_timer) clearTimeout(this._reconnect_timer);\n this._reconnect_timer = setTimeout(() => {\n this._reconnect_timer = undefined;\n // Re-check: disposal may have won the race after the timer fired.\n const current = this._notify_handler;\n if (!current) return;\n this._open_listen(current).catch((err) => {\n logger.error(err, \"act_commit: LISTEN reconnect failed, retrying\");\n this._reconnect();\n });\n }, delay);\n // Don't keep the event loop alive purely for a reconnect attempt —\n // a process that has nothing else to do should still be able to exit.\n this._reconnect_timer.unref?.();\n }\n\n /**\n * Atomically truncates streams and seeds each with a snapshot or tombstone.\n * Windowed targets (`before` set) prune the prefix below the closest safe\n * `__snapshot__` instead — no seed, subscriptions untouched, no-op when no\n * snapshot qualifies.\n * @param targets - Streams to truncate with optional snapshot state and meta,\n * or a `before`/`max_id` boundary for a windowed prefix delete.\n * @returns Map keyed by stream name, each entry with `deleted` count and `committed` event.\n */\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Schema;\n meta?: EventMeta;\n before?: Date;\n max_id?: number;\n }>\n ): Promise<\n Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >\n > {\n if (!targets.length) return new Map();\n const full = targets.filter((t) => t.before === undefined);\n const windowed = targets.filter((t) => t.before !== undefined);\n const client = await this._client(\"truncate\");\n try {\n await client.query(\"BEGIN\");\n // Seeds (snapshots/tombstones) produce watermark-relevant ids, so\n // truncate takes the same visibility lock as commit — see the\n // commit path for the id-order-vs-visibility-order rationale.\n await client.query(\"SELECT pg_advisory_xact_lock(hashtext($1))\", [\n this._fqt,\n ]);\n const result = new Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >();\n // Subscriptions are deliberately untouched, for restart *and* retire\n // targets alike. A tombstoned stream's subscription is inert — the\n // framework refuses new commits on it, so no scan can raise its work\n // mark and `at < correlated_at` never becomes true again. Removing it\n // here bought nothing and cost a coupling: it is the one step that\n // spans the event log and the subscription table, which is what forced\n // a store whose halves live apart into a distributed transaction\n // (#1527). Reaping the inert rows is maintenance, and `seed()` does it.\n for (const { stream, snapshot, meta } of full) {\n const { rowCount } = await client.query(\n `DELETE FROM ${this._fqt} WHERE stream = $1`,\n [stream]\n );\n const name = snapshot !== undefined ? SNAP_EVENT : TOMBSTONE_EVENT;\n const { rows } = await client.query(\n `INSERT INTO ${this._fqt}(name, data, stream, version, created, meta)\n VALUES($1, $2, $3, 0, now(), $4) RETURNING *`,\n [\n name,\n snapshot ?? {},\n stream,\n meta ?? { correlation: \"\", causation: {} },\n ]\n );\n result.set(stream, {\n deleted: rowCount ?? 0,\n committed: rows[0] as Committed<Schemas, keyof Schemas>,\n });\n }\n for (const { stream, before, max_id } of windowed) {\n // Closest safe boundary: latest snapshot older than the cutoff and\n // at/below the consumer watermark cap. No qualifying snapshot →\n // no-op, stream absent from the result.\n const { rows } = await client.query(\n `SELECT id, stream, version, name, data, created, meta\n FROM ${this._fqt}\n WHERE stream = $1 AND name = $2 AND created < $3\n AND ($4::int IS NULL OR id <= $4)\n ORDER BY id DESC LIMIT 1`,\n [stream, SNAP_EVENT, before, max_id ?? null]\n );\n if (!rows.length) continue;\n const boundary = rows[0] as Committed<Schemas, keyof Schemas>;\n const { rowCount } = await client.query(\n `DELETE FROM ${this._fqt} WHERE stream = $1 AND id < $2`,\n [stream, boundary.id]\n );\n result.set(stream, {\n deleted: rowCount ?? 0,\n committed: boundary,\n before,\n });\n }\n await client.query(\"COMMIT\");\n return result;\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Atomically wipe-and-rebuild the store inside a single\n * `BEGIN`/`COMMIT` transaction.\n *\n * On any throw inside the driver the transaction rolls back and the\n * store ends byte-for-byte unchanged. `TRUNCATE ... RESTART\n * IDENTITY CASCADE` wipes events + resets the serial sequence to 1;\n * the streams table is cleared in the same statement via\n * `CASCADE`-like `DELETE`. Events are inserted one at a time with\n * explicit columns (skipping `id`) so the serial assigns dense ids\n * from 1. `created` is preserved verbatim from the source.\n */\n async restore(\n driver: (\n callback: (event: Committed<Schemas, keyof Schemas>) => Promise<number>\n ) => Promise<void>\n ): Promise<void> {\n const client = await this._client(\"restore\");\n try {\n await client.query(\"BEGIN\");\n // RESTART IDENTITY resets the id sequence; CASCADE handles any\n // future FK refs (none today, but cheap insurance).\n await client.query(\n `TRUNCATE TABLE ${this._fqt} RESTART IDENTITY CASCADE`\n );\n await client.query(`TRUNCATE TABLE ${this._fqs}`);\n await driver(async (event) => {\n // Restore mirrors commit: encrypt the pii payload when\n // encryption is configured. The source iterator yields\n // plaintext events (restore is the rebuild path — the driver\n // already presents data in the framework's native shape),\n // so this is symmetric with the commit-path call above —\n // including the JSON.stringify wrapper that turns the bare\n // base64 string into a jsonb-acceptable JSON string literal.\n const pii_for_write =\n this._resolve_pii_key && event.pii != null\n ? JSON.stringify(await encrypt(event.pii, this._resolve_pii_key))\n : (event.pii ?? null);\n const { rows } = await client.query<{ id: number }>(\n `INSERT INTO ${this._fqt}(name, data, pii, stream, version, created, meta)\n VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING id`,\n [\n event.name,\n event.data,\n pii_for_write,\n event.stream,\n event.version,\n event.created,\n event.meta,\n ]\n );\n return rows[0]!.id;\n });\n await client.query(\"COMMIT\");\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Wipe the sensitive-data payload for every event on the stream — the\n * physical-erasure side of the sensitive-data epic (#566). Sets\n * `events.pii` to `NULL` for the stream's events; `events.data` and\n * the rest of the row are never touched.\n *\n * Row-level locks (no table lock), bounded by events-per-stream.\n * Idempotent — a second call on an already-wiped stream returns `0`.\n *\n * Disk reclamation is autovacuum-driven; for strict-deletion\n * jurisdictions the production checklist documents `VACUUM FULL` as\n * the operator step.\n *\n * @param stream Target stream\n * @returns Count of events whose `pii` was set to `NULL`\n */\n async forget_pii(stream: string): Promise<number> {\n const r = await this._pool.query(\n `UPDATE ${this._fqt} SET pii = NULL WHERE stream = $1 AND pii IS NOT NULL`,\n [stream]\n );\n return r.rowCount ?? 0;\n }\n}\n"],"mappings":"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","names":["lagging","lane","rowCount"]}
|