@rotorsoft/act-pg 0.24.0 → 0.25.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/index.cjs CHANGED
@@ -206,13 +206,18 @@ var PostgresStore = class {
206
206
  error text,
207
207
  leased_by text,
208
208
  leased_until timestamptz,
209
- priority int NOT NULL DEFAULT 0
209
+ priority int NOT NULL DEFAULT 0,
210
+ lane text NOT NULL DEFAULT 'default'
210
211
  ) TABLESPACE pg_default;`
211
212
  );
212
213
  await client.query(
213
214
  `ALTER TABLE ${this._fqs}
214
215
  ADD COLUMN IF NOT EXISTS priority int NOT NULL DEFAULT 0;`
215
216
  );
217
+ await client.query(
218
+ `ALTER TABLE ${this._fqs}
219
+ ADD COLUMN IF NOT EXISTS lane text NOT NULL DEFAULT 'default';`
220
+ );
216
221
  await client.query(
217
222
  `DROP INDEX IF EXISTS "${this.config.schema}"."${this.config.table}_streams_fetch_ix"`
218
223
  );
@@ -220,6 +225,10 @@ var PostgresStore = class {
220
225
  `CREATE INDEX IF NOT EXISTS "${this.config.table}_streams_claim_ix"
221
226
  ON ${this._fqs} (blocked, priority DESC, at);`
222
227
  );
228
+ await client.query(
229
+ `CREATE INDEX IF NOT EXISTS "${this.config.table}_streams_lane_ix"
230
+ ON ${this._fqs} (lane);`
231
+ );
223
232
  await client.query("COMMIT");
224
233
  logger.info(
225
234
  `Seeded schema "${this.config.schema}" with table "${this.config.table}"`
@@ -417,17 +426,20 @@ var PostgresStore = class {
417
426
  * @param millis - Lease duration in milliseconds
418
427
  * @returns Leased streams with metadata
419
428
  */
420
- async claim(lagging, leading, by, millis) {
429
+ async claim(lagging, leading, by, millis, lane) {
421
430
  const client = await this._pool.connect();
422
431
  try {
423
432
  await client.query("BEGIN");
433
+ const laneClause = lane !== void 0 ? `AND s.lane = $5` : "";
434
+ const params = lane !== void 0 ? [lagging, leading, by, millis, lane] : [lagging, leading, by, millis];
424
435
  const { rows } = await client.query(
425
436
  `
426
437
  WITH
