@rotorsoft/act-pg 1.3.0 → 1.4.1

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,10 @@ types.setTypeParser(
27
27
  var SAFE_IDENTIFIER = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
28
28
  var PG_UNIQUE_VIOLATION = "23505";
29
29
  var NOTIFY_CHANNEL_PREFIX = "act_commit";
30
- function notifyChannel(schema, table) {
30
+ function notify_channel(schema, table) {
31
31
  return `${NOTIFY_CHANNEL_PREFIX}_${schema}_${table}`;
32
32
  }
33
- function assertSafeIdentifier(value, label) {
33
+ function assert_safe_identifier(value, label) {
34
34
  if (!SAFE_IDENTIFIER.test(value))
35
35
  throw new Error(`Unsafe SQL identifier for ${label}: "${value}"`);
36
36
  }
@@ -63,14 +63,14 @@ var PostgresStore = class {
63
63
  */
64
64
  _channel;
65
65
  /** Active LISTEN client (one per `notify()` subscription). */
66
- _listenClient;
66
+ _listen_client;
67
67
  /**
68
68
  * Notification listener attached to the active LISTEN client. Tracked
69
69
  * separately so the re-subscribe / dispose paths can detach it before
70
70
  * destroying the client — without this, a pool that reused the
71
71
  * connection would re-fire the stale handler.
72
72
  */
73
- _listenHandler;
73
+ _listen_handler;
74
74
  /**
75
75
  * Cross-process commit subscription. **Present only when
76
76
  * `config.notify === true`** — the orchestrator's auto-wire path
@@ -88,15 +88,15 @@ var PostgresStore = class {
88
88
  */
89
89
  constructor(config = {}) {
90
90
  this.config = { ...DEFAULT_CONFIG, ...config };
91
- assertSafeIdentifier(this.config.schema, "schema");
92
- assertSafeIdentifier(this.config.table, "table");
91
+ assert_safe_identifier(this.config.schema, "schema");
92
+ assert_safe_identifier(this.config.table, "table");
93
93
  const { schema: _, table: __, ...poolConfig } = this.config;
94
94
  this._pool = new Pool(poolConfig);
95
95
  this._fqt = `"${this.config.schema}"."${this.config.table}"`;
96
96
  this._fqs = `"${this.config.schema}"."${this.config.table}_streams"`;
97
- this._channel = notifyChannel(this.config.schema, this.config.table);
97
+ this._channel = notify_channel(this.config.schema, this.config.table);
98
98
  if (this.config.notify) {
99
- this.notify = this._subscribeNotifications.bind(this);
99
+ this.notify = this._subscribe_notifications.bind(this);
100
100
  }
101
101
  }
102
102
  /**
@@ -105,7 +105,7 @@ var PostgresStore = class {
105
105
  * @returns Promise that resolves when all connections are closed
106
106
  */
107
107
  async dispose() {
108
- await this._teardownListen();
108
+ await this._teardown_listen();
109
109
  await this._pool.end();
110
110
  }
111
111
  /**
@@ -115,16 +115,16 @@ var PostgresStore = class {
115
115
  * destroying belt-and-braces guards against any future change in
116
116
  * pg-pool semantics that could re-issue a half-clean client).
117
117
  */
118
- async _teardownListen() {
119
- if (!this._listenClient) return;
120
- this._listenClient.removeListener("notification", this._listenHandler);
121
- this._listenHandler = void 0;
118
+ async _teardown_listen() {
119
+ if (!this._listen_client) return;
120
+ this._listen_client.removeListener("notification", this._listen_handler);
121
+ this._listen_handler = void 0;
122
122
  try {
123
- await this._listenClient.query(`UNLISTEN ${this._channel}`);
123
+ await this._listen_client.query(`UNLISTEN ${this._channel}`);
124
124
  } catch {
125
125
  }
126
- this._listenClient.release(true);
127
- this._listenClient = void 0;
126
+ this._listen_client.release(true);
127
+ this._listen_client = void 0;
128
128
  }
129
129
  /**
130
130
  * Seed the database with required tables, indexes, and schema for event storage.
@@ -399,7 +399,7 @@ var PostgresStore = class {
399
399
  const client = await this._pool.connect();
400
400
  try {
401
401
  await client.query("BEGIN");
402
- const laneClause = lane !== void 0 ? `AND s.lane = $5` : "";
402
+ const lane_clause = lane !== void 0 ? `AND s.lane = $5` : "";
403
403
  const params = lane !== void 0 ? [lagging, leading, by, millis, lane] : [lagging, leading, by, millis];
404
404
  const { rows } = await client.query(
405
405
  `
@@ -408,7 +408,7 @@ var PostgresStore = class {
408
408
  SELECT stream, source, at, priority, lane
409
409
  FROM ${this._fqs} s
410
410
  WHERE blocked = false
411
- ${laneClause}
411
+ ${lane_clause}
412
412
  AND (leased_by IS NULL OR leased_until <= NOW())
413
413
  AND (s.at < 0 OR EXISTS (
414
414
  SELECT 1 FROM ${this._fqt} e
@@ -633,7 +633,7 @@ var PostgresStore = class {
633
633
  * `WHERE` — callers compose it with any other predicates they need.
634
634
  * Returns an always-true clause (`true`) when the filter is empty.
635
635
  */
636
- _filterClause(filter, start) {
636
+ _filter_clause(filter, start) {
637
637
  const conditions = [];
638
638
  const values = [];
639
639
  if (filter.stream !== void 0) {
@@ -663,19 +663,19 @@ var PostgresStore = class {
663
663
  };
664
664
  }
665
665
  async reset(input) {
666
- const setClause = `SET at = -1, retry = 0, blocked = false, error = NULL,
666
+ const set_clause = `SET at = -1, retry = 0, blocked = false, error = NULL,
667
667
  leased_by = NULL, leased_until = NULL`;
668
668
  if (Array.isArray(input)) {
669
669
  if (!input.length) return 0;
670
670
  const { rowCount: rowCount2 } = await this._pool.query(
671
- `UPDATE ${this._fqs} ${setClause} WHERE stream = ANY($1)`,
671
+ `UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($1)`,
672
672
  [input]
673
673
  );
674
674
  return rowCount2 ?? 0;
675
675
  }
676
- const { clause, values } = this._filterClause(input, 1);
676
+ const { clause, values } = this._filter_clause(input, 1);
677
677
  const { rowCount } = await this._pool.query(
678
- `UPDATE ${this._fqs} ${setClause} WHERE ${clause}`,
678
+ `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,
679
679
  values
680
680
  );
681
681
  return rowCount ?? 0;
@@ -696,23 +696,23 @@ var PostgresStore = class {
696
696
  * @returns Count of streams that were actually flipped (were blocked).
697
697
  */
698
698
  async unblock(input) {
699
- const setClause = `SET retry = -1, blocked = false, error = NULL,
699
+ const set_clause = `SET retry = -1, blocked = false, error = NULL,
700
700
  leased_by = NULL, leased_until = NULL`;
701
701
  if (Array.isArray(input)) {
702
702
  if (!input.length) return 0;
703
703
  const { rowCount: rowCount2 } = await this._pool.query(
704
- `UPDATE ${this._fqs} ${setClause}
704
+ `UPDATE ${this._fqs} ${set_clause}
705
705
  WHERE stream = ANY($1) AND blocked = true`,
706
706
  [input]
707
707
  );
708
708
  return rowCount2 ?? 0;
709
709
  }
710
- const { clause, values } = this._filterClause(
710
+ const { clause, values } = this._filter_clause(
711
711
  { ...input, blocked: true },
712
712
  1
713
713
  );
714
714
  const { rowCount } = await this._pool.query(
715
- `UPDATE ${this._fqs} ${setClause} WHERE ${clause}`,
715
+ `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,
716
716
  values
717
717
  );
718
718
  return rowCount ?? 0;
@@ -732,7 +732,7 @@ var PostgresStore = class {
732
732
  * @returns Count of streams whose priority changed.
733
733
  */
734
734
  async prioritize(filter, priority) {
735
- const { clause, values } = this._filterClause(filter, 2);
735
+ const { clause, values } = this._filter_clause(filter, 2);
736
736
  const sql = `UPDATE ${this._fqs} SET priority = $1
737
737
  WHERE priority <> $1 AND ${clause}`;
738
738
  const { rowCount } = await this._pool.query(sql, [priority, ...values]);
@@ -839,11 +839,11 @@ var PostgresStore = class {
839
839
  */
840
840
  async query_stats(input, options) {
841
841
  const exclude = options?.exclude ?? [];
842
- const wantTail = options?.tail ?? false;
843
- const wantCount = options?.count ?? false;
844
- const wantNames = options?.names ?? false;
842
+ const want_tail = options?.tail ?? false;
843
+ const want_count = options?.count ?? false;
844
+ const want_names = options?.names ?? false;
845
845
  const before = options?.before;
846
- const fullScan = wantCount || wantNames;
846
+ const full_scan = want_count || want_names;
847
847
  if (Array.isArray(input) && input.length === 0) {
848
848
  return /* @__PURE__ */ new Map();
849
849
  }
@@ -866,29 +866,34 @@ var PostgresStore = class {
866
866
  params.push(before);
867
867
  where.push(`e.id < $${params.length}`);
868
868
  }
869
- const fromClause = `${this._fqt} e`;
870
- const whereClause = `WHERE ${where.length ? where.join(" AND ") : "TRUE"}`;
871
- return fullScan ? this._queryStatsFullScan(
872
- fromClause,
873
- whereClause,
869
+ const from_clause = `${this._fqt} e`;
870
+ const where_clause = `WHERE ${where.length ? where.join(" AND ") : "TRUE"}`;
871
+ return full_scan ? this._query_stats_full_scan(
872
+ from_clause,
873
+ where_clause,
874
874
  params,
875
- wantTail,
876
- wantCount,
877
- wantNames
878
- ) : this._queryStatsHeadsOnly(fromClause, whereClause, params, wantTail);
875
+ want_tail,
876
+ want_count,
877
+ want_names
878
+ ) : this._query_stats_heads_only(
879
+ from_clause,
880
+ where_clause,
881
+ params,
882
+ want_tail
883
+ );
879
884
  }
880
885
  /**
881
886
  * Cheap path: index-only DISTINCT ON for the head per stream, plus an
882
887
  * optional second query (in parallel) for the tail. K rows touched
883
888
  * per query, not N events.
884
889
  */
885
- async _queryStatsHeadsOnly(fromClause, whereClause, params, wantTail) {
890
+ async _query_stats_heads_only(from_clause, where_clause, params, want_tail) {
886
891
  const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;
887
- const headSql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version DESC`;
888
- const tailSql = wantTail ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${fromClause} ${whereClause} ORDER BY e.stream, e.version ASC` : null;
892
+ const head_sql = `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version DESC`;
893
+ const tail_sql = want_tail ? `SELECT DISTINCT ON (e.stream) ${cols} FROM ${from_clause} ${where_clause} ORDER BY e.stream, e.version ASC` : null;
889
894
  const [headRes, tailRes] = await Promise.all([
890
- this._pool.query(headSql, params),
891
- tailSql ? this._pool.query(tailSql, params) : Promise.resolve(null)
895
+ this._pool.query(head_sql, params),
896
+ tail_sql ? this._pool.query(tail_sql, params) : Promise.resolve(null)
892
897
  ]);
893
898
  const out = /* @__PURE__ */ new Map();
894
899
  for (const row of headRes.rows) {
@@ -906,16 +911,16 @@ var PostgresStore = class {
906
911
  * `COUNT(*)` and `jsonb_object_agg(name, n)` map alongside the head
907
912
  * (and tail when requested). All extras share the single events scan.
908
913
  */
909
- async _queryStatsFullScan(fromClause, whereClause, params, wantTail, wantCount, wantNames) {
910
- const tailCte = wantTail ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)` : "";
911
- const tailJoin = wantTail ? `LEFT JOIN tails t ON t.stream = h.stream` : "";
912
- const tailCols = wantTail ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,
914
+ async _query_stats_full_scan(from_clause, where_clause, params, want_tail, want_count, want_names) {
915
+ const tail_cte = want_tail ? `, tails AS (SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version ASC)` : "";
916
+ const tail_join = want_tail ? `LEFT JOIN tails t ON t.stream = h.stream` : "";
917
+ const tail_cols = want_tail ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,
913
918
  t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta` : "";
914
919
  const sql = `
915
920
  WITH ef AS (
916
921
  SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta
917
- FROM ${fromClause}
918
- ${whereClause}
922
+ FROM ${from_clause}
923
+ ${where_clause}
919
924
  ),
920
925
  agg AS (
921
926
  SELECT stream,
@@ -931,15 +936,15 @@ var PostgresStore = class {
931
936
  heads AS (
932
937
  SELECT DISTINCT ON (stream) * FROM ef ORDER BY stream, version DESC
933
938
  )
934
- ${tailCte}
939
+ ${tail_cte}
935
940
  SELECT
936
941
  h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,
937
942
  a.cnt AS agg_count,
938
943
  a.names AS agg_names
939
- ${tailCols}
944
+ ${tail_cols}
940
945
  FROM heads h
941
946
  LEFT JOIN agg a ON a.stream = h.stream
942
- ${tailJoin}
947
+ ${tail_join}
943
948
  `;
944
949
  const res = await this._pool.query(sql, params);
945
950
  const out = /* @__PURE__ */ new Map();
@@ -955,7 +960,7 @@ var PostgresStore = class {
955
960
  meta: row.meta
956
961
  }
957
962
  };
958
- if (wantTail && row.t_id !== void 0 && row.t_id !== null) {
963
+ if (want_tail && row.t_id !== void 0 && row.t_id !== null) {
959
964
  stats.tail = {
960
965
  id: row.t_id,
961
966
  stream: row.t_stream,
@@ -966,8 +971,8 @@ var PostgresStore = class {
966
971
  meta: row.t_meta
967
972
  };
968
973
  }
969
- if (wantCount) stats.count = row.agg_count;
970
- if (wantNames) stats.names = row.agg_names;
974
+ if (want_count) stats.count = row.agg_count;
975
+ if (want_names) stats.names = row.agg_names;
971
976
  out.set(row.stream, stats);
972
977
  }
973
978
  return out;
@@ -993,10 +998,10 @@ var PostgresStore = class {
993
998
  * @param handler Called for each cross-process commit notification.
994
999
  * @returns Disposer that releases the LISTEN client.
995
1000
  */
996
- async _subscribeNotifications(handler) {
997
- await this._teardownListen();
1001
+ async _subscribe_notifications(handler) {
1002
+ await this._teardown_listen();
998
1003
  const client = await this._pool.connect();
999
- const onNotification = (msg) => {
1004
+ const on_notification = (msg) => {
1000
1005
  if (msg.channel !== this._channel) return;
1001
1006
  if (!msg.payload) return;
1002
1007
  let parsed;
@@ -1033,19 +1038,19 @@ var PostgresStore = class {
1033
1038
  logger.error(err, "act_commit: handler threw, listener preserved");
1034
1039
  }
1035
1040
  };
1036
- client.on("notification", onNotification);
1041
+ client.on("notification", on_notification);
1037
1042
  try {
1038
1043
  await client.query(`LISTEN ${this._channel}`);
1039
1044
  } catch (err) {
1040
- client.removeListener("notification", onNotification);
1045
+ client.removeListener("notification", on_notification);
1041
1046
  client.release(true);
1042
1047
  throw err;
1043
1048
  }
1044
- this._listenClient = client;
1045
- this._listenHandler = onNotification;
1049
+ this._listen_client = client;
1050
+ this._listen_handler = on_notification;
1046
1051
  return async () => {
1047
- if (this._listenClient !== client) return;
1048
- await this._teardownListen();
1052
+ if (this._listen_client !== client) return;
1053
+ await this._teardown_listen();
1049
1054
  };
1050
1055
  }
1051
1056
  /**