actly 1.1.0 → 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.
- package/LICENSE +0 -0
- package/README.md +262 -232
- package/dist/core/act.cjs +505 -39
- package/dist/core/act.d.ts +81 -10
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +504 -40
- package/dist/core/act.js.map +1 -1
- package/dist/core/executor.cjs +21 -9
- package/dist/core/executor.d.ts +31 -6
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +21 -9
- package/dist/core/executor.js.map +1 -1
- package/dist/core/health.cjs +34 -0
- package/dist/core/health.d.ts +18 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +28 -0
- package/dist/core/health.js.map +1 -0
- package/dist/core/shutdown.cjs +60 -0
- package/dist/core/shutdown.d.ts +8 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +56 -0
- package/dist/core/shutdown.js.map +1 -0
- package/dist/core/tenant.cjs +71 -0
- package/dist/core/tenant.d.ts +29 -0
- package/dist/core/tenant.d.ts.map +1 -0
- package/dist/core/tenant.js +68 -0
- package/dist/core/tenant.js.map +1 -0
- package/dist/errors.cjs +182 -0
- package/dist/errors.d.ts +151 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +171 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.cjs +56 -6
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -4
- package/dist/index.js.map +1 -1
- package/dist/observability.cjs +45 -0
- package/dist/observability.d.ts +145 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +43 -0
- package/dist/observability.js.map +1 -0
- package/dist/policies/bulkhead.cjs +97 -0
- package/dist/policies/bulkhead.d.ts +3 -0
- package/dist/policies/bulkhead.d.ts.map +1 -0
- package/dist/policies/bulkhead.js +95 -0
- package/dist/policies/bulkhead.js.map +1 -0
- package/dist/policies/cache.cjs +223 -22
- package/dist/policies/cache.d.ts +42 -7
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +223 -22
- package/dist/policies/cache.js.map +1 -1
- package/dist/policies/circuitBreaker.cjs +85 -0
- package/dist/policies/circuitBreaker.d.ts +3 -0
- package/dist/policies/circuitBreaker.d.ts.map +1 -0
- package/dist/policies/circuitBreaker.js +83 -0
- package/dist/policies/circuitBreaker.js.map +1 -0
- package/dist/policies/dedupe.cjs +98 -21
- package/dist/policies/dedupe.d.ts +20 -13
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +98 -21
- package/dist/policies/dedupe.js.map +1 -1
- package/dist/policies/rateLimit.cjs +35 -0
- package/dist/policies/rateLimit.d.ts +3 -0
- package/dist/policies/rateLimit.d.ts.map +1 -0
- package/dist/policies/rateLimit.js +33 -0
- package/dist/policies/rateLimit.js.map +1 -0
- package/dist/policies/retry.cjs +103 -26
- package/dist/policies/retry.d.ts +25 -2
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +103 -26
- package/dist/policies/retry.js.map +1 -1
- package/dist/policies/timeout.cjs +78 -36
- package/dist/policies/timeout.d.ts +7 -18
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +84 -31
- package/dist/policies/timeout.js.map +1 -1
- package/dist/state/store.cjs +9 -2
- package/dist/state/store.d.ts +9 -0
- package/dist/state/store.d.ts.map +1 -1
- package/dist/state/store.js +9 -2
- package/dist/state/store.js.map +1 -1
- package/dist/stores/base.cjs +4 -4
- package/dist/stores/base.d.ts +32 -56
- package/dist/stores/base.d.ts.map +1 -1
- package/dist/stores/base.js +4 -4
- package/dist/stores/base.js.map +1 -1
- package/dist/stores/memory.cjs +161 -39
- package/dist/stores/memory.d.ts +64 -24
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +160 -39
- package/dist/stores/memory.js.map +1 -1
- package/dist/types/index.d.ts +315 -37
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/abort.cjs +212 -0
- package/dist/utils/abort.d.ts +79 -0
- package/dist/utils/abort.d.ts.map +1 -0
- package/dist/utils/abort.js +206 -0
- package/dist/utils/abort.js.map +1 -0
- package/dist/utils/abortPool.cjs +26 -0
- package/dist/utils/abortPool.d.ts +4 -0
- package/dist/utils/abortPool.d.ts.map +1 -0
- package/dist/utils/abortPool.js +22 -0
- package/dist/utils/abortPool.js.map +1 -0
- package/dist/utils/backoff.cjs +54 -0
- package/dist/utils/backoff.d.ts +14 -0
- package/dist/utils/backoff.d.ts.map +1 -0
- package/dist/utils/backoff.js +52 -0
- package/dist/utils/backoff.js.map +1 -0
- package/dist/utils/key.cjs +77 -0
- package/dist/utils/key.d.ts +29 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +75 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/limits.cjs +40 -0
- package/dist/utils/limits.d.ts +39 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +38 -0
- package/dist/utils/limits.js.map +1 -0
- package/dist/utils/sanitize.cjs +29 -0
- package/dist/utils/sanitize.d.ts +3 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +26 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/validate.cjs +153 -0
- package/dist/utils/validate.d.ts +26 -0
- package/dist/utils/validate.d.ts.map +1 -0
- package/dist/utils/validate.js +141 -0
- package/dist/utils/validate.js.map +1 -0
- package/package.json +20 -37
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose multiple AbortSignals into one. Aborts when ANY input aborts,
|
|
3
|
+
* with the same reason.
|
|
4
|
+
*
|
|
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.
|
|
8
|
+
*
|
|
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.
|
|
14
|
+
*/
|
|
15
|
+
export declare function anySignal(signals: ReadonlyArray<AbortSignal>): AbortSignal;
|
|
16
|
+
/**
|
|
17
|
+
* Race a promise against an AbortSignal.
|
|
18
|
+
*
|
|
19
|
+
* - If the signal is already aborted, rejects immediately with `signal.reason`.
|
|
20
|
+
* - If the signal aborts while the promise is pending, rejects with `signal.reason`.
|
|
21
|
+
* - If the promise settles first, returns its value (or rejects with its error).
|
|
22
|
+
*
|
|
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.
|
|
28
|
+
*
|
|
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`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Sleep for `ms` milliseconds, but abort early if `signal` fires.
|
|
39
|
+
*
|
|
40
|
+
* Resolves normally on timer expiry. Rejects with `signal.reason` if the
|
|
41
|
+
* signal aborts before the timer fires. If the signal is already aborted
|
|
42
|
+
* when called, rejects synchronously (in microtask).
|
|
43
|
+
*
|
|
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.
|
|
49
|
+
*/
|
|
50
|
+
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* True if `err` is an `AbortError` (DOMException name or Error name).
|
|
53
|
+
*
|
|
54
|
+
* The default `shouldRetry` predicate uses this to skip retrying on
|
|
55
|
+
* cancellations — if the caller aborted, retrying would just abort again.
|
|
56
|
+
*/
|
|
57
|
+
export declare function isAbortError(err: unknown): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Link a parent signal to a child controller: when the parent aborts, the
|
|
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
|
|
72
|
+
*
|
|
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.
|
|
77
|
+
*/
|
|
78
|
+
export declare function linkSignal(parent: AbortSignal, child: AbortController): () => void;
|
|
79
|
+
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// ─── AbortSignal helpers ──────────────────────────────────────────────────────
|
|
2
|
+
//
|
|
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.
|
|
12
|
+
/**
|
|
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.
|
|
19
|
+
*
|
|
20
|
+
* # Listener safety
|
|
21
|
+
*
|
|
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.
|
|
25
|
+
*/
|
|
26
|
+
export function anySignal(signals) {
|
|
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).
|
|
37
|
+
const native = AbortSignal.any;
|
|
38
|
+
if (typeof native === 'function') {
|
|
39
|
+
return native.call(AbortSignal, filtered);
|
|
40
|
+
}
|
|
41
|
+
// Polyfill: only used when native is missing. Listener-safe.
|
|
42
|
+
const controller = new AbortController();
|
|
43
|
+
const listeners = [];
|
|
44
|
+
for (const signal of filtered) {
|
|
45
|
+
if (signal.aborted) {
|
|
46
|
+
controller.abort(signal.reason);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
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));
|
|
58
|
+
}
|
|
59
|
+
return controller.signal;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Race a promise against an AbortSignal.
|
|
63
|
+
*
|
|
64
|
+
* - If the signal is already aborted, rejects immediately with `signal.reason`.
|
|
65
|
+
* - If the signal aborts while the promise is pending, rejects with `signal.reason`.
|
|
66
|
+
* - If the promise settles first, returns its value (or rejects with its error).
|
|
67
|
+
*
|
|
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.
|
|
73
|
+
*
|
|
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`.
|
|
80
|
+
*/
|
|
81
|
+
export function raceAbort(promise, signal) {
|
|
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(() => { });
|
|
87
|
+
return Promise.reject(signal.reason);
|
|
88
|
+
}
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let settled = false;
|
|
91
|
+
const onAbort = () => {
|
|
92
|
+
if (settled)
|
|
93
|
+
return;
|
|
94
|
+
settled = true;
|
|
95
|
+
signal.removeEventListener('abort', onAbort);
|
|
96
|
+
reject(signal.reason);
|
|
97
|
+
};
|
|
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);
|
|
101
|
+
promise.then((value) => {
|
|
102
|
+
if (settled)
|
|
103
|
+
return;
|
|
104
|
+
settled = true;
|
|
105
|
+
signal.removeEventListener('abort', onAbort);
|
|
106
|
+
resolve(value);
|
|
107
|
+
}, (error) => {
|
|
108
|
+
if (settled)
|
|
109
|
+
return;
|
|
110
|
+
settled = true;
|
|
111
|
+
signal.removeEventListener('abort', onAbort);
|
|
112
|
+
reject(error);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Sleep for `ms` milliseconds, but abort early if `signal` fires.
|
|
118
|
+
*
|
|
119
|
+
* Resolves normally on timer expiry. Rejects with `signal.reason` if the
|
|
120
|
+
* signal aborts before the timer fires. If the signal is already aborted
|
|
121
|
+
* when called, rejects synchronously (in microtask).
|
|
122
|
+
*
|
|
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.
|
|
128
|
+
*/
|
|
129
|
+
export function sleep(ms, signal) {
|
|
130
|
+
if (ms <= 0) {
|
|
131
|
+
if (signal?.aborted)
|
|
132
|
+
return Promise.reject(signal.reason);
|
|
133
|
+
return Promise.resolve();
|
|
134
|
+
}
|
|
135
|
+
if (signal?.aborted)
|
|
136
|
+
return Promise.reject(signal.reason);
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
const timer = setTimeout(() => {
|
|
139
|
+
signal?.removeEventListener('abort', onAbort);
|
|
140
|
+
resolve();
|
|
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.
|
|
148
|
+
const onAbort = () => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
reject(signal.reason);
|
|
151
|
+
};
|
|
152
|
+
signal?.addEventListener('abort', onAbort);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* True if `err` is an `AbortError` (DOMException name or Error name).
|
|
157
|
+
*
|
|
158
|
+
* The default `shouldRetry` predicate uses this to skip retrying on
|
|
159
|
+
* cancellations — if the caller aborted, retrying would just abort again.
|
|
160
|
+
*/
|
|
161
|
+
export function isAbortError(err) {
|
|
162
|
+
if (err == null || typeof err !== 'object')
|
|
163
|
+
return false;
|
|
164
|
+
const name = err.name;
|
|
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' &&
|
|
170
|
+
err instanceof Error &&
|
|
171
|
+
typeof DOMException !== 'undefined' &&
|
|
172
|
+
err instanceof DOMException) {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Link a parent signal to a child controller: when the parent aborts, the
|
|
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
|
|
191
|
+
*
|
|
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.
|
|
196
|
+
*/
|
|
197
|
+
export function linkSignal(parent, child) {
|
|
198
|
+
if (parent.aborted) {
|
|
199
|
+
child.abort(parent.reason);
|
|
200
|
+
return () => { };
|
|
201
|
+
}
|
|
202
|
+
const onAbort = () => child.abort(parent.reason);
|
|
203
|
+
parent.addEventListener('abort', onAbort);
|
|
204
|
+
return () => parent.removeEventListener('abort', onAbort);
|
|
205
|
+
}
|
|
206
|
+
//# sourceMappingURL=abort.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 @@
|
|
|
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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeDelay = computeDelay;
|
|
4
|
+
/**
|
|
5
|
+
* Compute the delay before the next retry attempt, applying backoff, cap, and
|
|
6
|
+
* jitter in that order.
|
|
7
|
+
*
|
|
8
|
+
* Order matters:
|
|
9
|
+
* 1. `backoff` grows the base delay geometrically/linearly.
|
|
10
|
+
* 2. `maxDelay` caps the result (prevents exponential blowup).
|
|
11
|
+
* 3. `jitter` randomises within `[0, delay]` (prevents thundering herd).
|
|
12
|
+
*
|
|
13
|
+
* Returns 0 if `delayMs` is 0 or undefined — skipping the sleep entirely.
|
|
14
|
+
*/
|
|
15
|
+
function computeDelay(attempt, opts) {
|
|
16
|
+
const base = opts.delayMs ?? 0;
|
|
17
|
+
if (base === 0)
|
|
18
|
+
return 0;
|
|
19
|
+
// Step 1: backoff
|
|
20
|
+
let delay;
|
|
21
|
+
switch (opts.backoff ?? 'none') {
|
|
22
|
+
case 'linear':
|
|
23
|
+
delay = base * attempt;
|
|
24
|
+
break;
|
|
25
|
+
case 'exponential':
|
|
26
|
+
delay = base * 2 ** (attempt - 1);
|
|
27
|
+
break;
|
|
28
|
+
default: delay = base;
|
|
29
|
+
}
|
|
30
|
+
// Step 2: cap (guard against Infinity and NaN before Math.min)
|
|
31
|
+
const max = opts.maxDelay ?? Number.POSITIVE_INFINITY;
|
|
32
|
+
if (!Number.isFinite(delay))
|
|
33
|
+
delay = max;
|
|
34
|
+
delay = Math.min(delay, max);
|
|
35
|
+
// Step 3: jitter
|
|
36
|
+
// All jitter variants produce a delay in [0, delay].
|
|
37
|
+
// The `decorrelated` formula `base + random() * (delay - base)` assumes
|
|
38
|
+
// delay >= base, which can be violated when maxDelay caps below base.
|
|
39
|
+
// Fix: clamp the final result to [0, delay] to guarantee the cap holds.
|
|
40
|
+
switch (opts.jitter ?? 'full') {
|
|
41
|
+
case 'none': return delay;
|
|
42
|
+
case 'full': return Math.random() * delay;
|
|
43
|
+
case 'equal': return delay / 2 + Math.random() * delay / 2;
|
|
44
|
+
case 'decorrelated': {
|
|
45
|
+
// If delay < base (maxDelay capped below base), decorrelated degrades
|
|
46
|
+
// to full jitter — the formula would otherwise produce values > delay.
|
|
47
|
+
const lo = Math.min(base, delay);
|
|
48
|
+
const hi = delay;
|
|
49
|
+
const result = lo + Math.random() * (hi - lo);
|
|
50
|
+
return Math.max(0, Math.min(result, delay));
|
|
51
|
+
}
|
|
52
|
+
default: return delay;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RetryOptions } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compute the delay before the next retry attempt, applying backoff, cap, and
|
|
4
|
+
* jitter in that order.
|
|
5
|
+
*
|
|
6
|
+
* Order matters:
|
|
7
|
+
* 1. `backoff` grows the base delay geometrically/linearly.
|
|
8
|
+
* 2. `maxDelay` caps the result (prevents exponential blowup).
|
|
9
|
+
* 3. `jitter` randomises within `[0, delay]` (prevents thundering herd).
|
|
10
|
+
*
|
|
11
|
+
* Returns 0 if `delayMs` is 0 or undefined — skipping the sleep entirely.
|
|
12
|
+
*/
|
|
13
|
+
export declare function computeDelay(attempt: number, opts: RetryOptions): number;
|
|
14
|
+
//# sourceMappingURL=backoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.d.ts","sourceRoot":"","sources":["../../src/utils/backoff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAErD;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,MAAM,CAoCxE"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the delay before the next retry attempt, applying backoff, cap, and
|
|
3
|
+
* jitter in that order.
|
|
4
|
+
*
|
|
5
|
+
* Order matters:
|
|
6
|
+
* 1. `backoff` grows the base delay geometrically/linearly.
|
|
7
|
+
* 2. `maxDelay` caps the result (prevents exponential blowup).
|
|
8
|
+
* 3. `jitter` randomises within `[0, delay]` (prevents thundering herd).
|
|
9
|
+
*
|
|
10
|
+
* Returns 0 if `delayMs` is 0 or undefined — skipping the sleep entirely.
|
|
11
|
+
*/
|
|
12
|
+
export function computeDelay(attempt, opts) {
|
|
13
|
+
const base = opts.delayMs ?? 0;
|
|
14
|
+
if (base === 0)
|
|
15
|
+
return 0;
|
|
16
|
+
// Step 1: backoff
|
|
17
|
+
let delay;
|
|
18
|
+
switch (opts.backoff ?? 'none') {
|
|
19
|
+
case 'linear':
|
|
20
|
+
delay = base * attempt;
|
|
21
|
+
break;
|
|
22
|
+
case 'exponential':
|
|
23
|
+
delay = base * 2 ** (attempt - 1);
|
|
24
|
+
break;
|
|
25
|
+
default: delay = base;
|
|
26
|
+
}
|
|
27
|
+
// Step 2: cap (guard against Infinity and NaN before Math.min)
|
|
28
|
+
const max = opts.maxDelay ?? Number.POSITIVE_INFINITY;
|
|
29
|
+
if (!Number.isFinite(delay))
|
|
30
|
+
delay = max;
|
|
31
|
+
delay = Math.min(delay, max);
|
|
32
|
+
// Step 3: jitter
|
|
33
|
+
// All jitter variants produce a delay in [0, delay].
|
|
34
|
+
// The `decorrelated` formula `base + random() * (delay - base)` assumes
|
|
35
|
+
// delay >= base, which can be violated when maxDelay caps below base.
|
|
36
|
+
// Fix: clamp the final result to [0, delay] to guarantee the cap holds.
|
|
37
|
+
switch (opts.jitter ?? 'full') {
|
|
38
|
+
case 'none': return delay;
|
|
39
|
+
case 'full': return Math.random() * delay;
|
|
40
|
+
case 'equal': return delay / 2 + Math.random() * delay / 2;
|
|
41
|
+
case 'decorrelated': {
|
|
42
|
+
// If delay < base (maxDelay capped below base), decorrelated degrades
|
|
43
|
+
// to full jitter — the formula would otherwise produce values > delay.
|
|
44
|
+
const lo = Math.min(base, delay);
|
|
45
|
+
const hi = delay;
|
|
46
|
+
const result = lo + Math.random() * (hi - lo);
|
|
47
|
+
return Math.max(0, Math.min(result, delay));
|
|
48
|
+
}
|
|
49
|
+
default: return delay;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=backoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backoff.js","sourceRoot":"","sources":["../../src/utils/backoff.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,IAAkB;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,CAAA;IAC9B,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,CAAC,CAAA;IAExB,kBAAkB;IAClB,IAAI,KAAa,CAAA;IACjB,QAAQ,IAAI,CAAC,OAAO,IAAI,MAAM,EAAE,CAAC;QAC/B,KAAK,QAAQ;YAAO,KAAK,GAAG,IAAI,GAAG,OAAO,CAAC;YAAC,MAAK;QACjD,KAAK,aAAa;YAAE,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;YAAC,MAAK;QAC5D,OAAO,CAAC,CAAY,KAAK,GAAG,IAAI,CAAA;IAClC,CAAC;IAED,+DAA+D;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,MAAM,CAAC,iBAAiB,CAAA;IACrD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,KAAK,GAAG,GAAG,CAAA;IACxC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;IAE5B,iBAAiB;IACjB,qDAAqD;IACrD,wEAAwE;IACxE,sEAAsE;IACtE,wEAAwE;IACxE,QAAQ,IAAI,CAAC,MAAM,IAAI,MAAM,EAAE,CAAC;QAC9B,KAAK,MAAM,CAAC,CAAS,OAAO,KAAK,CAAA;QACjC,KAAK,MAAM,CAAC,CAAS,OAAO,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,CAAA;QACjD,KAAK,OAAO,CAAC,CAAQ,OAAO,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,KAAK,GAAG,CAAC,CAAA;QACjE,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,sEAAsE;YACtE,uEAAuE;YACvE,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;YAChC,MAAM,EAAE,GAAG,KAAK,CAAA;YAChB,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,CAAA;YAC7C,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;QAC7C,CAAC;QACD,OAAO,CAAC,CAAa,OAAO,KAAK,CAAA;IACnC,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Key sanitisation — the first line of defence against:
|
|
4
|
+
*
|
|
5
|
+
* - **Prototype pollution**: `__proto__`, `constructor`, `prototype` as keys
|
|
6
|
+
* bypass namespace isolation in plain-object store adapters.
|
|
7
|
+
* - **Control-char injection**: `\x00`–`\x1f`, `\x7f` break some store
|
|
8
|
+
* backends (Redis Lua, HTTP header logs, JSON-LD contexts).
|
|
9
|
+
* - **CRLF injection**: `\r\n` in keys can break naive log/HTTP serializers.
|
|
10
|
+
* - **Memory exhaustion**: unbounded key length lets a caller store a 10MB
|
|
11
|
+
* string as a key, bloating the store and blocking iteration.
|
|
12
|
+
* - **Namespace collision**: user keys starting with `dedupe:`, `cache:`,
|
|
13
|
+
* or `__inflight:` would clobber internal slots.
|
|
14
|
+
*
|
|
15
|
+
* # Why a dedicated module?
|
|
16
|
+
*
|
|
17
|
+
* Key validation is security-sensitive. Centralising it here means every
|
|
18
|
+
* call site (`act()`, `invalidate()`, `withStore().invalidate()`) applies
|
|
19
|
+
* the same rules. Local inlining risks drift.
|
|
20
|
+
*/
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.sanitizeKey = sanitizeKey;
|
|
23
|
+
const limits_js_1 = require("./limits.js");
|
|
24
|
+
const RESERVED_PREFIXES = ['dedupe:', 'cache:', '__inflight:', '__tenant:', 'tenant:'];
|
|
25
|
+
/**
|
|
26
|
+
* Strings that, if used as Map keys, are safe — but if used as plain-object
|
|
27
|
+
* keys (e.g. a naive store adapter) enable prototype pollution. Reject them
|
|
28
|
+
* regardless of store type: the contract is "your key is safe everywhere".
|
|
29
|
+
*/
|
|
30
|
+
const FORBIDDEN_LITERALS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
31
|
+
/**
|
|
32
|
+
* Reject any character in the C0 control range, DEL (0x7f), or CRLF.
|
|
33
|
+
* Allow TAB (0x09) and LF (0x0a) since some callers embed newlines in
|
|
34
|
+
* structured keys legitimately — but CR is always forbidden.
|
|
35
|
+
*/
|
|
36
|
+
const UNSAFE_CHAR = /[\x00-\x08\x0b\x0c\x0d\x0e-\x1f\x7f]/;
|
|
37
|
+
/**
|
|
38
|
+
* Validate a user-supplied key. Throws synchronously on invalid input.
|
|
39
|
+
*
|
|
40
|
+
* Programmer errors throw — they must not be swallowed into an `ActFailure`
|
|
41
|
+
* because the caller's code is broken.
|
|
42
|
+
*
|
|
43
|
+
* @returns the same `key` (for chaining); never transforms it.
|
|
44
|
+
*/
|
|
45
|
+
function sanitizeKey(key) {
|
|
46
|
+
if (typeof key !== 'string') {
|
|
47
|
+
throw new TypeError(`Actly: key must be a string, got ${typeName(key)}`);
|
|
48
|
+
}
|
|
49
|
+
if (key.length === 0) {
|
|
50
|
+
throw new RangeError("Actly: key must be non-empty. An empty key collapses every caller " +
|
|
51
|
+
"onto the same dedupe/cache slot — almost certainly a bug.");
|
|
52
|
+
}
|
|
53
|
+
if (key.length > limits_js_1.LIMITS.MAX_KEY_LENGTH) {
|
|
54
|
+
throw new RangeError(`Actly: key length ${key.length} exceeds limit ${limits_js_1.LIMITS.MAX_KEY_LENGTH}. ` +
|
|
55
|
+
`Long keys bloat stores and slow iteration. Hash externally if you need longer keys.`);
|
|
56
|
+
}
|
|
57
|
+
if (FORBIDDEN_LITERALS.has(key)) {
|
|
58
|
+
throw new RangeError(`Actly: key ${JSON.stringify(key)} is forbidden (prototype-pollution vector). ` +
|
|
59
|
+
`Pick a different key.`);
|
|
60
|
+
}
|
|
61
|
+
if (UNSAFE_CHAR.test(key)) {
|
|
62
|
+
throw new RangeError(`Actly: key contains control characters or CRLF, which break store backends ` +
|
|
63
|
+
`and log serializers. Got ${JSON.stringify(key)}.`);
|
|
64
|
+
}
|
|
65
|
+
for (const prefix of RESERVED_PREFIXES) {
|
|
66
|
+
if (key.startsWith(prefix)) {
|
|
67
|
+
throw new RangeError(`Actly: key must not start with reserved prefix "${prefix}" ` +
|
|
68
|
+
`(got ${JSON.stringify(key)}). These namespaces are used internally.`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return key;
|
|
72
|
+
}
|
|
73
|
+
function typeName(v) {
|
|
74
|
+
if (v === null)
|
|
75
|
+
return 'null';
|
|
76
|
+
return typeof v;
|
|
77
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Key sanitisation — the first line of defence against:
|
|
3
|
+
*
|
|
4
|
+
* - **Prototype pollution**: `__proto__`, `constructor`, `prototype` as keys
|
|
5
|
+
* bypass namespace isolation in plain-object store adapters.
|
|
6
|
+
* - **Control-char injection**: `\x00`–`\x1f`, `\x7f` break some store
|
|
7
|
+
* backends (Redis Lua, HTTP header logs, JSON-LD contexts).
|
|
8
|
+
* - **CRLF injection**: `\r\n` in keys can break naive log/HTTP serializers.
|
|
9
|
+
* - **Memory exhaustion**: unbounded key length lets a caller store a 10MB
|
|
10
|
+
* string as a key, bloating the store and blocking iteration.
|
|
11
|
+
* - **Namespace collision**: user keys starting with `dedupe:`, `cache:`,
|
|
12
|
+
* or `__inflight:` would clobber internal slots.
|
|
13
|
+
*
|
|
14
|
+
* # Why a dedicated module?
|
|
15
|
+
*
|
|
16
|
+
* Key validation is security-sensitive. Centralising it here means every
|
|
17
|
+
* call site (`act()`, `invalidate()`, `withStore().invalidate()`) applies
|
|
18
|
+
* the same rules. Local inlining risks drift.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Validate a user-supplied key. Throws synchronously on invalid input.
|
|
22
|
+
*
|
|
23
|
+
* Programmer errors throw — they must not be swallowed into an `ActFailure`
|
|
24
|
+
* because the caller's code is broken.
|
|
25
|
+
*
|
|
26
|
+
* @returns the same `key` (for chaining); never transforms it.
|
|
27
|
+
*/
|
|
28
|
+
export declare function sanitizeKey(key: string): string;
|
|
29
|
+
//# sourceMappingURL=key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"key.d.ts","sourceRoot":"","sources":["../../src/utils/key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAoBH;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAqC/C"}
|