427
438
  available AS (
428
- SELECT stream, source, at, priority
439
+ SELECT stream, source, at, priority, lane
429
440
  FROM ${this._fqs} s
430
441
  WHERE blocked = false
442
+ ${laneClause}
431
443
  AND (leased_by IS NULL OR leased_until <= NOW())
432
444
  AND (s.at < 0 OR EXISTS (
433
445
  SELECT 1 FROM ${this._fqt} e
@@ -443,19 +455,19 @@ var PostgresStore = class {
443
455
  -- ORDER BY collapses to plain at ASC so existing workloads
444
456
  -- see no behavior change.
445
457
  lag AS (
446
- SELECT stream, source, at, TRUE AS lagging
458
+ SELECT stream, source, at, lane, TRUE AS lagging
447
459
  FROM available
448
460
  ORDER BY priority DESC, at ASC
449
461
  LIMIT $1
450
462
  ),
451
463
  lead AS (
452
- SELECT stream, source, at, FALSE AS lagging
464
+ SELECT stream, source, at, lane, FALSE AS lagging
453
465
  FROM available
454
466
  ORDER BY at DESC
455
467
  LIMIT $2
456
468
  ),
457
469
  combined AS (
458
- SELECT DISTINCT ON (stream) stream, source, at, lagging
470
+ SELECT DISTINCT ON (stream) stream, source, at, lane, lagging
459
471
  FROM (SELECT * FROM lag UNION ALL SELECT * FROM lead) t
460
472
  ORDER BY stream, at
461
473
  )
@@ -466,18 +478,19 @@ var PostgresStore = class {
466
478
  retry = s.retry + 1
467
479
  FROM combined c
468
480
  WHERE s.stream = c.stream
469
- RETURNING s.stream, s.source, s.at, s.retry, c.lagging
481
+ RETURNING s.stream, s.source, s.at, s.retry, c.lagging, s.lane
470
482
  `,
471
- [lagging, leading, by, millis]
483
+ params
472
484
  );
473
485
  await client.query("COMMIT");
474
- return rows.map(({ stream, source, at, retry, lagging: lagging2 }) => ({
486
+ return rows.map(({ stream, source, at, retry, lagging: lagging2, lane: lane2 }) => ({
475
487
  stream,
476
488
  source: source ?? void 0,
477
489
  at,
478
490
  by,
479
491
  retry,
480
- lagging: lagging2
492
+ lagging: lagging2,
493
+ lane: lane2
481
494
  }));
482
495
  } catch (error) {
483
496
  await client.query("ROLLBACK").catch(() => {
@@ -503,10 +516,11 @@ var PostgresStore = class {
503
516
  if (streams.length) {
504
517
  const { rowCount: inserted } = await client.query(
505
518
  `
506
- INSERT INTO ${this._fqs} (stream, source, priority)
519
+ INSERT INTO ${this._fqs} (stream, source, priority, lane)
507
520
  SELECT s->>'stream',
508
521
  s->>'source',
509
- COALESCE((s->>'priority')::int, 0)
522
+ COALESCE((s->>'priority')::int, 0),
523
+ COALESCE(s->>'lane', 'default')
510
524
  FROM jsonb_array_elements($1::jsonb) AS s
511
525
  ON CONFLICT (stream) DO NOTHING
512
526
  `,
@@ -523,6 +537,16 @@ var PostgresStore = class {
523
537
  `,
524
538
  [JSON.stringify(streams)]
525
539
  );
540
+ await client.query(
541
+ `
542
+ UPDATE ${this._fqs} t
543
+ SET lane = COALESCE(s->>'lane', 'default')
544
+ FROM jsonb_array_elements($1::jsonb) AS s
545
+ WHERE t.stream = s->>'stream'
546
+ AND t.lane <> COALESCE(s->>'lane', 'default')
547
+ `,
548
+ [JSON.stringify(streams)]
549
+ );
526
550
  }
527
551
  const { rows } = await client.query(
528
552
  `SELECT COALESCE(MAX(at), -1) AS max FROM ${this._fqs}`
@@ -562,7 +586,7 @@ var PostgresStore = class {
562
586
  leased_until = NULL
563
587
  FROM input i
564
588
  WHERE s.stream = i.stream AND s.leased_by = i.by
565
- RETURNING s.stream, s.source, s.at, i.by, s.retry, i.lagging
589
+ RETURNING s.stream, s.source, s.at, i.by, s.retry, i.lagging, s.lane
566
590
  `,
567
591
  [JSON.stringify(leases)]
568
592
  );
@@ -573,7 +597,8 @@ var PostgresStore = class {
573
597
  at: row.at,
574
598
  by: row.by,
575
599
  retry: row.retry,
576
- lagging: row.lagging
600
+ lagging: row.lagging,
601
+ lane: row.lane
577
602
  }));
578
603
  } catch (error) {
579
604
  await client.query("ROLLBACK").catch(() => {
@@ -603,7 +628,7 @@ var PostgresStore = class {
603
628
  SET blocked = true, error = i.error
604
629
  FROM input i
605
630
  WHERE s.stream = i.stream AND s.leased_by = i.by AND s.blocked = false
606
- RETURNING s.stream, s.source, s.at, i.by, s.retry, s.error, i.lagging
631
+ RETURNING s.stream, s.source, s.at, i.by, s.retry, s.error, i.lagging, s.lane
607
632
  `,
608
633
  [JSON.stringify(leases)]
609
634
  );
@@ -615,7 +640,8 @@ var PostgresStore = class {
615
640
  by: row.by,
616
641
  retry: row.retry,
617
642
  lagging: row.lagging,
618
- error: row.error
643
+ error: row.error,
644
+ lane: row.lane
619
645
  }));
620
646
  } catch (error) {
621
647
  await client.query("ROLLBACK").catch(() => {
@@ -658,6 +684,10 @@ var PostgresStore = class {
658
684
  values.push(filter.blocked);
659
685
  conditions.push(`blocked = $${start + values.length - 1}`);
660
686
  }
687
+ if (filter.lane !== void 0) {
688
+ values.push(filter.lane);
689
+ conditions.push(`lane = $${start + values.length - 1}`);
690
+ }
661
691
  return {
662
692
  clause: conditions.length ? conditions.join(" AND ") : "TRUE",
663
693
  values
@@ -770,11 +800,15 @@ var PostgresStore = class {
770
800
  values.push(query.blocked);
771
801
  conditions.push(`blocked = $${values.length}`);
772
802
  }
803
+ if (query?.lane !== void 0) {
804
+ values.push(query.lane);
805
+ conditions.push(`lane = $${values.length}`);
806
+ }
773
807
  if (query?.after !== void 0) {
774
808
  values.push(query.after);
775
809
  conditions.push(`stream > $${values.length}`);
776
810
  }
777
- let sql = `SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority FROM ${this._fqs}`;
811
+ let sql = `SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane FROM ${this._fqs}`;
778
812
  if (conditions.length) sql += " WHERE " + conditions.join(" AND ");
779
813
  values.push(limit);
780
814
  sql += ` ORDER BY stream LIMIT $${values.length}`;
@@ -797,7 +831,8 @@ var PostgresStore = class {
797
831
  error: row.error ?? "",
798
832
  priority: row.priority,
799
833
  leased_by: row.leased_by ?? void 0,
800
- leased_until: row.leased_until ?? void 0
834
+ leased_until: row.leased_until ?? void 0,
835
+ lane: row.lane
801
836
  });
802
837
  count++;
803
838
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/postgres-store.ts","../src/utils.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-pg\n * Main entry point for the Act-PG framework. Re-exports all core APIs\n */\nexport * from \"./postgres-store.js\";\n","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} from \"@rotorsoft/act\";\nimport {\n ConcurrencyError,\n log,\n SNAP_EVENT,\n TOMBSTONE_EVENT,\n} from \"@rotorsoft/act\";\nimport pg from \"pg\";\nimport { dateReviver } from \"./utils.js\";\n\nconst logger: Logger = log();\n\nconst { Pool, types } = pg;\ntypes.setTypeParser(types.builtins.JSONB, (val) =>\n JSON.parse(val, dateReviver)\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 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// 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\nfunction notifyChannel(schema: string, table: string): string {\n return `${NOTIFY_CHANNEL_PREFIX}_${schema}_${table}`;\n}\nfunction assertSafeIdentifier(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};\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\n * // Pool is created automatically with default settings\n * // For custom pool config, use environment variables:\n * // PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD\n * // PGMAXCONNECTIONS, PGIDLETIMEOUT, etc.\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 * });\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 /**\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 _listenClient: 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 _listenHandler: ((msg: pg.Notification) => void) | undefined;\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 * 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 assertSafeIdentifier(this.config.schema, \"schema\");\n assertSafeIdentifier(this.config.table, \"table\");\n const { schema: _, table: __, ...poolConfig } = this.config;\n this._pool = new Pool(poolConfig);\n this._fqt = `\"${this.config.schema}\".\"${this.config.table}\"`;\n this._fqs = `\"${this.config.schema}\".\"${this.config.table}_streams\"`;\n this._channel = notifyChannel(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._subscribeNotifications.bind(this);\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._teardownListen();\n await this._pool.end();\n }\n\n /**\n * Tear down the active LISTEN subscription if any: detach the\n * notification listener, run UNLISTEN, and destroy the dedicated\n * client (do not return it to the pool — its listener is removed but\n * destroying belt-and-braces guards against any future change in\n * pg-pool semantics that could re-issue a half-clean client).\n */\n private async _teardownListen() {\n if (!this._listenClient) return;\n // _listenHandler is set in lockstep with _listenClient in notify(),\n // so if the client is present, the handler is too.\n this._listenClient.removeListener(\"notification\", this._listenHandler!);\n this._listenHandler = undefined;\n try {\n await this._listenClient.query(`UNLISTEN ${this._channel}`);\n } catch {\n // best-effort — pool end (or destroy) tears the connection down\n }\n this._listenClient.release(true);\n this._listenClient = 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._pool.connect();\n\n try {\n await client.query(\"BEGIN\");\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 varchar(100) COLLATE pg_catalog.\"default\" NOT NULL,\n data jsonb,\n stream varchar(100) COLLATE pg_catalog.\"default\" NOT NULL,\n version int NOT NULL,\n created timestamptz NOT NULL DEFAULT now(),\n meta jsonb\n ) TABLESPACE pg_default;`\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\n // Streams table\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqs} (\n stream varchar(100) COLLATE pg_catalog.\"default\" PRIMARY KEY,\n source varchar(100) COLLATE pg_catalog.\"default\",\n at int NOT NULL DEFAULT -1,\n retry smallint NOT NULL DEFAULT 0,\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 ) TABLESPACE pg_default;`\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\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\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._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 {\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?.length) {\n values.push(names);\n conditions.push(`name = ANY($${values.length})`);\n }\n if (before) {\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) callback(row);\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 const client = await this._pool.connect();\n let version = -1;\n try {\n await client.query(\"BEGIN\");\n\n const last = await client.query<Committed<E, keyof E>>(\n `SELECT version\n FROM ${this._fqt}\n WHERE stream=$1 ORDER BY version DESC LIMIT 1`,\n [stream]\n );\n version = last.rowCount ? last.rows[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 const committed: Committed<E, keyof E>[] = [];\n for (const { name, data } of msgs) {\n version++;\n const sql = `\n INSERT INTO ${this._fqt}(name, data, stream, version, meta)\n VALUES($1, $2, $3, $4, $5) RETURNING *`;\n const vals = [name, data, stream, version, meta];\n try {\n const { rows } = await client.query<Committed<E, keyof E>>(sql, vals);\n committed.push(rows.at(0)!);\n } catch (error) {\n // PG unique-violation on (stream, version) — a concurrent commit\n // beat us between the version SELECT and this INSERT. Surface as\n // ConcurrencyError so callers retry on the framework signal\n // instead of an adapter-specific error.\n if ((error as { code?: string })?.code === PG_UNIQUE_VIOLATION) {\n throw new ConcurrencyError(\n stream,\n version - 1,\n msgs as unknown as Message<Schemas, string>[],\n expectedVersion ?? -1\n );\n }\n throw error;\n }\n }\n\n // One NOTIFY per commit transaction, payload carries the full event\n // batch so listeners reason about atomic groups (matches reaction\n // semantics in the rest of the framework). `by` lets other\n // PostgresStore instances self-filter their own writes — see\n // `_subscribeNotifications()`. PG NOTIFY payloads cap at 8000\n // bytes; for typical commits (1–10 events) this is comfortably\n // under, and the polling fallback path handles the rare overflow\n // case correctly. Skipped entirely when `config.notify === false`\n // (the default) so single-instance deployments pay zero\n // per-write overhead.\n if (this.config.notify) {\n const payload = JSON.stringify({\n stream,\n events: committed.map((c) => ({ id: c.id, name: c.name as string })),\n by: this._by,\n });\n await client.query(`SELECT pg_notify($1, $2)`, [\n this._channel,\n payload,\n ]);\n }\n\n await client.query(\"COMMIT\");\n return committed;\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw error;\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 ): Promise<Lease[]> {\n const client = await this._pool.connect();\n try {\n await client.query(\"BEGIN\");\n const { rows } = await client.query<{\n stream: string;\n source: string | null;\n at: number;\n retry: number;\n lagging: boolean;\n }>(\n `\n WITH\n available AS (\n SELECT stream, source, at, priority\n FROM ${this._fqs} s\n WHERE blocked = false\n AND (leased_by IS NULL OR leased_until <= NOW())\n AND (s.at < 0 OR EXISTS (\n SELECT 1 FROM ${this._fqt} e\n WHERE e.id > s.at\n AND e.name <> '${SNAP_EVENT}'\n AND (s.source IS NULL OR e.stream = COALESCE(s.source, s.stream))\n LIMIT 1\n ))\n FOR UPDATE SKIP LOCKED\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 lag AS (\n SELECT stream, source, at, TRUE AS lagging\n FROM available\n ORDER BY priority DESC, at ASC\n LIMIT $1\n ),\n lead AS (\n SELECT stream, source, at, 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, lagging\n FROM (SELECT * FROM lag UNION ALL SELECT * FROM lead) t\n ORDER BY stream, at\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 RETURNING s.stream, s.source, s.at, s.retry, c.lagging\n `,\n [lagging, leading, by, millis]\n );\n await client.query(\"COMMIT\");\n\n return rows.map(({ stream, source, at, retry, lagging }) => ({\n stream,\n source: source ?? undefined,\n at,\n by,\n retry,\n lagging,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n logger.error(error);\n return [];\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: Array<{ stream: string; source?: string; priority?: number }>\n ): Promise<{ subscribed: number; watermark: number }> {\n const client = await this._pool.connect();\n try {\n await client.query(\"BEGIN\");\n let subscribed = 0;\n if (streams.length) {\n // Two statements to keep `subscribed` meaning \"newly\n // registered streams\" (not \"rows touched\"):\n // 1. INSERT ... ON CONFLICT DO NOTHING — rowCount = inserts.\n // 2. UPDATE priority on the existing rows whose new value is\n // higher than the stored one (ACT-102: keep the max so the\n // highest-priority registered reaction wins). Operator\n // overrides (which may *decrease*) go through `prioritize()`.\n const { rowCount: inserted } = await client.query(\n `\n INSERT INTO ${this._fqs} (stream, source, priority)\n SELECT s->>'stream',\n s->>'source',\n COALESCE((s->>'priority')::int, 0)\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 await client.query(\n `\n UPDATE ${this._fqs} t\n SET priority = COALESCE((s->>'priority')::int, 0)\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 `,\n [JSON.stringify(streams)]\n );\n }\n const { rows } = await client.query<{ max: number | null }>(\n `SELECT COALESCE(MAX(at), -1) AS max FROM ${this._fqs}`\n );\n await client.query(\"COMMIT\");\n return { subscribed, watermark: rows[0]?.max ?? -1 };\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n logger.error(error);\n return { subscribed: 0, watermark: -1 };\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._pool.connect();\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 }>(\n `\n WITH input AS (\n SELECT * FROM jsonb_to_recordset($1::jsonb)\n AS x(stream text, by text, at int, lagging boolean)\n )\n UPDATE ${this._fqs} AS s\n SET\n at = i.at,\n retry = -1,\n leased_by = NULL,\n leased_until = NULL\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\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 }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n logger.error(error);\n return [];\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._pool.connect();\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 }>(\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\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\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 }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n logger.error(error);\n return [];\n } finally {\n client.release();\n }\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 _filterClause(\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 return {\n clause: conditions.length ? conditions.join(\" AND \") : \"TRUE\",\n values,\n };\n }\n\n async reset(input: string[] | StreamFilter): Promise<number> {\n const setClause = `SET at = -1, retry = 0, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause} WHERE stream = ANY($1)`,\n [input]\n );\n return rowCount ?? 0;\n }\n const { clause, values } = this._filterClause(input, 1);\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause} 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 setClause = `SET retry = -1, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause}\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._filterClause(\n { ...input, blocked: true },\n 1\n );\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause} 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._filterClause(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?.blocked !== undefined) {\n values.push(query.blocked);\n conditions.push(`blocked = $${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 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._pool.connect();\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 }>(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 });\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 wantTail = options?.tail ?? false;\n const wantCount = options?.count ?? false;\n const wantNames = options?.names ?? false;\n const before = options?.before;\n const fullScan = wantCount || wantNames;\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\n const fromClause = `${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 whereClause = `WHERE ${where.length ? where.join(\" AND \") : \"TRUE\"}`;\n\n return fullScan\n ? this._queryStatsFullScan<E>(\n fromClause,\n whereClause,\n params,\n wantTail,\n wantCount,\n wantNames\n )\n : this._queryStatsHeadsOnly<E>(fromClause, whereClause, params, wantTail);\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 _queryStatsHeadsOnly<E extends Schemas>(\n fromClause: string,\n whereClause: string,\n params: unknown[],\n wantTail: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;\n const headSql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version DESC`;\n const tailSql = wantTail\n ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version ASC`\n : null;\n\n const [headRes, tailRes] = await Promise.all([\n this._pool.query<Committed<E, keyof E>>(headSql, params),\n tailSql\n ? this._pool.query<Committed<E, keyof E>>(tailSql, 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 _queryStatsFullScan<E extends Schemas>(\n fromClause: string,\n whereClause: string,\n params: unknown[],\n wantTail: boolean,\n wantCount: boolean,\n wantNames: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const tailCte = wantTail\n ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)`\n : \"\";\n const tailJoin = wantTail ? `LEFT JOIN tails t ON t.stream = h.stream` : \"\";\n const tailCols = wantTail\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 ${fromClause}\n ${whereClause}\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 ${tailCte}\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 ${tailCols}\n FROM heads h\n LEFT JOIN agg a ON a.stream = h.stream\n ${tailJoin}\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 (wantTail && 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 (wantCount) 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 (wantNames) 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 * @param handler Called for each cross-process commit notification.\n * @returns Disposer that releases the LISTEN client.\n */\n private async _subscribeNotifications(\n handler: (notification: StoreNotification) => void\n ): Promise<NotifyDisposer> {\n // Close any prior subscription so callers don't silently double-listen.\n await this._teardownListen();\n\n const client = await this._pool.connect();\n const onNotification = (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._wireNotify`) — 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 client.on(\"notification\", onNotification);\n try {\n await client.query(`LISTEN ${this._channel}`);\n } catch (err) {\n client.removeListener(\"notification\", onNotification);\n client.release(true);\n throw err;\n }\n this._listenClient = client;\n this._listenHandler = onNotification;\n\n return async () => {\n // No-op when this disposer is stale (a later notify() call already\n // tore the subscription down).\n if (this._listenClient !== client) return;\n await this._teardownListen();\n };\n }\n\n /**\n * Atomically truncates streams and seeds each with a snapshot or tombstone.\n * @param targets - Streams to truncate with optional snapshot state and meta.\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 }>\n ): Promise<\n Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >\n > {\n if (!targets.length) return new Map();\n const streams = targets.map((t) => t.stream);\n const client = await this._pool.connect();\n try {\n await client.query(\"BEGIN\");\n await client.query(`DELETE FROM ${this._fqs} WHERE stream = ANY($1)`, [\n streams,\n ]);\n const result = new Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >();\n for (const { stream, snapshot, meta } of targets) {\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 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 * @module act-pg\n * Date reviver for JSON.parse to automatically convert ISO 8601 date strings to Date objects.\n *\n * Recognizes the following formats:\n * - YYYY-MM-DDTHH:MM:SS.sssZ\n * - YYYY-MM-DDTHH:MM:SS.sss+HH:MM\n * - YYYY-MM-DDTHH:MM:SS.sss-HH:MM\n *\n * @param key The key being parsed\n * @param value The value being parsed\n * @returns A Date object if the value matches ISO 8601, otherwise the original value\n *\n * @example\n * const obj = JSON.parse(jsonString, dateReviver);\n */\nconst ISO_8601 =\n /^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\\.\\d+)?(Z|[+-][0-2][0-9]:[0-5][0-9])?$/;\nexport const dateReviver = (_key: string, value: string): string | Date => {\n if (typeof value === \"string\" && ISO_8601.test(value)) {\n return new Date(value);\n }\n return value;\n};\n"],"mappings":"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","names":["pg","lagging","rowCount"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/postgres-store.ts","../src/utils.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-pg\n * Main entry point for the Act-PG framework. Re-exports all core APIs\n */\nexport * from \"./postgres-store.js\";\n","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} from \"@rotorsoft/act\";\nimport {\n ConcurrencyError,\n log,\n SNAP_EVENT,\n TOMBSTONE_EVENT,\n} from \"@rotorsoft/act\";\nimport pg from \"pg\";\nimport { dateReviver } from \"./utils.js\";\n\nconst logger: Logger = log();\n\nconst { Pool, types } = pg;\ntypes.setTypeParser(types.builtins.JSONB, (val) =>\n JSON.parse(val, dateReviver)\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 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// 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\nfunction notifyChannel(schema: string, table: string): string {\n return `${NOTIFY_CHANNEL_PREFIX}_${schema}_${table}`;\n}\nfunction assertSafeIdentifier(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};\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\n * // Pool is created automatically with default settings\n * // For custom pool config, use environment variables:\n * // PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD\n * // PGMAXCONNECTIONS, PGIDLETIMEOUT, etc.\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 * });\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 /**\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 _listenClient: 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 _listenHandler: ((msg: pg.Notification) => void) | undefined;\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 * 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 assertSafeIdentifier(this.config.schema, \"schema\");\n assertSafeIdentifier(this.config.table, \"table\");\n const { schema: _, table: __, ...poolConfig } = this.config;\n this._pool = new Pool(poolConfig);\n this._fqt = `\"${this.config.schema}\".\"${this.config.table}\"`;\n this._fqs = `\"${this.config.schema}\".\"${this.config.table}_streams\"`;\n this._channel = notifyChannel(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._subscribeNotifications.bind(this);\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._teardownListen();\n await this._pool.end();\n }\n\n /**\n * Tear down the active LISTEN subscription if any: detach the\n * notification listener, run UNLISTEN, and destroy the dedicated\n * client (do not return it to the pool — its listener is removed but\n * destroying belt-and-braces guards against any future change in\n * pg-pool semantics that could re-issue a half-clean client).\n */\n private async _teardownListen() {\n if (!this._listenClient) return;\n // _listenHandler is set in lockstep with _listenClient in notify(),\n // so if the client is present, the handler is too.\n this._listenClient.removeListener(\"notification\", this._listenHandler!);\n this._listenHandler = undefined;\n try {\n await this._listenClient.query(`UNLISTEN ${this._channel}`);\n } catch {\n // best-effort — pool end (or destroy) tears the connection down\n }\n this._listenClient.release(true);\n this._listenClient = 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._pool.connect();\n\n try {\n await client.query(\"BEGIN\");\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 varchar(100) COLLATE pg_catalog.\"default\" NOT NULL,\n data jsonb,\n stream varchar(100) COLLATE pg_catalog.\"default\" NOT NULL,\n version int NOT NULL,\n created timestamptz NOT NULL DEFAULT now(),\n meta jsonb\n ) TABLESPACE pg_default;`\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\n // Streams table\n await client.query(\n `CREATE TABLE IF NOT EXISTS ${this._fqs} (\n stream varchar(100) COLLATE pg_catalog.\"default\" PRIMARY KEY,\n source varchar(100) COLLATE pg_catalog.\"default\",\n at int NOT NULL DEFAULT -1,\n retry smallint NOT NULL DEFAULT 0,\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 ) TABLESPACE pg_default;`\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\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\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._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 {\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?.length) {\n values.push(names);\n conditions.push(`name = ANY($${values.length})`);\n }\n if (before) {\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) callback(row);\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 const client = await this._pool.connect();\n let version = -1;\n try {\n await client.query(\"BEGIN\");\n\n const last = await client.query<Committed<E, keyof E>>(\n `SELECT version\n FROM ${this._fqt}\n WHERE stream=$1 ORDER BY version DESC LIMIT 1`,\n [stream]\n );\n version = last.rowCount ? last.rows[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 const committed: Committed<E, keyof E>[] = [];\n for (const { name, data } of msgs) {\n version++;\n const sql = `\n INSERT INTO ${this._fqt}(name, data, stream, version, meta)\n VALUES($1, $2, $3, $4, $5) RETURNING *`;\n const vals = [name, data, stream, version, meta];\n try {\n const { rows } = await client.query<Committed<E, keyof E>>(sql, vals);\n committed.push(rows.at(0)!);\n } catch (error) {\n // PG unique-violation on (stream, version) — a concurrent commit\n // beat us between the version SELECT and this INSERT. Surface as\n // ConcurrencyError so callers retry on the framework signal\n // instead of an adapter-specific error.\n if ((error as { code?: string })?.code === PG_UNIQUE_VIOLATION) {\n throw new ConcurrencyError(\n stream,\n version - 1,\n msgs as unknown as Message<Schemas, string>[],\n expectedVersion ?? -1\n );\n }\n throw error;\n }\n }\n\n // One NOTIFY per commit transaction, payload carries the full event\n // batch so listeners reason about atomic groups (matches reaction\n // semantics in the rest of the framework). `by` lets other\n // PostgresStore instances self-filter their own writes — see\n // `_subscribeNotifications()`. PG NOTIFY payloads cap at 8000\n // bytes; for typical commits (1–10 events) this is comfortably\n // under, and the polling fallback path handles the rare overflow\n // case correctly. Skipped entirely when `config.notify === false`\n // (the default) so single-instance deployments pay zero\n // per-write overhead.\n if (this.config.notify) {\n const payload = JSON.stringify({\n stream,\n events: committed.map((c) => ({ id: c.id, name: c.name as string })),\n by: this._by,\n });\n await client.query(`SELECT pg_notify($1, $2)`, [\n this._channel,\n payload,\n ]);\n }\n\n await client.query(\"COMMIT\");\n return committed;\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n throw error;\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._pool.connect();\n try {\n await client.query(\"BEGIN\");\n const laneClause = lane !== undefined ? `AND s.lane = $5` : \"\";\n const params: unknown[] =\n lane !== undefined\n ? [lagging, leading, by, millis, lane]\n : [lagging, leading, by, millis];\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 available AS (\n SELECT stream, source, at, priority, lane\n FROM ${this._fqs} s\n WHERE blocked = false\n ${laneClause}\n AND (leased_by IS NULL OR leased_until <= NOW())\n AND (s.at < 0 OR EXISTS (\n SELECT 1 FROM ${this._fqt} e\n WHERE e.id > s.at\n AND e.name <> '${SNAP_EVENT}'\n AND (s.source IS NULL OR e.stream = COALESCE(s.source, s.stream))\n LIMIT 1\n ))\n FOR UPDATE SKIP LOCKED\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 lag AS (\n SELECT stream, source, at, lane, TRUE AS lagging\n FROM available\n ORDER BY priority DESC, at ASC\n LIMIT $1\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 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 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 logger.error(error);\n return [];\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: Array<{\n stream: string;\n source?: string;\n priority?: number;\n lane?: string;\n }>\n ): Promise<{ subscribed: number; watermark: number }> {\n const client = await this._pool.connect();\n try {\n await client.query(\"BEGIN\");\n let subscribed = 0;\n if (streams.length) {\n // Three statements to keep `subscribed` meaning \"newly\n // registered streams\" (not \"rows touched\"):\n // 1. INSERT ... ON CONFLICT DO NOTHING — rowCount = inserts.\n // 2. UPDATE priority on the existing rows whose new value is\n // higher than the stored one (ACT-102: keep the max so the\n // highest-priority registered reaction wins). Operator\n // overrides (which may *decrease*) go through `prioritize()`.\n // 3. UPDATE lane unconditionally — current subscribe wins (ACT-1103).\n const { rowCount: inserted } = await client.query(\n `\n INSERT INTO ${this._fqs} (stream, source, priority, lane)\n SELECT s->>'stream',\n s->>'source',\n COALESCE((s->>'priority')::int, 0),\n COALESCE(s->>'lane', 'default')\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 await client.query(\n `\n UPDATE ${this._fqs} t\n SET priority = COALESCE((s->>'priority')::int, 0)\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 `,\n [JSON.stringify(streams)]\n );\n await client.query(\n `\n UPDATE ${this._fqs} t\n SET lane = COALESCE(s->>'lane', 'default')\n FROM jsonb_array_elements($1::jsonb) AS s\n WHERE t.stream = s->>'stream'\n AND t.lane <> COALESCE(s->>'lane', 'default')\n `,\n [JSON.stringify(streams)]\n );\n }\n const { rows } = await client.query<{ max: number | null }>(\n `SELECT COALESCE(MAX(at), -1) AS max FROM ${this._fqs}`\n );\n await client.query(\"COMMIT\");\n return { subscribed, watermark: rows[0]?.max ?? -1 };\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n logger.error(error);\n return { subscribed: 0, watermark: -1 };\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._pool.connect();\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 lane: string;\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)\n )\n UPDATE ${this._fqs} AS s\n SET\n at = i.at,\n retry = -1,\n leased_by = NULL,\n leased_until = NULL\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\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 lane: row.lane,\n }));\n } catch (error) {\n await client.query(\"ROLLBACK\").catch(() => {});\n logger.error(error);\n return [];\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._pool.connect();\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\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 logger.error(error);\n return [];\n } finally {\n client.release();\n }\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 _filterClause(\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 setClause = `SET at = -1, retry = 0, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause} WHERE stream = ANY($1)`,\n [input]\n );\n return rowCount ?? 0;\n }\n const { clause, values } = this._filterClause(input, 1);\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause} 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 setClause = `SET retry = -1, blocked = false, error = NULL,\n leased_by = NULL, leased_until = NULL`;\n if (Array.isArray(input)) {\n if (!input.length) return 0;\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause}\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._filterClause(\n { ...input, blocked: true },\n 1\n );\n const { rowCount } = await this._pool.query(\n `UPDATE ${this._fqs} ${setClause} 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._filterClause(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?.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 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._pool.connect();\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 }>(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 });\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 wantTail = options?.tail ?? false;\n const wantCount = options?.count ?? false;\n const wantNames = options?.names ?? false;\n const before = options?.before;\n const fullScan = wantCount || wantNames;\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\n const fromClause = `${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 whereClause = `WHERE ${where.length ? where.join(\" AND \") : \"TRUE\"}`;\n\n return fullScan\n ? this._queryStatsFullScan<E>(\n fromClause,\n whereClause,\n params,\n wantTail,\n wantCount,\n wantNames\n )\n : this._queryStatsHeadsOnly<E>(fromClause, whereClause, params, wantTail);\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 _queryStatsHeadsOnly<E extends Schemas>(\n fromClause: string,\n whereClause: string,\n params: unknown[],\n wantTail: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;\n const headSql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version DESC`;\n const tailSql = wantTail\n ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version ASC`\n : null;\n\n const [headRes, tailRes] = await Promise.all([\n this._pool.query<Committed<E, keyof E>>(headSql, params),\n tailSql\n ? this._pool.query<Committed<E, keyof E>>(tailSql, 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 _queryStatsFullScan<E extends Schemas>(\n fromClause: string,\n whereClause: string,\n params: unknown[],\n wantTail: boolean,\n wantCount: boolean,\n wantNames: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const tailCte = wantTail\n ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)`\n : \"\";\n const tailJoin = wantTail ? `LEFT JOIN tails t ON t.stream = h.stream` : \"\";\n const tailCols = wantTail\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 ${fromClause}\n ${whereClause}\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 ${tailCte}\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 ${tailCols}\n FROM heads h\n LEFT JOIN agg a ON a.stream = h.stream\n ${tailJoin}\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 (wantTail && 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 (wantCount) 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 (wantNames) 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 * @param handler Called for each cross-process commit notification.\n * @returns Disposer that releases the LISTEN client.\n */\n private async _subscribeNotifications(\n handler: (notification: StoreNotification) => void\n ): Promise<NotifyDisposer> {\n // Close any prior subscription so callers don't silently double-listen.\n await this._teardownListen();\n\n const client = await this._pool.connect();\n const onNotification = (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._wireNotify`) — 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 client.on(\"notification\", onNotification);\n try {\n await client.query(`LISTEN ${this._channel}`);\n } catch (err) {\n client.removeListener(\"notification\", onNotification);\n client.release(true);\n throw err;\n }\n this._listenClient = client;\n this._listenHandler = onNotification;\n\n return async () => {\n // No-op when this disposer is stale (a later notify() call already\n // tore the subscription down).\n if (this._listenClient !== client) return;\n await this._teardownListen();\n };\n }\n\n /**\n * Atomically truncates streams and seeds each with a snapshot or tombstone.\n * @param targets - Streams to truncate with optional snapshot state and meta.\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 }>\n ): Promise<\n Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >\n > {\n if (!targets.length) return new Map();\n const streams = targets.map((t) => t.stream);\n const client = await this._pool.connect();\n try {\n await client.query(\"BEGIN\");\n await client.query(`DELETE FROM ${this._fqs} WHERE stream = ANY($1)`, [\n streams,\n ]);\n const result = new Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >();\n for (const { stream, snapshot, meta } of targets) {\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 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 * @module act-pg\n * Date reviver for JSON.parse to automatically convert ISO 8601 date strings to Date objects.\n *\n * Recognizes the following formats:\n * - YYYY-MM-DDTHH:MM:SS.sssZ\n * - YYYY-MM-DDTHH:MM:SS.sss+HH:MM\n * - YYYY-MM-DDTHH:MM:SS.sss-HH:MM\n *\n * @param key The key being parsed\n * @param value The value being parsed\n * @returns A Date object if the value matches ISO 8601, otherwise the original value\n *\n * @example\n * const obj = JSON.parse(jsonString, dateReviver);\n */\nconst ISO_8601 =\n /^(\\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(\\.\\d+)?(Z|[+-][0-2][0-9]:[0-5][0-9])?$/;\nexport const dateReviver = (_key: string, value: string): string | Date => {\n if (typeof value === \"string\" && ISO_8601.test(value)) {\n return new Date(value);\n }\n return value;\n};\n"],"mappings":"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","names":["pg","lagging","lane","rowCount"]}