@rotorsoft/act-pg 1.9.3 → 1.10.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.
@@ -110,18 +110,18 @@ type Config = Readonly<{
110
110
  *
111
111
  * @example Connection pooling configuration
112
112
  * ```typescript
113
- * // PostgresStore uses node-postgres (pg) connection pooling
114
- * // Pool is created automatically with default settings
115
- * // For custom pool config, use environment variables:
116
- * // PGHOST, PGPORT, PGDATABASE, PGUSER, PGPASSWORD
117
- * // PGMAXCONNECTIONS, PGIDLETIMEOUT, etc.
113
+ * // PostgresStore uses node-postgres (pg) connection pooling with
114
+ * // opinionated defaults: max 20, connectionTimeoutMillis 10s,
115
+ * // idleTimeoutMillis 30s, statement_timeout 60s. Any pg.PoolConfig
116
+ * // field passed to the constructor overrides the default.
118
117
  *
119
118
  * const pgStore = new PostgresStore({
120
119
  * host: "db.example.com",
121
120
  * port: 5432,
122
121
  * database: "production",
123
122
  * user: "app_user",
124
- * password: process.env.DB_PASSWORD
123
+ * password: process.env.DB_PASSWORD,
124
+ * max: 40 // lanes × streamLimit + API concurrency + headroom
125
125
  * });
126
126
  * ```
127
127
  *
@@ -212,6 +212,15 @@ export declare class PostgresStore implements Store {
212
212
  * @param config Partial configuration (host, port, user, password, schema, table, etc.)
213
213
  */
214
214
  constructor(config?: Partial<Config>);
215
+ /**
216
+ * Acquire a pooled client, translating acquisition failures into
217
+ * {@link StoreError} with the calling operation as context. With the
218
+ * default `connectionTimeoutMillis`, a saturated pool fails here
219
+ * after 10s with `Store operation "<operation>" failed` (driver
220
+ * error preserved as `cause`) instead of hanging indefinitely.
221
+ * Every method that checks out a client routes through this helper.
222
+ */
223
+ private _client;
215
224
  /**
216
225
  * Dispose of the store and close all database connections.
217
226
  * Releases any active LISTEN client first so the pool can drain cleanly.
@@ -303,6 +312,17 @@ export declare class PostgresStore implements Store {
303
312
  * @returns Blocked leases.
304
313
  */
305
314
  block(leases: BlockedLease[]): Promise<BlockedLease[]>;
315
+ /**
316
+ * Hold the matched streams out of {@link claim} until `deferred_at`
317
+ * (ms since epoch) — see {@link Store.defer}. Persists `deferred_at`
318
+ * (as a `timestamptz`) so the skip is honored by every competing
319
+ * worker; `claim` filters on `deferred_at <= NOW()`. Accepts an
320
+ * explicit list of names or a {@link StreamFilter}, mirroring
321
+ * {@link reset}/{@link prioritize}. Cleared by ack/block/reset/unblock.
322
+ *
323
+ * @returns Count of streams whose `deferred_at` was set.
324
+ */
325
+ defer(input: string[] | StreamFilter, deferred_at: number): Promise<number>;
306
326
  /**
307
327
  * Reset watermarks for the given streams to -1, clearing retry, blocked,
308
328
  * error, and lease state so they can be replayed from the beginning.
@@ -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
@@ -74,7 +74,35 @@ var DEFAULT_CONFIG = {
74
74
  password: "postgres",
75
75
  schema: "public",
76
76
  table: "events",
77
- notify: false
77
+ notify: false,
78
+ // Opinionated pool defaults (#1119). node-postgres ships `max: 10`
79
+ // with no acquisition timeout and no statement timeout — a saturated
80
+ // pool makes every caller hang indefinitely instead of failing with
81
+ // a diagnosable error. Nearly every store method holds a client for
82
+ // a multi-statement transaction, so multi-lane drains plus API
83
+ // traffic can exhaust a 10-client pool quickly. All four values are
84
+ // plain `pg.PoolConfig` fields — caller config overrides any of them
85
+ // via the constructor spread.
86
+ //
87
+ // - `max: 20` — floor for the default lane's parallel handler budget
88
+ // (streamLimit 10, each commit holds a client) plus API commits,
89
+ // the optional LISTEN client, and headroom. Sizing rule in the
90
+ // README: Σ per-lane streamLimit + API concurrency + notify + 2–4.
91
+ // - `connectionTimeoutMillis: 10_000` — fail acquisition fast (the
92
+ // pg default of 0 waits forever); surfaces as StoreError via
93
+ // `_client()` so operators see *which* operation starved.
94
+ // - `idleTimeoutMillis: 30_000` — keep idle clients warm across
95
+ // drain cycles (cycleMs can exceed the pg default of 10s in
96
+ // low-traffic deployments) without pinning connections for long.
97
+ // - `statement_timeout: 60_000` — per-statement (not per-transaction)
98
+ // ceiling; every statement the store issues (claim CTE, seed DDL,
99
+ // truncate, restore's per-event inserts) legitimately completes
100
+ // orders of magnitude faster, so 60s only fires on a wedged server
101
+ // or a lost lock instead of holding the client hostage.
102
+ max: 20,
103
+ connectionTimeoutMillis: 1e4,
104
+ idleTimeoutMillis: 3e4,
105
+ statement_timeout: 6e4
78
106
  };
79
107
  var PostgresStore = class {
80
108
  _pool;
@@ -145,6 +173,21 @@ var PostgresStore = class {
145
173
  }
146
174
  this._resolve_pii_key = this.config.pii_encryption ? (0, import_act_crypto.makeKeyResolver)(this.config.pii_encryption) : void 0;
147
175
  }
176
+ /**
177
+ * Acquire a pooled client, translating acquisition failures into
178
+ * {@link StoreError} with the calling operation as context. With the
179
+ * default `connectionTimeoutMillis`, a saturated pool fails here
180
+ * after 10s with `Store operation "<operation>" failed` (driver
181
+ * error preserved as `cause`) instead of hanging indefinitely.
182
+ * Every method that checks out a client routes through this helper.
183
+ */
184
+ async _client(operation) {
185
+ try {
186
+ return await this._pool.connect();
187
+ } catch (error) {
188
+ throw new import_act.StoreError(operation, { cause: error });
189
+ }
190
+ }
148
191
  /**
149
192
  * Dispose of the store and close all database connections.
150
193
  * Releases any active LISTEN client first so the pool can drain cleanly.
@@ -178,7 +221,7 @@ var PostgresStore = class {
178
221
  * @throws Error if seeding fails
179
222
  */
180
223
  async seed() {
181
- const client = await this._pool.connect();
224
+ const client = await this._client("seed");
182
225
  try {
183
226
  await client.query("BEGIN");
184
227
  await client.query(
@@ -231,7 +274,8 @@ var PostgresStore = class {
231
274
  leased_by text,
232
275
  leased_until timestamptz,
233
276
  priority int NOT NULL DEFAULT 0,
234
- lane text NOT NULL DEFAULT 'default'
277
+ lane text NOT NULL DEFAULT 'default',
278
+ deferred_at timestamptz
235
279
  ) TABLESPACE pg_default;`
236
280
  );
237
281
  await client.query(
@@ -242,6 +286,10 @@ var PostgresStore = class {
242
286
  `ALTER TABLE ${this._fqs}
243
287
  ADD COLUMN IF NOT EXISTS lane text NOT NULL DEFAULT 'default';`
244
288
  );
289
+ await client.query(
290
+ `ALTER TABLE ${this._fqs}
291
+ ADD COLUMN IF NOT EXISTS deferred_at timestamptz;`
292
+ );
245
293
  await client.query(
246
294
  `DROP INDEX IF EXISTS "${this.config.schema}"."${this.config.table}_streams_fetch_ix"`
247
295
  );
@@ -386,7 +434,7 @@ var PostgresStore = class {
386
434
  */
387
435
  async commit(stream, msgs, meta, expectedVersion) {
388
436
  if (msgs.length === 0) return [];
389
- const client = await this._pool.connect();
437
+ const client = await this._client("commit");
390
438
  let version = -1;
391
439
  try {
392
440
  await client.query("BEGIN");
@@ -468,7 +516,7 @@ var PostgresStore = class {
468
516
  * @returns Leased streams with metadata
469
517
  */
470
518
  async claim(lagging, leading, by, millis, lane) {
471
- const client = await this._pool.connect();
519
+ const client = await this._client("claim");
472
520
  try {
473
521
  await client.query("BEGIN");
474
522
  const lane_clause = lane !== void 0 ? `AND s.lane = $5` : "";
@@ -482,6 +530,7 @@ var PostgresStore = class {
482
530
  WHERE blocked = false
483
531
  ${lane_clause}
484
532
  AND (leased_by IS NULL OR leased_until <= NOW())
533
+ AND (deferred_at IS NULL OR deferred_at <= NOW())
485
534
  AND (s.at < 0 OR EXISTS (
486
535
  SELECT 1 FROM ${this._fqt} e
487
536
  WHERE e.id > s.at
@@ -549,7 +598,7 @@ var PostgresStore = class {
549
598
  * @returns subscribed count and current max watermark.
550
599
  */
551
600
  async subscribe(streams) {
552
- const client = await this._pool.connect();
601
+ const client = await this._client("subscribe");
553
602
  try {
554
603
  await client.query("BEGIN");
555
604
  let subscribed = 0;
@@ -609,7 +658,7 @@ var PostgresStore = class {
609
658
  * @returns Acked leases.
610
659
  */
611
660
  async ack(leases) {
612
- const client = await this._pool.connect();
661
+ const client = await this._client("ack");
613
662
  try {
614
663
  await client.query("BEGIN");
615
664
  const { rows } = await client.query(
@@ -623,7 +672,8 @@ var PostgresStore = class {
623
672
  at = i.at,
624
673
  retry = -1,
625
674
  leased_by = NULL,
626
- leased_until = NULL
675
+ leased_until = NULL,
676
+ deferred_at = NULL
627
677
  FROM input i
628
678
  WHERE s.stream = i.stream AND s.leased_by = i.by
629
679
  RETURNING s.stream, s.source, s.at, i.by, s.retry, i.lagging, s.lane
@@ -654,7 +704,7 @@ var PostgresStore = class {
654
704
  * @returns Blocked leases.
655
705
  */
656
706
  async block(leases) {
657
- const client = await this._pool.connect();
707
+ const client = await this._client("block");
658
708
  try {
659
709
  await client.query("BEGIN");
660
710
  const { rows } = await client.query(
@@ -664,7 +714,7 @@ var PostgresStore = class {
664
714
  AS x(stream text, by text, error text, lagging boolean)
665
715
  )
666
716
  UPDATE ${this._fqs} AS s
667
- SET blocked = true, error = i.error
717
+ SET blocked = true, error = i.error, deferred_at = NULL
668
718
  FROM input i
669
719
  WHERE s.stream = i.stream AND s.leased_by = i.by AND s.blocked = false
670
720
  RETURNING s.stream, s.source, s.at, i.by, s.retry, s.error, i.lagging, s.lane
@@ -690,6 +740,33 @@ var PostgresStore = class {
690
740
  client.release();
691
741
  }
692
742
  }
743
+ /**
744
+ * Hold the matched streams out of {@link claim} until `deferred_at`
745
+ * (ms since epoch) — see {@link Store.defer}. Persists `deferred_at`
746
+ * (as a `timestamptz`) so the skip is honored by every competing
747
+ * worker; `claim` filters on `deferred_at <= NOW()`. Accepts an
748
+ * explicit list of names or a {@link StreamFilter}, mirroring
749
+ * {@link reset}/{@link prioritize}. Cleared by ack/block/reset/unblock.
750
+ *
751
+ * @returns Count of streams whose `deferred_at` was set.
752
+ */
753
+ async defer(input, deferred_at) {
754
+ const set_clause = `SET deferred_at = to_timestamp($1 / 1000.0), retry = -1`;
755
+ if (Array.isArray(input)) {
756
+ if (!input.length) return 0;
757
+ const { rowCount: rowCount2 } = await this._pool.query(
758
+ `UPDATE ${this._fqs} ${set_clause} WHERE stream = ANY($2)`,
759
+ [deferred_at, input]
760
+ );
761
+ return rowCount2 ?? 0;
762
+ }
763
+ const { clause, values } = this._filter_clause(input, 2);
764
+ const { rowCount } = await this._pool.query(
765
+ `UPDATE ${this._fqs} ${set_clause} WHERE ${clause}`,
766
+ [deferred_at, ...values]
767
+ );
768
+ return rowCount ?? 0;
769
+ }
693
770
  /**
694
771
  * Reset watermarks for the given streams to -1, clearing retry, blocked,
695
772
  * error, and lease state so they can be replayed from the beginning.
@@ -733,7 +810,7 @@ var PostgresStore = class {
733
810
  }
734
811
  async reset(input) {
735
812
  const set_clause = `SET at = -1, retry = -1, blocked = false, error = NULL,
736
- leased_by = NULL, leased_until = NULL`;
813
+ leased_by = NULL, leased_until = NULL, deferred_at = NULL`;
737
814
  if (Array.isArray(input)) {
738
815
  if (!input.length) return 0;
739
816
  const { rowCount: rowCount2 } = await this._pool.query(
@@ -766,7 +843,7 @@ var PostgresStore = class {
766
843
  */
767
844
  async unblock(input) {
768
845
  const set_clause = `SET retry = -1, blocked = false, error = NULL,
769
- leased_by = NULL, leased_until = NULL`;
846
+ leased_by = NULL, leased_until = NULL, deferred_at = NULL`;
770
847
  if (Array.isArray(input)) {
771
848
  if (!input.length) return 0;
772
849
  const { rowCount: rowCount2 } = await this._pool.query(
@@ -858,7 +935,7 @@ var PostgresStore = class {
858
935
  if (conditions.length) sql += " WHERE " + conditions.join(" AND ");
859
936
  values.push(limit);
860
937
  sql += ` ORDER BY stream LIMIT $${values.length}`;
861
- const client = await this._pool.connect();
938
+ const client = await this._client("query_streams");
862
939
  try {
863
940
  const [streamsResult, maxResult] = await Promise.all([
864
941
  client.query(sql, values),
@@ -1088,7 +1165,7 @@ var PostgresStore = class {
1088
1165
  */
1089
1166
  async _subscribe_notifications(handler) {
1090
1167
  await this._teardown_listen();
1091
- const client = await this._pool.connect();
1168
+ const client = await this._client("notify");
1092
1169
  const on_notification = (msg) => {
1093
1170
  if (msg.channel !== this._channel) return;
1094
1171
  if (!msg.payload) return;
@@ -1149,7 +1226,7 @@ var PostgresStore = class {
1149
1226
  async truncate(targets) {
1150
1227
  if (!targets.length) return /* @__PURE__ */ new Map();
1151
1228
  const streams = targets.map((t) => t.stream);
1152
- const client = await this._pool.connect();
1229
+ const client = await this._client("truncate");
1153
1230
  try {
1154
1231
  await client.query("BEGIN");
1155
1232
  await client.query(`DELETE FROM ${this._fqs} WHERE stream = ANY($1)`, [
@@ -1200,7 +1277,7 @@ var PostgresStore = class {
1200
1277
  * from 1. `created` is preserved verbatim from the source.
1201
1278
  */
1202
1279
  async restore(driver) {
1203
- const client = await this._pool.connect();
1280
+ const client = await this._client("restore");
1204
1281
  try {
1205
1282
  await client.query("BEGIN");
1206
1283
  await client.query(