@rotorsoft/act-pg 1.13.18 → 1.14.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.js CHANGED
@@ -84,6 +84,8 @@ var PostgresStore = class {
84
84
  config;
85
85
  _fqt;
86
86
  _fqs;
87
+ /** Correlate checkpoint table (#1484) — one row, id 0. */
88
+ _fqc;
87
89
  /**
88
90
  * Per-instance writer identifier embedded in every NOTIFY payload. The
89
91
  * `notify()` LISTEN handler skips payloads where `by === this._by`,
@@ -170,6 +172,7 @@ var PostgresStore = class {
170
172
  this._pool = new Pool({ ...poolConfig, types: scopedTypes });
171
173
  this._fqt = `"${this.config.schema}"."${this.config.table}"`;
172
174
  this._fqs = `"${this.config.schema}"."${this.config.table}_streams"`;
175
+ this._fqc = `"${this.config.schema}"."${this.config.table}_correlated"`;
173
176
  this._channel = notify_channel(this.config.schema, this.config.table);
174
177
  if (this.config.notify) {
175
178
  this.notify = this._subscribe_notifications.bind(this);
@@ -306,6 +309,16 @@ var PostgresStore = class {
306
309
  deferred_at timestamptz
307
310
  ) TABLESPACE pg_default;`
308
311
  );
312
+ await client.query(
313
+ `CREATE TABLE IF NOT EXISTS ${this._fqc} (
314
+ id int PRIMARY KEY DEFAULT 0,
315
+ at bigint NOT NULL DEFAULT -1,
316
+ CONSTRAINT ${this.config.table}_correlated_singleton CHECK (id = 0)
317
+ ) TABLESPACE pg_default;`
318
+ );
319
+ await client.query(
320
+ `INSERT INTO ${this._fqc} (id) VALUES (0) ON CONFLICT (id) DO NOTHING;`
321
+ );
309
322
  await client.query(
310
323
  `ALTER TABLE ${this._fqs}
311
324
  ADD COLUMN IF NOT EXISTS priority int NOT NULL DEFAULT 0;`
@@ -391,7 +404,7 @@ var PostgresStore = class {
391
404
  WHERE schema_name = '${this.config.schema}'
392
405
  ) THEN
393
406
  EXECUTE 'DROP TABLE IF EXISTS ${this._fqt}';
394
- EXECUTE 'DROP TABLE IF EXISTS ${this._fqs}';
407
+ EXECUTE 'DROP TABLE IF EXISTS ${this._fqc}, ${this._fqs}';
395
408
  IF '${this.config.schema}' <> 'public' THEN
396
409
  EXECUTE 'DROP SCHEMA "${this.config.schema}" CASCADE';
397
410
  END IF;
@@ -650,13 +663,16 @@ var PostgresStore = class {
650
663
  AND (deferred_at IS NULL OR deferred_at <= NOW())
651
664
  ),
652
665
  available AS (
653
- -- Never processed: claimable without probing the log at all.
654
- SELECT stream, source, at, priority, lane FROM eligible WHERE at < 0
655
- UNION ALL
666
+ -- Every arm below is watermark-agnostic (#1446): a fresh
667
+ -- subscription sits at -1, and e.id > s.at already answers
668
+ -- correctly there, since the first event has a greater id.
669
+ -- There used to be a fourth arm claiming at < 0 unconditionally,
670
+ -- which handed out an empty lease and a no-op ack for every
671
+ -- subscription with no matching events yet.
656
672
  -- Source-less subscription: any non-snapshot event past the
657
673
  -- watermark counts, so the id index alone answers it.
658
674
  SELECT stream, source, at, priority, lane FROM eligible s
659
- WHERE s.at >= 0 AND s.source IS NULL
675
+ WHERE s.source IS NULL
660
676
  AND EXISTS (
661
677
  SELECT 1 FROM ${this._fqt} e
662
678
  WHERE e.id > s.at AND e.name <> '${SNAP_EVENT}' LIMIT 1
@@ -665,7 +681,7 @@ var PostgresStore = class {
665
681
  -- Literal source (no regex metacharacter): exact equality, so
666
682
  -- "s1" never claims "s12", and (stream, id) is usable.
667
683
  SELECT stream, source, at, priority, lane FROM eligible s
668
- WHERE s.at >= 0 AND s.source IS NOT NULL
684
+ WHERE s.source IS NOT NULL
669
685
  AND s.source !~ '${SOURCE_METACHARACTER_CLASS}'
670
686
  AND EXISTS (
671
687
  SELECT 1 FROM ${this._fqt} e
@@ -678,7 +694,7 @@ var PostgresStore = class {
678
694
  -- it anchors. Unavoidably a scan \u2014 but only for the subscriptions
679
695
  -- that actually declare a pattern.
680
696
  SELECT stream, source, at, priority, lane FROM eligible s
681
- WHERE s.at >= 0 AND s.source IS NOT NULL
697
+ WHERE s.source IS NOT NULL
682
698
  AND s.source ~ '${SOURCE_METACHARACTER_CLASS}'
683
699
  AND EXISTS (
684
700
  SELECT 1 FROM ${this._fqt} e
@@ -780,7 +796,7 @@ var PostgresStore = class {
780
796
  * @param streams - Streams to register with optional source.
781
797
  * @returns subscribed count and current max watermark.
782
798
  */
783
- async subscribe(streams) {
799
+ async subscribe(streams, correlated_at) {
784
800
  const client = await this._client("subscribe");
785
801
  try {
786
802
  await client.query("BEGIN");
@@ -821,11 +837,21 @@ var PostgresStore = class {
821
837
  [JSON.stringify(streams)]
822
838
  );
823
839
  }
840
+ if (correlated_at !== void 0)
841
+ await client.query(
842
+ `UPDATE ${this._fqc} SET at = GREATEST(at, $1::bigint) WHERE id = 0`,
843
+ [correlated_at]
844
+ );
824
845
  const { rows } = await client.query(
825
- `SELECT COALESCE(MAX(at), -1) AS max FROM ${this._fqs}`
846
+ `SELECT (SELECT COALESCE(MAX(at), -1) FROM ${this._fqs}) AS max,
847
+ (SELECT at FROM ${this._fqc} WHERE id = 0) AS correlated_at`
826
848
  );
827
849
  await client.query("COMMIT");
828
- return { subscribed, watermark: rows[0]?.max ?? -1 };
850
+ return {
851
+ subscribed,
852
+ watermark: rows[0]?.max ?? -1,
853
+ correlated_at: Number(rows[0]?.correlated_at ?? -1)
854
+ };
829
855
  } catch (error) {
830
856
  await client.query("ROLLBACK").catch(() => {
831
857
  });