@epztickets/common 1.82.0 → 1.83.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.
@@ -13,6 +13,12 @@ export declare abstract class JetStreamListener<T extends Event> {
13
13
  constructor(js: JetStreamClient, jsm: JetStreamManager);
14
14
  listen(): Promise<void>;
15
15
  private ensureConsumer;
16
+ /**
17
+ * Ask JetStream to redeliver this message later (NAK with backoff).
18
+ * Use for "not ready yet" (replica missing) and transient failures.
19
+ * Safe to call from handlers whose tests pass a bare { ack } mock.
20
+ */
21
+ protected defer(msg: JsMsg, reason?: string): void;
16
22
  abstract onMessage(data: T["data"], msg: JsMsg): Promise<void>;
17
23
  }
18
24
  export {};
@@ -18,6 +18,20 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.JetStreamListener = void 0;
20
20
  const nats_1 = require("nats");
21
+ // Must match max_deliver in ensureConsumer below.
22
+ const MAX_DELIVER = 5;
23
+ // Retry backoff for defer(): 2s, 4s, 8s, 16s (capped at 30s).
24
+ const BASE_RETRY_MS = 2000;
25
+ const MAX_RETRY_MS = 30000;
26
+ // ACK CONTRACT for onMessage implementations:
27
+ // - Return normally → the message is ACKed (done, never redelivered).
28
+ // This is true even if you didn't call msg.ack() yourself.
29
+ // - Want a retry (replica not caught up, transient DB/SMTP error)?
30
+ // `return this.defer(msg)` — NAKs with backoff so JetStream
31
+ // redelivers. Just returning without msg.ack() does NOT retry.
32
+ // - Throwing also retries (the base class defers for you).
33
+ // After MAX_DELIVER attempts JetStream stops redelivering; defer()
34
+ // logs an error on the final attempt so a dropped event is visible.
21
35
  class JetStreamListener {
22
36
  constructor(js, jsm) {
23
37
  this.js = js;
@@ -42,10 +56,13 @@ class JetStreamListener {
42
56
  try {
43
57
  const data = JSON.parse(msg.data.toString());
44
58
  yield this.onMessage(data, msg);
59
+ // No-op if onMessage already acked or deferred (nats ignores a
60
+ // second ack/nak on the same message).
45
61
  msg.ack();
46
62
  }
47
63
  catch (err) {
48
- console.error("Listener error", err);
64
+ console.error(`[${this.durableName}] Listener error`, err);
65
+ this.defer(msg);
49
66
  }
50
67
  }
51
68
  }
@@ -72,10 +89,29 @@ class JetStreamListener {
72
89
  filter_subject: this.subject,
73
90
  ack_policy: nats_1.AckPolicy.Explicit,
74
91
  deliver_policy: nats_1.DeliverPolicy.All,
75
- max_deliver: 5,
92
+ max_deliver: MAX_DELIVER,
76
93
  });
77
94
  }
78
95
  });
79
96
  }
97
+ /**
98
+ * Ask JetStream to redeliver this message later (NAK with backoff).
99
+ * Use for "not ready yet" (replica missing) and transient failures.
100
+ * Safe to call from handlers whose tests pass a bare { ack } mock.
101
+ */
102
+ defer(msg, reason) {
103
+ var _a, _b;
104
+ const attempt = (_b = (_a = msg === null || msg === void 0 ? void 0 : msg.info) === null || _a === void 0 ? void 0 : _a.deliveryCount) !== null && _b !== void 0 ? _b : 1;
105
+ const delayMs = Math.min(BASE_RETRY_MS * 2 ** (attempt - 1), MAX_RETRY_MS);
106
+ const why = reason ? ` (${reason})` : "";
107
+ if (attempt >= MAX_DELIVER) {
108
+ console.error(`[${this.durableName}] giving up on ${this.subject} after ${attempt} attempts${why} — event will NOT be redelivered`);
109
+ }
110
+ else {
111
+ console.warn(`[${this.durableName}] deferring ${this.subject}${why}; attempt ${attempt}/${MAX_DELIVER}, retry in ${delayMs}ms`);
112
+ }
113
+ if (typeof (msg === null || msg === void 0 ? void 0 : msg.nak) === "function")
114
+ msg.nak(delayMs);
115
+ }
80
116
  }
81
117
  exports.JetStreamListener = JetStreamListener;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epztickets/common",
3
- "version": "1.82.0",
3
+ "version": "1.83.0",
4
4
  "main": "./build/index.js",
5
5
  "types": "./build/index.d.ts",
6
6
  "files": [