@rotorsoft/act-pg 0.23.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.
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/postgres-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/postgres-store.ts"],"names":[],"mappings":"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"}
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
  }
@@ -806,6 +841,168 @@ var PostgresStore = class {
806
841
  client.release();
807
842
  }
808
843
  }
844
+ /**
845
+ * Per-stream aggregated stats — see {@link Store.query_stats}.
846
+ *
847
+ * Two code paths chosen by the requested stats:
848
+ *
849
+ * - **Heads-only path** (no `count`, no `names`): one or two
850
+ * `SELECT DISTINCT ON (stream) ... ORDER BY stream, version DESC|ASC`
851
+ * queries, executed in parallel when `tail: true`. The
852
+ * `(stream, version)` unique index gives index-only access — K rows
853
+ * touched per query (K = matched streams), not N (events).
854
+ * Ordering by `version` (not `id`) is equivalent within a stream
855
+ * (versions are monotonic per stream and events are committed
856
+ * sequentially) and is the column actually indexed.
857
+ *
858
+ * - **Full-scan path** (`count` or `names` set): one CTE materializes
859
+ * the filtered events, then `GROUP BY stream, name` →
860
+ * `jsonb_object_agg(name, n)` for the `names` map plus per-stream
861
+ * `COUNT(*)` for `count`. Heads (and `tails` when requested) come
862
+ * from `DISTINCT ON` over the same CTE — they ride free on the
863
+ * already-paid scan.
864
+ *
865
+ * The stream universe is derived from the events table: filter form
866
+ * matches event-bearing streams (not subscription rows). When the
867
+ * filter sets `source` or `blocked`, the events table is joined
868
+ * against the streams subscription table since those concepts only
869
+ * exist for subscribed streams.
870
+ */
871
+ async query_stats(input, options) {
872
+ const exclude = options?.exclude ?? [];
873
+ const wantTail = options?.tail ?? false;
874
+ const wantCount = options?.count ?? false;
875
+ const wantNames = options?.names ?? false;
876
+ const before = options?.before;
877
+ const fullScan = wantCount || wantNames;
878
+ if (Array.isArray(input) && input.length === 0) {
879
+ return /* @__PURE__ */ new Map();
880
+ }
881
+ const where = [];
882
+ const params = [];
883
+ if (Array.isArray(input)) {
884
+ params.push(input);
885
+ where.push(`e.stream = ANY($${params.length})`);
886
+ } else if (input.stream !== void 0) {
887
+ params.push(input.stream);
888
+ where.push(
889
+ input.stream_exact ? `e.stream = $${params.length}` : `e.stream ~ $${params.length}`
890
+ );
891
+ }
892
+ if (exclude.length) {
893
+ params.push(exclude);
894
+ where.push(`e.name <> ALL($${params.length})`);
895
+ }
896
+ if (before !== void 0) {
897
+ params.push(before);
898
+ where.push(`e.id < $${params.length}`);
899
+ }
900
+ const fromClause = `${this._fqt} e`;
901
+ const whereClause = `WHERE ${where.length ? where.join(" AND ") : "TRUE"}`;
902
+ return fullScan ? this._queryStatsFullScan(
903
+ fromClause,
904
+ whereClause,
905
+ params,
906
+ wantTail,
907
+ wantCount,
908
+ wantNames
909
+ ) : this._queryStatsHeadsOnly(fromClause, whereClause, params, wantTail);
910
+ }
911
+ /**
912
+ * Cheap path: index-only DISTINCT ON for the head per stream, plus an
913
+ * optional second query (in parallel) for the tail. K rows touched
914
+ * per query, not N events.
915
+ */
916
+ async _queryStatsHeadsOnly(fromClause, whereClause, params, wantTail) {
917
+ const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;
918
+ const headSql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version DESC`;
919
+ const tailSql = wantTail ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version ASC` : null;
920
+ const [headRes, tailRes] = await Promise.all([
921
+ this._pool.query(headSql, params),
922
+ tailSql ? this._pool.query(tailSql, params) : Promise.resolve(null)
923
+ ]);
924
+ const out = /* @__PURE__ */ new Map();
925
+ for (const row of headRes.rows) {
926
+ out.set(row.stream, { head: row });
927
+ }
928
+ if (tailRes) {
929
+ for (const row of tailRes.rows) {
930
+ out.get(row.stream).tail = row;
931
+ }
932
+ }
933
+ return out;
934
+ }
935
+ /**
936
+ * Full-scan path: one CTE-based query computes the per-stream
937
+ * `COUNT(*)` and `jsonb_object_agg(name, n)` map alongside the head
938
+ * (and tail when requested). All extras share the single events scan.
939
+ */
940
+ async _queryStatsFullScan(fromClause, whereClause, params, wantTail, wantCount, wantNames) {
941
+ const tailCte = wantTail ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)` : "";
942
+ const tailJoin = wantTail ? `LEFT JOIN tails t ON t.stream = h.stream` : "";
943
+ const tailCols = wantTail ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,
944
+ t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta` : "";
945
+ const sql = `
946
+ WITH ef AS (
947
+ SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta
948
+ FROM ${fromClause}
949
+ ${whereClause}
950
+ ),
951
+ agg AS (
952
+ SELECT stream,
953
+ SUM(n)::int AS cnt,
954
+ jsonb_object_agg(name, n) AS names
955
+ FROM (
956
+ SELECT stream, name, COUNT(*)::int AS n
957
+ FROM ef
958
+ GROUP BY stream, name
959
+ ) t
960
+ GROUP BY stream
961
+ ),
962
+ heads AS (
963
+ SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version DESC
964
+ )
965
+ ${tailCte}
966
+ SELECT
967
+ h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,
968
+ a.cnt AS agg_count,
969
+ a.names AS agg_names
970
+ ${tailCols}
971
+ FROM heads h
972
+ LEFT JOIN agg a ON a.stream = h.stream
973
+ ${tailJoin}
974
+ `;
975
+ const res = await this._pool.query(sql, params);
976
+ const out = /* @__PURE__ */ new Map();
977
+ for (const row of res.rows) {
978
+ const stats = {
979
+ head: {
980
+ id: row.id,
981
+ stream: row.stream,
982
+ version: row.version,
983
+ name: row.name,
984
+ data: row.data,
985
+ created: row.created,
986
+ meta: row.meta
987
+ }
988
+ };
989
+ if (wantTail && row.t_id !== void 0 && row.t_id !== null) {
990
+ stats.tail = {
991
+ id: row.t_id,
992
+ stream: row.t_stream,
993
+ version: row.t_version,
994
+ name: row.t_name,
995
+ data: row.t_data,
996
+ created: row.t_created,
997
+ meta: row.t_meta
998
+ };
999
+ }
1000
+ if (wantCount) stats.count = row.agg_count;
1001
+ if (wantNames) stats.names = row.agg_names;
1002
+ out.set(row.stream, stats);
1003
+ }
1004
+ return out;
1005
+ }
809
1006
  /**
810
1007
  * Implementation of the optional `Store.notify` hook. Bound onto
811
1008
  * `this.notify` in the constructor when `config.notify === true`,