@rotorsoft/act-pg 1.13.5 → 1.13.7

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
@@ -27,10 +27,13 @@ var dateReviver = (_key, value) => {
27
27
  var logger = log();
28
28
  var SOURCE_METACHARACTER_CLASS = "[]^$.*+?()[{}|\\\\]";
29
29
  var { Pool, types } = pg;
30
- types.setTypeParser(
31
- types.builtins.JSONB,
32
- (val) => JSON.parse(val, dateReviver)
33
- );
30
+ var JSONB_OID = types.builtins.JSONB;
31
+ var scopedTypes = {
32
+ getTypeParser: ((oid, format) => oid === JSONB_OID ? (val) => JSON.parse(val, dateReviver) : types.getTypeParser(
33
+ oid,
34
+ format
35
+ ))
36
+ };
34
37
  var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
35
38
  var PG_UNIQUE_VIOLATION = "23505";
36
39
  var NOTIFY_CHANNEL_PREFIX = "act_commit";
@@ -172,7 +175,7 @@ var PostgresStore = class {
172
175
  pii_encryption: ___,
173
176
  ...poolConfig
174
177
  } = this.config;
175
- this._pool = new Pool(poolConfig);
178
+ this._pool = new Pool({ ...poolConfig, types: scopedTypes });
176
179
  this._fqt = `"${this.config.schema}"."${this.config.table}"`;
177
180
  this._fqs = `"${this.config.schema}"."${this.config.table}_streams"`;
178
181
  this._channel = notify_channel(this.config.schema, this.config.table);
@@ -420,11 +423,11 @@ var PostgresStore = class {
420
423
  query.stream_exact ? `stream = $${values.length}` : `stream ~ $${values.length}`
421
424
  );
422
425
  }
423
- if (names?.length) {
426
+ if (names !== void 0) {
424
427
  values.push(names);
425
428
  conditions.push(`name = ANY($${values.length})`);
426
429
  }
427
- if (before) {
430
+ if (before !== void 0) {
428
431
  values.push(before);
429
432
  conditions.push(`id<$${values.length}`);
430
433
  }
@@ -579,6 +582,11 @@ var PostgresStore = class {
579
582
  const { rows } = await client.query(
580
583
  `
581
584
  WITH
585
+ -- Plain read of the eligible frontier \u2014 no row lock here. A CTE
586
+ -- carrying FOR UPDATE never inlines, so locking it would materialize
587
+ -- and lock EVERY claimable stream for this transaction, starving
588
+ -- overlapping competing consumers. We only lock the small
589
+ -- lagging+leading candidate slice, down in the "locked" CTE.
582
590
  available AS (
583
591
  SELECT stream, source, at, priority, lane
584
592
  FROM ${this._fqs} s
@@ -602,7 +610,6 @@ var PostgresStore = class {
602
610
  )
603
611
  LIMIT 1
604
612
  ))
605
- FOR UPDATE SKIP LOCKED
606
613
  ),
607
614
  -- Priority lanes (ACT-102): higher priority first, then
608
615
  -- lagging-watermark order. With everyone at priority=0 the
@@ -624,6 +631,20 @@ var PostgresStore = class {
624
631
  SELECT DISTINCT ON (stream) stream, source, at, lane, lagging
625
632
  FROM (SELECT * FROM lag UNION ALL SELECT * FROM lead) t
626
633
  ORDER BY stream, at
634
+ ),
635
+ -- Lock ONLY the <= lagging+leading candidate rows. The
636
+ -- lease-eligibility predicate is re-asserted here under the lock so a
637
+ -- lease acquired by a competing worker between the unlocked read and
638
+ -- this lock is never stolen. Competing workers SKIP-LOCK just this
639
+ -- slice, not the whole frontier, and claim other eligible streams.
640
+ locked AS (
641
+ SELECT s2.stream
642
+ FROM ${this._fqs} s2
643
+ WHERE s2.stream IN (SELECT stream FROM combined)
644
+ AND s2.blocked = false
645
+ AND (s2.leased_by IS NULL OR s2.leased_until <= NOW())
646
+ AND (s2.deferred_at IS NULL OR s2.deferred_at <= NOW())
647
+ FOR UPDATE OF s2 SKIP LOCKED
627
648
  )
628
649
  UPDATE ${this._fqs} s
629
650
  SET
@@ -632,6 +653,7 @@ var PostgresStore = class {
632
653
  retry = s.retry + 1
633
654
  FROM combined c
634
655
  WHERE s.stream = c.stream
656
+ AND s.stream IN (SELECT stream FROM locked)
635
657
  RETURNING s.stream, s.source, s.at, s.retry, c.lagging, s.lane
636
658
  `,
637
659
  params