actly 1.1.5 → 1.2.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.
Files changed (119) hide show
  1. package/README.md +232 -434
  2. package/dist/core/act.cjs +353 -35
  3. package/dist/core/act.d.ts.map +1 -1
  4. package/dist/core/act.js +355 -37
  5. package/dist/core/act.js.map +1 -1
  6. package/dist/core/executor.cjs +2 -0
  7. package/dist/core/executor.d.ts +6 -1
  8. package/dist/core/executor.d.ts.map +1 -1
  9. package/dist/core/executor.js +2 -0
  10. package/dist/core/executor.js.map +1 -1
  11. package/dist/core/health.cjs +34 -0
  12. package/dist/core/health.d.ts +18 -0
  13. package/dist/core/health.d.ts.map +1 -0
  14. package/dist/core/health.js +28 -0
  15. package/dist/core/health.js.map +1 -0
  16. package/dist/core/shutdown.cjs +60 -0
  17. package/dist/core/shutdown.d.ts +8 -0
  18. package/dist/core/shutdown.d.ts.map +1 -0
  19. package/dist/core/shutdown.js +56 -0
  20. package/dist/core/shutdown.js.map +1 -0
  21. package/dist/core/tenant.cjs +71 -0
  22. package/dist/core/tenant.d.ts +29 -0
  23. package/dist/core/tenant.d.ts.map +1 -0
  24. package/dist/core/tenant.js +68 -0
  25. package/dist/core/tenant.js.map +1 -0
  26. package/dist/errors.cjs +182 -0
  27. package/dist/errors.d.ts +151 -0
  28. package/dist/errors.d.ts.map +1 -0
  29. package/dist/errors.js +171 -0
  30. package/dist/errors.js.map +1 -0
  31. package/dist/index.cjs +44 -4
  32. package/dist/index.d.ts +17 -2
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +17 -1
  35. package/dist/index.js.map +1 -1
  36. package/dist/observability.cjs +45 -0
  37. package/dist/observability.d.ts +145 -0
  38. package/dist/observability.d.ts.map +1 -0
  39. package/dist/observability.js +43 -0
  40. package/dist/observability.js.map +1 -0
  41. package/dist/policies/bulkhead.cjs +97 -0
  42. package/dist/policies/bulkhead.d.ts +3 -0
  43. package/dist/policies/bulkhead.d.ts.map +1 -0
  44. package/dist/policies/bulkhead.js +95 -0
  45. package/dist/policies/bulkhead.js.map +1 -0
  46. package/dist/policies/cache.cjs +168 -31
  47. package/dist/policies/cache.d.ts +17 -4
  48. package/dist/policies/cache.d.ts.map +1 -1
  49. package/dist/policies/cache.js +168 -31
  50. package/dist/policies/cache.js.map +1 -1
  51. package/dist/policies/circuitBreaker.cjs +85 -0
  52. package/dist/policies/circuitBreaker.d.ts +3 -0
  53. package/dist/policies/circuitBreaker.d.ts.map +1 -0
  54. package/dist/policies/circuitBreaker.js +83 -0
  55. package/dist/policies/circuitBreaker.js.map +1 -0
  56. package/dist/policies/dedupe.cjs +74 -45
  57. package/dist/policies/dedupe.d.ts +10 -28
  58. package/dist/policies/dedupe.d.ts.map +1 -1
  59. package/dist/policies/dedupe.js +74 -45
  60. package/dist/policies/dedupe.js.map +1 -1
  61. package/dist/policies/rateLimit.cjs +35 -0
  62. package/dist/policies/rateLimit.d.ts +3 -0
  63. package/dist/policies/rateLimit.d.ts.map +1 -0
  64. package/dist/policies/rateLimit.js +33 -0
  65. package/dist/policies/rateLimit.js.map +1 -0
  66. package/dist/policies/retry.cjs +45 -8
  67. package/dist/policies/retry.d.ts.map +1 -1
  68. package/dist/policies/retry.js +45 -8
  69. package/dist/policies/retry.js.map +1 -1
  70. package/dist/policies/timeout.cjs +25 -53
  71. package/dist/policies/timeout.d.ts +2 -33
  72. package/dist/policies/timeout.d.ts.map +1 -1
  73. package/dist/policies/timeout.js +31 -48
  74. package/dist/policies/timeout.js.map +1 -1
  75. package/dist/state/store.cjs +3 -3
  76. package/dist/state/store.d.ts +3 -3
  77. package/dist/state/store.js +3 -3
  78. package/dist/stores/memory.cjs +126 -53
  79. package/dist/stores/memory.d.ts +35 -16
  80. package/dist/stores/memory.d.ts.map +1 -1
  81. package/dist/stores/memory.js +125 -53
  82. package/dist/stores/memory.js.map +1 -1
  83. package/dist/types/index.d.ts +176 -3
  84. package/dist/types/index.d.ts.map +1 -1
  85. package/dist/utils/abort.cjs +108 -38
  86. package/dist/utils/abort.d.ts +43 -19
  87. package/dist/utils/abort.d.ts.map +1 -1
  88. package/dist/utils/abort.js +108 -38
  89. package/dist/utils/abort.js.map +1 -1
  90. package/dist/utils/abortPool.cjs +26 -0
  91. package/dist/utils/abortPool.d.ts +4 -0
  92. package/dist/utils/abortPool.d.ts.map +1 -0
  93. package/dist/utils/abortPool.js +22 -0
  94. package/dist/utils/abortPool.js.map +1 -0
  95. package/dist/utils/backoff.cjs +12 -1
  96. package/dist/utils/backoff.d.ts.map +1 -1
  97. package/dist/utils/backoff.js +12 -1
  98. package/dist/utils/backoff.js.map +1 -1
  99. package/dist/utils/key.cjs +77 -0
  100. package/dist/utils/key.d.ts +29 -0
  101. package/dist/utils/key.d.ts.map +1 -0
  102. package/dist/utils/key.js +75 -0
  103. package/dist/utils/key.js.map +1 -0
  104. package/dist/utils/limits.cjs +40 -0
  105. package/dist/utils/limits.d.ts +39 -0
  106. package/dist/utils/limits.d.ts.map +1 -0
  107. package/dist/utils/limits.js +38 -0
  108. package/dist/utils/limits.js.map +1 -0
  109. package/dist/utils/sanitize.cjs +29 -0
  110. package/dist/utils/sanitize.d.ts +3 -0
  111. package/dist/utils/sanitize.d.ts.map +1 -0
  112. package/dist/utils/sanitize.js +26 -0
  113. package/dist/utils/sanitize.js.map +1 -0
  114. package/dist/utils/validate.cjs +90 -16
  115. package/dist/utils/validate.d.ts +11 -0
  116. package/dist/utils/validate.d.ts.map +1 -1
  117. package/dist/utils/validate.js +85 -16
  118. package/dist/utils/validate.js.map +1 -1
  119. package/package.json +6 -5
@@ -1,10 +1,15 @@
1
1
  "use strict";
2
2
  // ─── AbortSignal helpers ──────────────────────────────────────────────────────
3
3
  //
4
- // Centralised utilities for composing AbortSignals. These exist because
5
- // Node 18 lacks `AbortSignal.any` (added in Node 20) and we want to keep
6
- // the `engines` floor at 18 for backwards compatibility with existing
7
- // consumers.
4
+ // Contract: zero listener accumulation on long-lived AbortSignals.
5
+ //
6
+ // Every `addEventListener('abort', ...)` MUST be paired with a
7
+ // `removeEventListener` on the success path, OR use `AbortSignal.any()`
8
+ // (Node 20+ native) which handles cleanup automatically.
9
+ //
10
+ // Long-lived signals (server shutdown, request pool) must not accumulate
11
+ // one listener per `act()` call — that would cause
12
+ // `MaxListenersExceededWarning` and unbounded closure retention.
8
13
  Object.defineProperty(exports, "__esModule", { value: true });
9
14
  exports.anySignal = anySignal;
10
15
  exports.raceAbort = raceAbort;
@@ -12,29 +17,51 @@ exports.sleep = sleep;
12
17
  exports.isAbortError = isAbortError;
13
18
  exports.linkSignal = linkSignal;
14
19
  /**
15
- * Polyfill for `AbortSignal.any(signals)` (Node 20+).
20
+ * Compose multiple AbortSignals into one. Aborts when ANY input aborts,
21
+ * with the same reason.
22
+ *
23
+ * Uses native `AbortSignal.any()` on Node 20+ (zero allocations, no
24
+ * listener leaks — the runtime owns the lifecycle). Falls back to a
25
+ * polyfill that explicitly removes listeners on first abort.
16
26
  *
17
- * Returns a single signal that aborts when ANY of the input signals aborts,
18
- * with the same reason. If any input is already aborted, the returned signal
19
- * is aborted synchronously.
27
+ * # Listener safety
20
28
  *
21
- * Listener registration is `{ once: true }` once any signal fires, we stop
22
- * listening on the others. The composite signal cannot be "un-aborted".
29
+ * The polyfill registers `{ once: true }` listeners on each input and
30
+ * manually removes the others when one fires. After settlement, the
31
+ * composite signal holds zero references to the inputs — they may be GC'd.
23
32
  */
24
33
  function anySignal(signals) {
25
- // Fast path: native implementation (Node 20+, modern browsers, Bun).
26
- // The cast is safe the runtime check guards the call.
34
+ // Filter out undefined / null defensively.
35
+ const filtered = signals.filter((s) => s != null);
36
+ if (filtered.length === 0) {
37
+ // No inputs → never-aborting signal. Use a fresh controller so callers
38
+ // get a real AbortSignal, not a hand-rolled mock.
39
+ return new AbortController().signal;
40
+ }
41
+ if (filtered.length === 1)
42
+ return filtered[0];
43
+ // Native path (Node 20+, modern browsers, Bun).
27
44
  const native = AbortSignal.any;
28
- if (typeof native === 'function')
29
- return native.call(AbortSignal, signals);
30
- // Polyfill for Node 18.
45
+ if (typeof native === 'function') {
46
+ return native.call(AbortSignal, filtered);
47
+ }
48
+ // Polyfill: only used when native is missing. Listener-safe.
31
49
  const controller = new AbortController();
32
- for (const signal of signals) {
50
+ const listeners = [];
51
+ for (const signal of filtered) {
33
52
  if (signal.aborted) {
34
53
  controller.abort(signal.reason);
35
54
  break;
36
55
  }
37
- signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
56
+ const onAbort = () => {
57
+ controller.abort(signal.reason);
58
+ // Remove all other listeners — they hold closures over `signal`
59
+ // and would otherwise keep the inputs alive past settlement.
60
+ for (const off of listeners)
61
+ off();
62
+ };
63
+ signal.addEventListener('abort', onAbort, { once: true });
64
+ listeners.push(() => signal.removeEventListener('abort', onAbort));
38
65
  }
39
66
  return controller.signal;
40
67
  }
@@ -45,34 +72,50 @@ function anySignal(signals) {
45
72
  * - If the signal aborts while the promise is pending, rejects with `signal.reason`.
46
73
  * - If the promise settles first, returns its value (or rejects with its error).
47
74
  *
48
- * The listener is registered with `{ once: true }` and never leaks: either
49
- * the signal fires (listener auto-removed) or the promise settles (the
50
- * signal will eventually be GC'd along with the listener).
75
+ * # Listener safety
76
+ *
77
+ * On success path, the abort listener is explicitly removed. A
78
+ * `{ once: true }` listener would stay attached on long-lived signals,
79
+ * leaking one closure per call.
51
80
  *
52
- * Used by `dedupePolicy` so joiners can cancel their own `await` even if the
53
- * originator's `fn` is still running.
81
+ * # Mark-as-handled
82
+ *
83
+ * When the signal aborts first, the original `promise` may still settle
84
+ * later (success or failure). We attach a no-op `.catch` to it so V8
85
+ * doesn't emit an `unhandledRejection` warning. The error is NOT
86
+ * swallowed from the caller — the caller already received `signal.reason`.
54
87
  */
55
88
  function raceAbort(promise, signal) {
56
- if (signal.aborted)
89
+ if (signal.aborted) {
90
+ // Mark the promise as handled — its eventual rejection won't surface
91
+ // as an unhandled rejection. Its eventual success is dropped silently,
92
+ // which is correct: the caller has already moved on.
93
+ promise.catch(() => { });
57
94
  return Promise.reject(signal.reason);
95
+ }
58
96
  return new Promise((resolve, reject) => {
59
97
  let settled = false;
60
98
  const onAbort = () => {
61
99
  if (settled)
62
100
  return;
63
101
  settled = true;
102
+ signal.removeEventListener('abort', onAbort);
64
103
  reject(signal.reason);
65
104
  };
66
- signal.addEventListener('abort', onAbort, { once: true });
105
+ // Not using `{ once: true }` — we want explicit removal on success
106
+ // path so the listener doesn't linger on a long-lived signal.
107
+ signal.addEventListener('abort', onAbort);
67
108
  promise.then((value) => {
68
109
  if (settled)
69
110
  return;
70
111
  settled = true;
112
+ signal.removeEventListener('abort', onAbort);
71
113
  resolve(value);
72
114
  }, (error) => {
73
115
  if (settled)
74
116
  return;
75
117
  settled = true;
118
+ signal.removeEventListener('abort', onAbort);
76
119
  reject(error);
77
120
  });
78
121
  });
@@ -84,9 +127,11 @@ function raceAbort(promise, signal) {
84
127
  * signal aborts before the timer fires. If the signal is already aborted
85
128
  * when called, rejects synchronously (in microtask).
86
129
  *
87
- * Used by `retryPolicy` to make backoff delays interruptible: when an outer
88
- * `totalTimeout` fires mid-delay, the delay rejects immediately instead of
89
- * blocking the retry loop until the timer would have elapsed.
130
+ * # Timer hygiene
131
+ *
132
+ * The internal `setTimeout` is `unref`'d on Node so it doesn't keep the
133
+ * event loop alive solely for this sleep. On browsers there is no
134
+ * equivalent — the timer is short-lived enough not to matter.
90
135
  */
91
136
  function sleep(ms, signal) {
92
137
  if (ms <= 0) {
@@ -101,11 +146,17 @@ function sleep(ms, signal) {
101
146
  signal?.removeEventListener('abort', onAbort);
102
147
  resolve();
103
148
  }, ms);
149
+ // NOTE: do NOT `unref()` this timer. Unlike InMemoryStore's autoCleanup
150
+ // interval (which is a background housekeeping task), `sleep()` IS the
151
+ // operation the caller is awaiting. If we unref'd it, Node could exit
152
+ // the process while a retry delay was pending — silently dropping the
153
+ // operation. The timer is short-lived (cleared in onAbort or after ms),
154
+ // so the cost of keeping the loop alive is bounded.
104
155
  const onAbort = () => {
105
156
  clearTimeout(timer);
106
157
  reject(signal.reason);
107
158
  };
108
- signal?.addEventListener('abort', onAbort, { once: true });
159
+ signal?.addEventListener('abort', onAbort);
109
160
  });
110
161
  }
111
162
  /**
@@ -118,25 +169,44 @@ function isAbortError(err) {
118
169
  if (err == null || typeof err !== 'object')
119
170
  return false;
120
171
  const name = err.name;
121
- return name === 'AbortError' || name === 'TimeoutError' &&
172
+ if (name === 'AbortError')
173
+ return true;
174
+ // DOMException with name 'TimeoutError' is what AbortSignal.timeout throws.
175
+ // Distinguish from our own TimeoutError class by checking for DOMException.
176
+ if (name === 'TimeoutError' &&
122
177
  err instanceof Error &&
123
- // DOMException with name 'TimeoutError' is what AbortSignal.timeout throws.
124
- // Distinguish from our own TimeoutError class by checking for DOMException.
125
178
  typeof DOMException !== 'undefined' &&
126
- err instanceof DOMException;
179
+ err instanceof DOMException) {
180
+ return true;
181
+ }
182
+ return false;
127
183
  }
128
184
  /**
129
185
  * Link a parent signal to a child controller: when the parent aborts, the
130
- * child is aborted with the same reason. No-op if the parent is already
131
- * aborted (the caller should check `parent.aborted` separately if it cares
132
- * about synchronous abort).
186
+ * child is aborted with the same reason.
187
+ *
188
+ * # Contract
189
+ *
190
+ * Returns an `unlink()` function that removes the listener. Callers MUST
191
+ * call `unlink()` on success path — otherwise the listener stays attached
192
+ * to the parent forever, leaking one closure per call.
193
+ *
194
+ * If the parent is already aborted, the child is aborted synchronously
195
+ * and `unlink` is a no-op.
196
+ *
197
+ * # Prefer `anySignal()` instead
133
198
  *
134
- * The listener is `{ once: true }` no leak.
199
+ * For new code, prefer `anySignal([parent, timeoutSignal])` which uses
200
+ * the native Node 20+ implementation and handles cleanup automatically.
201
+ * This function exists for call sites that need an `AbortController`
202
+ * (not just a signal) — e.g. to layer additional abort sources.
135
203
  */
136
204
  function linkSignal(parent, child) {
137
205
  if (parent.aborted) {
138
206
  child.abort(parent.reason);
139
- return;
207
+ return () => { };
140
208
  }
141
- parent.addEventListener('abort', () => child.abort(parent.reason), { once: true });
209
+ const onAbort = () => child.abort(parent.reason);
210
+ parent.addEventListener('abort', onAbort);
211
+ return () => parent.removeEventListener('abort', onAbort);
142
212
  }
@@ -1,12 +1,16 @@
1
1
  /**
2
- * Polyfill for `AbortSignal.any(signals)` (Node 20+).
2
+ * Compose multiple AbortSignals into one. Aborts when ANY input aborts,
3
+ * with the same reason.
3
4
  *
4
- * Returns a single signal that aborts when ANY of the input signals aborts,
5
- * with the same reason. If any input is already aborted, the returned signal
6
- * is aborted synchronously.
5
+ * Uses native `AbortSignal.any()` on Node 20+ (zero allocations, no
6
+ * listener leaks the runtime owns the lifecycle). Falls back to a
7
+ * polyfill that explicitly removes listeners on first abort.
7
8
  *
8
- * Listener registration is `{ once: true }` — once any signal fires, we stop
9
- * listening on the others. The composite signal cannot be "un-aborted".
9
+ * # Listener safety
10
+ *
11
+ * The polyfill registers `{ once: true }` listeners on each input and
12
+ * manually removes the others when one fires. After settlement, the
13
+ * composite signal holds zero references to the inputs — they may be GC'd.
10
14
  */
11
15
  export declare function anySignal(signals: ReadonlyArray<AbortSignal>): AbortSignal;
12
16
  /**
@@ -16,12 +20,18 @@ export declare function anySignal(signals: ReadonlyArray<AbortSignal>): AbortSig
16
20
  * - If the signal aborts while the promise is pending, rejects with `signal.reason`.
17
21
  * - If the promise settles first, returns its value (or rejects with its error).
18
22
  *
19
- * The listener is registered with `{ once: true }` and never leaks: either
20
- * the signal fires (listener auto-removed) or the promise settles (the
21
- * signal will eventually be GC'd along with the listener).
23
+ * # Listener safety
24
+ *
25
+ * On success path, the abort listener is explicitly removed. A
26
+ * `{ once: true }` listener would stay attached on long-lived signals,
27
+ * leaking one closure per call.
22
28
  *
23
- * Used by `dedupePolicy` so joiners can cancel their own `await` even if the
24
- * originator's `fn` is still running.
29
+ * # Mark-as-handled
30
+ *
31
+ * When the signal aborts first, the original `promise` may still settle
32
+ * later (success or failure). We attach a no-op `.catch` to it so V8
33
+ * doesn't emit an `unhandledRejection` warning. The error is NOT
34
+ * swallowed from the caller — the caller already received `signal.reason`.
25
35
  */
26
36
  export declare function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>;
27
37
  /**
@@ -31,9 +41,11 @@ export declare function raceAbort<T>(promise: Promise<T>, signal: AbortSignal):
31
41
  * signal aborts before the timer fires. If the signal is already aborted
32
42
  * when called, rejects synchronously (in microtask).
33
43
  *
34
- * Used by `retryPolicy` to make backoff delays interruptible: when an outer
35
- * `totalTimeout` fires mid-delay, the delay rejects immediately instead of
36
- * blocking the retry loop until the timer would have elapsed.
44
+ * # Timer hygiene
45
+ *
46
+ * The internal `setTimeout` is `unref`'d on Node so it doesn't keep the
47
+ * event loop alive solely for this sleep. On browsers there is no
48
+ * equivalent — the timer is short-lived enough not to matter.
37
49
  */
38
50
  export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
39
51
  /**
@@ -45,11 +57,23 @@ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
45
57
  export declare function isAbortError(err: unknown): boolean;
46
58
  /**
47
59
  * Link a parent signal to a child controller: when the parent aborts, the
48
- * child is aborted with the same reason. No-op if the parent is already
49
- * aborted (the caller should check `parent.aborted` separately if it cares
50
- * about synchronous abort).
60
+ * child is aborted with the same reason.
61
+ *
62
+ * # Contract
63
+ *
64
+ * Returns an `unlink()` function that removes the listener. Callers MUST
65
+ * call `unlink()` on success path — otherwise the listener stays attached
66
+ * to the parent forever, leaking one closure per call.
67
+ *
68
+ * If the parent is already aborted, the child is aborted synchronously
69
+ * and `unlink` is a no-op.
70
+ *
71
+ * # Prefer `anySignal()` instead
51
72
  *
52
- * The listener is `{ once: true }` no leak.
73
+ * For new code, prefer `anySignal([parent, timeoutSignal])` which uses
74
+ * the native Node 20+ implementation and handles cleanup automatically.
75
+ * This function exists for call sites that need an `AbortController`
76
+ * (not just a signal) — e.g. to layer additional abort sources.
53
77
  */
54
- export declare function linkSignal(parent: AbortSignal, child: AbortController): void;
78
+ export declare function linkSignal(parent: AbortSignal, child: AbortController): () => void;
55
79
  //# sourceMappingURL=abort.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAOA;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,WAAW,CAwB1E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,CAAC,CAAC,CA2BZ;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBrE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CASlD;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,eAAe,GACrB,IAAI,CAUN"}
1
+ {"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAYA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,WAAW,CAsC1E;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,CAAC,CAAC,CAsCZ;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BrE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAelD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,eAAe,GACrB,MAAM,IAAI,CAQZ"}
@@ -1,33 +1,60 @@
1
1
  // ─── AbortSignal helpers ──────────────────────────────────────────────────────
2
2
  //
3
- // Centralised utilities for composing AbortSignals. These exist because
4
- // Node 18 lacks `AbortSignal.any` (added in Node 20) and we want to keep
5
- // the `engines` floor at 18 for backwards compatibility with existing
6
- // consumers.
3
+ // Contract: zero listener accumulation on long-lived AbortSignals.
4
+ //
5
+ // Every `addEventListener('abort', ...)` MUST be paired with a
6
+ // `removeEventListener` on the success path, OR use `AbortSignal.any()`
7
+ // (Node 20+ native) which handles cleanup automatically.
8
+ //
9
+ // Long-lived signals (server shutdown, request pool) must not accumulate
10
+ // one listener per `act()` call — that would cause
11
+ // `MaxListenersExceededWarning` and unbounded closure retention.
7
12
  /**
8
- * Polyfill for `AbortSignal.any(signals)` (Node 20+).
13
+ * Compose multiple AbortSignals into one. Aborts when ANY input aborts,
14
+ * with the same reason.
15
+ *
16
+ * Uses native `AbortSignal.any()` on Node 20+ (zero allocations, no
17
+ * listener leaks — the runtime owns the lifecycle). Falls back to a
18
+ * polyfill that explicitly removes listeners on first abort.
9
19
  *
10
- * Returns a single signal that aborts when ANY of the input signals aborts,
11
- * with the same reason. If any input is already aborted, the returned signal
12
- * is aborted synchronously.
20
+ * # Listener safety
13
21
  *
14
- * Listener registration is `{ once: true }` once any signal fires, we stop
15
- * listening on the others. The composite signal cannot be "un-aborted".
22
+ * The polyfill registers `{ once: true }` listeners on each input and
23
+ * manually removes the others when one fires. After settlement, the
24
+ * composite signal holds zero references to the inputs — they may be GC'd.
16
25
  */
17
26
  export function anySignal(signals) {
18
- // Fast path: native implementation (Node 20+, modern browsers, Bun).
19
- // The cast is safe the runtime check guards the call.
27
+ // Filter out undefined / null defensively.
28
+ const filtered = signals.filter((s) => s != null);
29
+ if (filtered.length === 0) {
30
+ // No inputs → never-aborting signal. Use a fresh controller so callers
31
+ // get a real AbortSignal, not a hand-rolled mock.
32
+ return new AbortController().signal;
33
+ }
34
+ if (filtered.length === 1)
35
+ return filtered[0];
36
+ // Native path (Node 20+, modern browsers, Bun).
20
37
  const native = AbortSignal.any;
21
- if (typeof native === 'function')
22
- return native.call(AbortSignal, signals);
23
- // Polyfill for Node 18.
38
+ if (typeof native === 'function') {
39
+ return native.call(AbortSignal, filtered);
40
+ }
41
+ // Polyfill: only used when native is missing. Listener-safe.
24
42
  const controller = new AbortController();
25
- for (const signal of signals) {
43
+ const listeners = [];
44
+ for (const signal of filtered) {
26
45
  if (signal.aborted) {
27
46
  controller.abort(signal.reason);
28
47
  break;
29
48
  }
30
- signal.addEventListener('abort', () => controller.abort(signal.reason), { once: true });
49
+ const onAbort = () => {
50
+ controller.abort(signal.reason);
51
+ // Remove all other listeners — they hold closures over `signal`
52
+ // and would otherwise keep the inputs alive past settlement.
53
+ for (const off of listeners)
54
+ off();
55
+ };
56
+ signal.addEventListener('abort', onAbort, { once: true });
57
+ listeners.push(() => signal.removeEventListener('abort', onAbort));
31
58
  }
32
59
  return controller.signal;
33
60
  }
@@ -38,34 +65,50 @@ export function anySignal(signals) {
38
65
  * - If the signal aborts while the promise is pending, rejects with `signal.reason`.
39
66
  * - If the promise settles first, returns its value (or rejects with its error).
40
67
  *
41
- * The listener is registered with `{ once: true }` and never leaks: either
42
- * the signal fires (listener auto-removed) or the promise settles (the
43
- * signal will eventually be GC'd along with the listener).
68
+ * # Listener safety
69
+ *
70
+ * On success path, the abort listener is explicitly removed. A
71
+ * `{ once: true }` listener would stay attached on long-lived signals,
72
+ * leaking one closure per call.
44
73
  *
45
- * Used by `dedupePolicy` so joiners can cancel their own `await` even if the
46
- * originator's `fn` is still running.
74
+ * # Mark-as-handled
75
+ *
76
+ * When the signal aborts first, the original `promise` may still settle
77
+ * later (success or failure). We attach a no-op `.catch` to it so V8
78
+ * doesn't emit an `unhandledRejection` warning. The error is NOT
79
+ * swallowed from the caller — the caller already received `signal.reason`.
47
80
  */
48
81
  export function raceAbort(promise, signal) {
49
- if (signal.aborted)
82
+ if (signal.aborted) {
83
+ // Mark the promise as handled — its eventual rejection won't surface
84
+ // as an unhandled rejection. Its eventual success is dropped silently,
85
+ // which is correct: the caller has already moved on.
86
+ promise.catch(() => { });
50
87
  return Promise.reject(signal.reason);
88
+ }
51
89
  return new Promise((resolve, reject) => {
52
90
  let settled = false;
53
91
  const onAbort = () => {
54
92
  if (settled)
55
93
  return;
56
94
  settled = true;
95
+ signal.removeEventListener('abort', onAbort);
57
96
  reject(signal.reason);
58
97
  };
59
- signal.addEventListener('abort', onAbort, { once: true });
98
+ // Not using `{ once: true }` — we want explicit removal on success
99
+ // path so the listener doesn't linger on a long-lived signal.
100
+ signal.addEventListener('abort', onAbort);
60
101
  promise.then((value) => {
61
102
  if (settled)
62
103
  return;
63
104
  settled = true;
105
+ signal.removeEventListener('abort', onAbort);
64
106
  resolve(value);
65
107
  }, (error) => {
66
108
  if (settled)
67
109
  return;
68
110
  settled = true;
111
+ signal.removeEventListener('abort', onAbort);
69
112
  reject(error);
70
113
  });
71
114
  });
@@ -77,9 +120,11 @@ export function raceAbort(promise, signal) {
77
120
  * signal aborts before the timer fires. If the signal is already aborted
78
121
  * when called, rejects synchronously (in microtask).
79
122
  *
80
- * Used by `retryPolicy` to make backoff delays interruptible: when an outer
81
- * `totalTimeout` fires mid-delay, the delay rejects immediately instead of
82
- * blocking the retry loop until the timer would have elapsed.
123
+ * # Timer hygiene
124
+ *
125
+ * The internal `setTimeout` is `unref`'d on Node so it doesn't keep the
126
+ * event loop alive solely for this sleep. On browsers there is no
127
+ * equivalent — the timer is short-lived enough not to matter.
83
128
  */
84
129
  export function sleep(ms, signal) {
85
130
  if (ms <= 0) {
@@ -94,11 +139,17 @@ export function sleep(ms, signal) {
94
139
  signal?.removeEventListener('abort', onAbort);
95
140
  resolve();
96
141
  }, ms);
142
+ // NOTE: do NOT `unref()` this timer. Unlike InMemoryStore's autoCleanup
143
+ // interval (which is a background housekeeping task), `sleep()` IS the
144
+ // operation the caller is awaiting. If we unref'd it, Node could exit
145
+ // the process while a retry delay was pending — silently dropping the
146
+ // operation. The timer is short-lived (cleared in onAbort or after ms),
147
+ // so the cost of keeping the loop alive is bounded.
97
148
  const onAbort = () => {
98
149
  clearTimeout(timer);
99
150
  reject(signal.reason);
100
151
  };
101
- signal?.addEventListener('abort', onAbort, { once: true });
152
+ signal?.addEventListener('abort', onAbort);
102
153
  });
103
154
  }
104
155
  /**
@@ -111,26 +162,45 @@ export function isAbortError(err) {
111
162
  if (err == null || typeof err !== 'object')
112
163
  return false;
113
164
  const name = err.name;
114
- return name === 'AbortError' || name === 'TimeoutError' &&
165
+ if (name === 'AbortError')
166
+ return true;
167
+ // DOMException with name 'TimeoutError' is what AbortSignal.timeout throws.
168
+ // Distinguish from our own TimeoutError class by checking for DOMException.
169
+ if (name === 'TimeoutError' &&
115
170
  err instanceof Error &&
116
- // DOMException with name 'TimeoutError' is what AbortSignal.timeout throws.
117
- // Distinguish from our own TimeoutError class by checking for DOMException.
118
171
  typeof DOMException !== 'undefined' &&
119
- err instanceof DOMException;
172
+ err instanceof DOMException) {
173
+ return true;
174
+ }
175
+ return false;
120
176
  }
121
177
  /**
122
178
  * Link a parent signal to a child controller: when the parent aborts, the
123
- * child is aborted with the same reason. No-op if the parent is already
124
- * aborted (the caller should check `parent.aborted` separately if it cares
125
- * about synchronous abort).
179
+ * child is aborted with the same reason.
180
+ *
181
+ * # Contract
182
+ *
183
+ * Returns an `unlink()` function that removes the listener. Callers MUST
184
+ * call `unlink()` on success path — otherwise the listener stays attached
185
+ * to the parent forever, leaking one closure per call.
186
+ *
187
+ * If the parent is already aborted, the child is aborted synchronously
188
+ * and `unlink` is a no-op.
189
+ *
190
+ * # Prefer `anySignal()` instead
126
191
  *
127
- * The listener is `{ once: true }` no leak.
192
+ * For new code, prefer `anySignal([parent, timeoutSignal])` which uses
193
+ * the native Node 20+ implementation and handles cleanup automatically.
194
+ * This function exists for call sites that need an `AbortController`
195
+ * (not just a signal) — e.g. to layer additional abort sources.
128
196
  */
129
197
  export function linkSignal(parent, child) {
130
198
  if (parent.aborted) {
131
199
  child.abort(parent.reason);
132
- return;
200
+ return () => { };
133
201
  }
134
- parent.addEventListener('abort', () => child.abort(parent.reason), { once: true });
202
+ const onAbort = () => child.abort(parent.reason);
203
+ parent.addEventListener('abort', onAbort);
204
+ return () => parent.removeEventListener('abort', onAbort);
135
205
  }
136
206
  //# sourceMappingURL=abort.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.acquireController = acquireController;
4
+ exports.releaseController = releaseController;
5
+ exports.poolSize = poolSize;
6
+ const pool = [];
7
+ const MAX_POOL_SIZE = 64;
8
+ function acquireController() {
9
+ while (pool.length > 0) {
10
+ const c = pool.pop();
11
+ if (!c.signal.aborted) {
12
+ return c;
13
+ }
14
+ }
15
+ return new AbortController();
16
+ }
17
+ function releaseController(c) {
18
+ if (c.signal.aborted)
19
+ return;
20
+ if (pool.length >= MAX_POOL_SIZE)
21
+ return;
22
+ pool.push(c);
23
+ }
24
+ function poolSize() {
25
+ return pool.length;
26
+ }
@@ -0,0 +1,4 @@
1
+ export declare function acquireController(): AbortController;
2
+ export declare function releaseController(c: AbortController): void;
3
+ export declare function poolSize(): number;
4
+ //# sourceMappingURL=abortPool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abortPool.d.ts","sourceRoot":"","sources":["../../src/utils/abortPool.ts"],"names":[],"mappings":"AAGA,wBAAgB,iBAAiB,IAAI,eAAe,CAQnD;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,eAAe,GAAG,IAAI,CAI1D;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAEjC"}
@@ -0,0 +1,22 @@
1
+ const pool = [];
2
+ const MAX_POOL_SIZE = 64;
3
+ export function acquireController() {
4
+ while (pool.length > 0) {
5
+ const c = pool.pop();
6
+ if (!c.signal.aborted) {
7
+ return c;
8
+ }
9
+ }
10
+ return new AbortController();
11
+ }
12
+ export function releaseController(c) {
13
+ if (c.signal.aborted)
14
+ return;
15
+ if (pool.length >= MAX_POOL_SIZE)
16
+ return;
17
+ pool.push(c);
18
+ }
19
+ export function poolSize() {
20
+ return pool.length;
21
+ }
22
+ //# sourceMappingURL=abortPool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abortPool.js","sourceRoot":"","sources":["../../src/utils/abortPool.ts"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAsB,EAAE,CAAA;AAClC,MAAM,aAAa,GAAG,EAAE,CAAA;AAExB,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAG,CAAA;QACrB,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACtB,OAAO,CAAC,CAAA;QACV,CAAC;IACH,CAAC;IACD,OAAO,IAAI,eAAe,EAAE,CAAA;AAC9B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,CAAkB;IAClD,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO;QAAE,OAAM;IAC5B,IAAI,IAAI,CAAC,MAAM,IAAI,aAAa;QAAE,OAAM;IACxC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACd,CAAC;AAED,MAAM,UAAU,QAAQ;IACtB,OAAO,IAAI,CAAC,MAAM,CAAA;AACpB,CAAC"}