actly 1.1.5 → 1.3.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/CHANGELOG.md +234 -0
- package/MIGRATION.md +141 -0
- package/README.md +334 -428
- package/SECURITY.md +56 -0
- package/dist/core/act.cjs +630 -126
- package/dist/core/act.d.ts +6 -84
- package/dist/core/act.d.ts.map +1 -1
- package/dist/core/act.js +630 -129
- package/dist/core/executor.cjs +1 -26
- package/dist/core/executor.d.ts +2 -38
- package/dist/core/executor.d.ts.map +1 -1
- package/dist/core/executor.js +1 -27
- package/dist/core/health.cjs +163 -0
- package/dist/core/health.d.ts +33 -0
- package/dist/core/health.d.ts.map +1 -0
- package/dist/core/health.js +150 -0
- package/dist/core/shutdown.cjs +71 -0
- package/dist/core/shutdown.d.ts +5 -0
- package/dist/core/shutdown.d.ts.map +1 -0
- package/dist/core/shutdown.js +65 -0
- package/dist/core/tenant.cjs +113 -0
- package/dist/core/tenant.d.ts +24 -0
- package/dist/core/tenant.d.ts.map +1 -0
- package/dist/core/tenant.js +109 -0
- package/dist/errors.cjs +149 -0
- package/dist/errors.d.ts +94 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +134 -0
- package/dist/index.cjs +51 -8
- package/dist/index.d.ts +22 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/observability.cjs +6 -0
- package/dist/observability.d.ts +86 -0
- package/dist/observability.d.ts.map +1 -0
- package/dist/observability.js +3 -0
- package/dist/policies/bulkhead.cjs +137 -0
- package/dist/policies/bulkhead.d.ts +3 -0
- package/dist/policies/bulkhead.d.ts.map +1 -0
- package/dist/policies/bulkhead.js +134 -0
- package/dist/policies/cache.cjs +84 -67
- package/dist/policies/cache.d.ts +0 -33
- package/dist/policies/cache.d.ts.map +1 -1
- package/dist/policies/cache.js +84 -68
- package/dist/policies/circuitBreaker.cjs +179 -0
- package/dist/policies/circuitBreaker.d.ts +3 -0
- package/dist/policies/circuitBreaker.d.ts.map +1 -0
- package/dist/policies/circuitBreaker.js +176 -0
- package/dist/policies/dedupe.cjs +46 -64
- package/dist/policies/dedupe.d.ts +0 -41
- package/dist/policies/dedupe.d.ts.map +1 -1
- package/dist/policies/dedupe.js +46 -65
- package/dist/policies/noop.cjs +6 -0
- package/dist/policies/noop.d.ts +3 -0
- package/dist/policies/noop.d.ts.map +1 -0
- package/dist/policies/noop.js +3 -0
- package/dist/policies/rateLimit.cjs +45 -0
- package/dist/policies/rateLimit.d.ts +3 -0
- package/dist/policies/rateLimit.d.ts.map +1 -0
- package/dist/policies/rateLimit.js +42 -0
- package/dist/policies/retry.cjs +129 -57
- package/dist/policies/retry.d.ts +0 -27
- package/dist/policies/retry.d.ts.map +1 -1
- package/dist/policies/retry.js +129 -58
- package/dist/policies/timeout.cjs +47 -103
- package/dist/policies/timeout.d.ts +2 -46
- package/dist/policies/timeout.d.ts.map +1 -1
- package/dist/policies/timeout.js +46 -102
- package/dist/stores/base.cjs +0 -10
- package/dist/stores/base.d.ts +4 -67
- package/dist/stores/base.d.ts.map +1 -1
- package/dist/stores/base.js +0 -11
- package/dist/stores/memory.cjs +147 -87
- package/dist/stores/memory.d.ts +11 -59
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +146 -88
- package/dist/testing/index.cjs +56 -0
- package/dist/testing/index.d.ts +6 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +52 -0
- package/dist/types/index.cjs +0 -1
- package/dist/types/index.d.ts +62 -179
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +0 -2
- package/dist/utils/abort.cjs +46 -73
- package/dist/utils/abort.d.ts +4 -51
- package/dist/utils/abort.d.ts.map +1 -1
- package/dist/utils/abort.js +46 -74
- package/dist/utils/abortPool.cjs +25 -0
- package/dist/utils/abortPool.d.ts +4 -0
- package/dist/utils/abortPool.d.ts.map +1 -0
- package/dist/utils/abortPool.js +20 -0
- package/dist/utils/backoff.cjs +8 -15
- package/dist/utils/backoff.d.ts +0 -11
- package/dist/utils/backoff.d.ts.map +1 -1
- package/dist/utils/backoff.js +8 -16
- package/dist/utils/decorator.cjs +44 -0
- package/dist/utils/decorator.d.ts +3 -0
- package/dist/utils/decorator.d.ts.map +1 -0
- package/dist/utils/decorator.js +41 -0
- package/dist/utils/key.cjs +45 -0
- package/dist/utils/key.d.ts +2 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +42 -0
- package/dist/utils/limits.cjs +22 -0
- package/dist/utils/limits.d.ts +21 -0
- package/dist/utils/limits.d.ts.map +1 -0
- package/dist/utils/limits.js +19 -0
- package/dist/utils/safeCall.cjs +28 -0
- package/dist/utils/safeCall.d.ts +2 -0
- package/dist/utils/safeCall.d.ts.map +1 -0
- package/dist/utils/safeCall.js +25 -0
- package/dist/utils/sanitize.cjs +50 -0
- package/dist/utils/sanitize.d.ts +3 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +46 -0
- package/dist/utils/validate.cjs +163 -26
- package/dist/utils/validate.d.ts +6 -8
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +158 -27
- package/package.json +42 -7
- package/dist/core/act.js.map +0 -1
- package/dist/core/executor.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/policies/cache.js.map +0 -1
- package/dist/policies/dedupe.js.map +0 -1
- package/dist/policies/retry.js.map +0 -1
- package/dist/policies/timeout.js.map +0 -1
- package/dist/state/store.cjs +0 -14
- package/dist/state/store.d.ts +0 -11
- package/dist/state/store.d.ts.map +0 -1
- package/dist/state/store.js +0 -11
- package/dist/state/store.js.map +0 -1
- package/dist/stores/base.js.map +0 -1
- package/dist/stores/memory.js.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/utils/abort.js.map +0 -1
- package/dist/utils/backoff.js.map +0 -1
- package/dist/utils/validate.js.map +0 -1
package/dist/types/index.d.ts
CHANGED
|
@@ -1,233 +1,116 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The async function ACT wraps.
|
|
3
|
-
*
|
|
4
|
-
* Receives an {@link AbortSignal} that fires when:
|
|
5
|
-
* - the caller aborts via `options.signal`,
|
|
6
|
-
* - the per-attempt {@link TimeoutOptions} fires,
|
|
7
|
-
* - the operation-wide {@link ActOptions.totalTimeout} fires.
|
|
8
|
-
*
|
|
9
|
-
* Cooperative cancellation: pass `signal` through to `fetch`, `AbortController`,
|
|
10
|
-
* database drivers, or any primitive that accepts one. If you ignore it, ACT
|
|
11
|
-
* will still return promptly (the outer promise rejects), but the underlying
|
|
12
|
-
* work will keep running in the background — leaking resources until it
|
|
13
|
-
* settles on its own.
|
|
14
|
-
*
|
|
15
|
-
* Backwards compatible: `() => Promise<T>` is assignable to this type, so
|
|
16
|
-
* existing call sites continue to compile and run. They simply forgo
|
|
17
|
-
* cancellation.
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // Cooperative
|
|
21
|
-
* act('user:42', async (signal) => {
|
|
22
|
-
* return fetch(`/api/users/42`, { signal })
|
|
23
|
-
* }, { timeout: { ms: 5_000 } })
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* // Legacy (still works, signal ignored)
|
|
27
|
-
* act('user:42', () => fetchUser(42))
|
|
28
|
-
*/
|
|
29
1
|
export type ActFn<T> = (signal: AbortSignal) => Promise<T> | T;
|
|
30
|
-
/** Where a successful result came from. */
|
|
31
2
|
export type ActSource = 'fresh' | 'cache';
|
|
32
3
|
export interface ActSuccess<T> {
|
|
33
4
|
ok: true;
|
|
34
5
|
value: T;
|
|
35
6
|
source: ActSource;
|
|
36
|
-
/**
|
|
37
|
-
* Number of attempts made before success.
|
|
38
|
-
*
|
|
39
|
-
* - Fresh success on first try: `1`
|
|
40
|
-
* - Fresh success after N retries: `N`
|
|
41
|
-
* - Cache hit: `0` (no work was performed)
|
|
42
|
-
* - Dedupe joiner: mirrors the originator's attempt count
|
|
43
|
-
*/
|
|
44
7
|
attempts: number;
|
|
8
|
+
traceId?: string;
|
|
9
|
+
durationMs?: number;
|
|
45
10
|
}
|
|
46
11
|
export interface ActFailure {
|
|
47
12
|
ok: false;
|
|
48
13
|
error: unknown;
|
|
49
|
-
/**
|
|
50
|
-
* Number of attempts made before final failure.
|
|
51
|
-
* For dedupe joiners: mirrors the originator's attempt count.
|
|
52
|
-
*/
|
|
53
14
|
attempts: number;
|
|
15
|
+
traceId?: string;
|
|
16
|
+
durationMs?: number;
|
|
54
17
|
}
|
|
55
18
|
export type ActResult<T> = ActSuccess<T> | ActFailure;
|
|
56
19
|
export interface RetryOptions {
|
|
57
|
-
/**
|
|
58
|
-
* Total number of attempts including the first call.
|
|
59
|
-
* Must be an integer >= 1.
|
|
60
|
-
*
|
|
61
|
-
* `attempts: 1` is a no-op (equivalent to omitting `retry`); the policy
|
|
62
|
-
* is not added to the chain. This is intentional — adding a policy that
|
|
63
|
-
* never retries is pure overhead.
|
|
64
|
-
*/
|
|
65
20
|
attempts: number;
|
|
66
|
-
/**
|
|
67
|
-
* Base delay between attempts in milliseconds. Defaults to 0 (no delay).
|
|
68
|
-
* Must be a non-negative finite number.
|
|
69
|
-
*/
|
|
70
21
|
delayMs?: number;
|
|
71
|
-
/**
|
|
72
|
-
* How the base delay grows per attempt:
|
|
73
|
-
* - `'none'` -> always `delayMs`
|
|
74
|
-
* - `'linear'` -> `delayMs * attempt`
|
|
75
|
-
* - `'exponential'` -> `delayMs * 2^(attempt-1)`
|
|
76
|
-
*
|
|
77
|
-
* The computed delay is then capped by {@link maxDelay} and jittered by
|
|
78
|
-
* {@link jitter} before being slept.
|
|
79
|
-
*
|
|
80
|
-
* Defaults to `'none'`.
|
|
81
|
-
*/
|
|
82
22
|
backoff?: 'none' | 'linear' | 'exponential';
|
|
83
|
-
|
|
84
|
-
* Hard cap on the computed delay. Defaults to `Infinity`.
|
|
85
|
-
*
|
|
86
|
-
* Without a cap, `exponential` backoff with `delayMs: 1000` and
|
|
87
|
-
* `attempts: 10` would sleep 8.5 minutes between attempts 9 and 10
|
|
88
|
-
* (256 seconds). Set `maxDelay` to something sane (e.g. 30_000) to
|
|
89
|
-
* bound worst-case latency.
|
|
90
|
-
*/
|
|
23
|
+
backoffFn?: (attempt: number, error: unknown, state: Record<string, unknown>) => number;
|
|
91
24
|
maxDelay?: number;
|
|
92
|
-
/**
|
|
93
|
-
* Jitter strategy applied to the (post-`maxDelay`) delay.
|
|
94
|
-
*
|
|
95
|
-
* - `'none'` -> no jitter, return delay as-is
|
|
96
|
-
* - `'full'` -> `random() * delay` (default; best for thundering-herd prevention)
|
|
97
|
-
* - `'equal'` -> `delay/2 + random() * delay/2`
|
|
98
|
-
* - `'decorrelated'` -> `base + random() * (delay - base)`
|
|
99
|
-
*
|
|
100
|
-
* Defaults to `'full'`. Jitter prevents synchronised retry storms when
|
|
101
|
-
* many callers fail at the same instant (e.g. after an upstream outage
|
|
102
|
-
* recovers) — without it, all callers retry on the same tick.
|
|
103
|
-
*/
|
|
104
25
|
jitter?: 'none' | 'full' | 'equal' | 'decorrelated';
|
|
105
|
-
/**
|
|
106
|
-
* Predicate called after each failure, before the next attempt.
|
|
107
|
-
* Return `false` to stop retrying immediately and surface the error.
|
|
108
|
-
*
|
|
109
|
-
* Called for every failure including the last attempt (so observers stay
|
|
110
|
-
* informed); the return value is only consulted when there are remaining
|
|
111
|
-
* attempts.
|
|
112
|
-
*
|
|
113
|
-
* Use this to skip retries for errors that are definitively non-recoverable
|
|
114
|
-
* (e.g. HTTP 4xx, AuthError, ValidationError).
|
|
115
|
-
*
|
|
116
|
-
* Default behaviour: retry on every error except `AbortError` (which
|
|
117
|
-
* indicates the caller or a timeout cancelled the operation).
|
|
118
|
-
*
|
|
119
|
-
* @param error The error thrown by the most recent attempt.
|
|
120
|
-
* @param attempt The 1-based number of the attempt that just failed.
|
|
121
|
-
*/
|
|
122
26
|
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
27
|
+
shouldRetryResult?: <V>(value: V, attempt: number) => boolean;
|
|
28
|
+
dangerouslyUnref?: boolean;
|
|
123
29
|
}
|
|
124
30
|
export interface TimeoutOptions {
|
|
125
|
-
/**
|
|
126
|
-
* Abort after this many milliseconds.
|
|
127
|
-
* Must be a positive finite number.
|
|
128
|
-
*/
|
|
129
31
|
ms: number;
|
|
32
|
+
strategy?: 'race' | 'cooperative';
|
|
130
33
|
}
|
|
131
34
|
export interface DedupeOptions {
|
|
132
|
-
/**
|
|
133
|
-
* Collapse concurrent calls sharing the same key into one in-flight Promise.
|
|
134
|
-
* Opt-in: be explicit when you want this behaviour.
|
|
135
|
-
*/
|
|
136
35
|
enabled: boolean;
|
|
137
|
-
/**
|
|
138
|
-
* Safety-net TTL for the in-flight entry, in milliseconds.
|
|
139
|
-
*
|
|
140
|
-
* If the originator's promise does not settle within this window, the
|
|
141
|
-
* entry is removed from the store so subsequent callers can start fresh.
|
|
142
|
-
* Originator's promise continues in the background until it settles or
|
|
143
|
-
* an outer timeout fires.
|
|
144
|
-
*
|
|
145
|
-
* Default: `Infinity` (no safety net). Pair with `timeout` or
|
|
146
|
-
* `totalTimeout` for proper cancellation in production.
|
|
147
|
-
*/
|
|
148
36
|
inflightTtl?: number;
|
|
149
37
|
}
|
|
150
38
|
export interface CacheOptions {
|
|
151
|
-
/** Keep a successful result for this many milliseconds. Must be > 0. */
|
|
152
39
|
ttl: number;
|
|
153
40
|
}
|
|
154
|
-
export interface ActOptions {
|
|
41
|
+
export interface ActOptions<T = unknown> {
|
|
155
42
|
retry?: RetryOptions;
|
|
156
|
-
/** Per-attempt deadline. Each retry gets a fresh clock. */
|
|
157
43
|
timeout?: TimeoutOptions;
|
|
158
|
-
/**
|
|
159
|
-
* Collapse concurrent calls with the same key into one in-flight Promise.
|
|
160
|
-
*
|
|
161
|
-
* Shorthand: `dedupe: true`
|
|
162
|
-
* Full form: `dedupe: { enabled: true, inflightTtl: 30_000 }`
|
|
163
|
-
*/
|
|
164
44
|
dedupe?: boolean | DedupeOptions;
|
|
165
45
|
cache?: CacheOptions;
|
|
166
|
-
/**
|
|
167
|
-
* Hard budget over the ENTIRE operation — including all retry attempts,
|
|
168
|
-
* delays, and the per-attempt timeout.
|
|
169
|
-
*
|
|
170
|
-
* Distinct from `timeout`, which resets the clock on every attempt.
|
|
171
|
-
* Use both together to express: "each attempt may take at most X ms,
|
|
172
|
-
* but the whole thing must finish within Y ms."
|
|
173
|
-
*
|
|
174
|
-
* Rejects with {@link TotalTimeoutError} if the budget fires.
|
|
175
|
-
*/
|
|
176
46
|
totalTimeout?: TimeoutOptions;
|
|
177
|
-
/**
|
|
178
|
-
* Caller-provided cancellation signal.
|
|
179
|
-
*
|
|
180
|
-
* When this signal aborts:
|
|
181
|
-
* - if the operation has not yet started, it rejects immediately with
|
|
182
|
-
* the signal's `reason`,
|
|
183
|
-
* - if it is in progress, the inner {@link ActFn} receives an aborted
|
|
184
|
-
* signal (cooperative cancellation),
|
|
185
|
-
* - if it has already settled, the result is returned as normal.
|
|
186
|
-
*
|
|
187
|
-
* Combined with `timeout` / `totalTimeout`, this gives you full control
|
|
188
|
-
* over cancellation from outside `act()`.
|
|
189
|
-
*/
|
|
190
47
|
signal?: AbortSignal;
|
|
48
|
+
observability?: ObservabilityHooks;
|
|
49
|
+
traceId?: string;
|
|
50
|
+
circuitBreaker?: CircuitBreakerOptions;
|
|
51
|
+
bulkhead?: BulkheadOptions;
|
|
52
|
+
rateLimit?: RateLimitOptions;
|
|
53
|
+
hedge?: HedgeOptions;
|
|
54
|
+
fallback?: FallbackOptions<T>;
|
|
55
|
+
audit?: AuditOptions;
|
|
191
56
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
57
|
+
export interface CircuitBreakerOptions {
|
|
58
|
+
threshold: number;
|
|
59
|
+
cooldownMs: number;
|
|
60
|
+
resetTimeoutMs?: number;
|
|
61
|
+
strategy?: 'consecutive' | 'count';
|
|
62
|
+
countSize?: number;
|
|
63
|
+
countThreshold?: number;
|
|
64
|
+
countMinimumCalls?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface BulkheadOptions {
|
|
67
|
+
maxConcurrent: number;
|
|
68
|
+
queueTimeoutMs?: number;
|
|
69
|
+
maxQueueSize?: number;
|
|
70
|
+
}
|
|
71
|
+
export interface RateLimitOptions {
|
|
72
|
+
maxCalls: number;
|
|
73
|
+
windowMs: number;
|
|
74
|
+
}
|
|
75
|
+
export interface HedgeOptions {
|
|
76
|
+
delayMs: number;
|
|
77
|
+
placement?: 'outside-retry' | 'inside-retry';
|
|
78
|
+
keepLoser?: boolean;
|
|
79
|
+
}
|
|
80
|
+
export interface FallbackOptions<T> {
|
|
81
|
+
value: T | (() => T | Promise<T>);
|
|
82
|
+
}
|
|
83
|
+
export interface AuditOptions {
|
|
84
|
+
log: (entry: AuditEntry) => void;
|
|
85
|
+
}
|
|
86
|
+
export interface AuditEntry {
|
|
87
|
+
key: string;
|
|
88
|
+
traceId: string;
|
|
89
|
+
timestamp: number;
|
|
90
|
+
durationMs: number;
|
|
91
|
+
ok: boolean;
|
|
92
|
+
attempts: number;
|
|
93
|
+
failedBy?: ActlyFailedBy;
|
|
94
|
+
error?: unknown;
|
|
95
|
+
}
|
|
96
|
+
export type ActlyFailedBy = 'abort' | 'timeout' | 'total-timeout' | 'retry-exhausted' | 'fn-error' | 'validation' | 'circuit-open' | 'bulkhead-full' | 'rate-limited' | 'resource-exhausted' | 'hedge-timeout';
|
|
200
97
|
export interface RunMeta {
|
|
201
98
|
attempts: number;
|
|
202
99
|
source: ActSource;
|
|
203
100
|
}
|
|
204
|
-
|
|
101
|
+
export type { ObservabilityContext } from '../observability.js';
|
|
102
|
+
import type { ObservabilityContext } from '../observability.js';
|
|
205
103
|
export interface PolicyContext {
|
|
206
104
|
key: string;
|
|
207
105
|
store: AnyStateStore;
|
|
208
106
|
meta: RunMeta;
|
|
107
|
+
observability?: ObservabilityContext;
|
|
209
108
|
}
|
|
210
|
-
/**
|
|
211
|
-
* The ONLY shape the executor knows about policies.
|
|
212
|
-
*
|
|
213
|
-
* A policy wraps `ActFn<T>` and returns a new `ActFn<T>`. It may intercept
|
|
214
|
-
* before, after, or instead of the inner call. The executor never imports
|
|
215
|
-
* a concrete policy — only this type.
|
|
216
|
-
*/
|
|
217
109
|
export type PolicyApplier<T> = (fn: ActFn<T>, ctx: PolicyContext) => ActFn<T>;
|
|
218
110
|
import type { SyncStateStore, AsyncStateStore } from '../stores/base.js';
|
|
219
111
|
export type { SyncStateStore, AsyncStateStore };
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
*
|
|
223
|
-
* Kept for backwards compatibility — every v1.0 consumer typed against
|
|
224
|
-
* `StateStore` continues to compile without changes. A future major version
|
|
225
|
-
* may widen this to `SyncStateStore | AsyncStateStore`.
|
|
226
|
-
*/
|
|
112
|
+
import type { ObservabilityHooks as ObsHooks } from '../observability.js';
|
|
113
|
+
export type ObservabilityHooks = ObsHooks;
|
|
227
114
|
export type StateStore = SyncStateStore;
|
|
228
|
-
/**
|
|
229
|
-
* Union of sync and async stores. Used internally by `PolicyContext` and
|
|
230
|
-
* exported for consumers building custom policy chains or store adapters.
|
|
231
|
-
*/
|
|
232
115
|
export type AnyStateStore = SyncStateStore | AsyncStateStore;
|
|
233
116
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/dist/types/index.js
CHANGED
package/dist/utils/abort.cjs
CHANGED
|
@@ -1,94 +1,71 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// ─── AbortSignal helpers ──────────────────────────────────────────────────────
|
|
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.
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
3
|
exports.anySignal = anySignal;
|
|
10
4
|
exports.raceAbort = raceAbort;
|
|
11
5
|
exports.sleep = sleep;
|
|
12
6
|
exports.isAbortError = isAbortError;
|
|
13
7
|
exports.linkSignal = linkSignal;
|
|
14
|
-
|
|
15
|
-
* Polyfill for `AbortSignal.any(signals)` (Node 20+).
|
|
16
|
-
*
|
|
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.
|
|
20
|
-
*
|
|
21
|
-
* Listener registration is `{ once: true }` — once any signal fires, we stop
|
|
22
|
-
* listening on the others. The composite signal cannot be "un-aborted".
|
|
23
|
-
*/
|
|
8
|
+
const NATIVE_ANY = AbortSignal.any;
|
|
24
9
|
function anySignal(signals) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (
|
|
29
|
-
return
|
|
30
|
-
|
|
10
|
+
const filtered = signals.filter((s) => s != null);
|
|
11
|
+
if (filtered.length === 0)
|
|
12
|
+
return new AbortController().signal;
|
|
13
|
+
if (filtered.length === 1)
|
|
14
|
+
return filtered[0];
|
|
15
|
+
if (NATIVE_ANY)
|
|
16
|
+
return NATIVE_ANY.call(AbortSignal, filtered);
|
|
31
17
|
const controller = new AbortController();
|
|
32
|
-
|
|
18
|
+
const listeners = [];
|
|
19
|
+
for (const signal of filtered) {
|
|
33
20
|
if (signal.aborted) {
|
|
34
21
|
controller.abort(signal.reason);
|
|
35
22
|
break;
|
|
36
23
|
}
|
|
37
|
-
|
|
24
|
+
const onAbort = () => {
|
|
25
|
+
controller.abort(signal.reason);
|
|
26
|
+
for (const off of listeners)
|
|
27
|
+
off();
|
|
28
|
+
};
|
|
29
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
30
|
+
listeners.push(() => signal.removeEventListener('abort', onAbort));
|
|
31
|
+
}
|
|
32
|
+
if (controller.signal.aborted) {
|
|
33
|
+
for (const off of listeners)
|
|
34
|
+
off();
|
|
38
35
|
}
|
|
39
36
|
return controller.signal;
|
|
40
37
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Race a promise against an AbortSignal.
|
|
43
|
-
*
|
|
44
|
-
* - If the signal is already aborted, rejects immediately with `signal.reason`.
|
|
45
|
-
* - If the signal aborts while the promise is pending, rejects with `signal.reason`.
|
|
46
|
-
* - If the promise settles first, returns its value (or rejects with its error).
|
|
47
|
-
*
|
|
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).
|
|
51
|
-
*
|
|
52
|
-
* Used by `dedupePolicy` so joiners can cancel their own `await` even if the
|
|
53
|
-
* originator's `fn` is still running.
|
|
54
|
-
*/
|
|
55
38
|
function raceAbort(promise, signal) {
|
|
56
|
-
if (signal.aborted)
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
promise.catch(() => { });
|
|
57
41
|
return Promise.reject(signal.reason);
|
|
42
|
+
}
|
|
58
43
|
return new Promise((resolve, reject) => {
|
|
59
44
|
let settled = false;
|
|
60
45
|
const onAbort = () => {
|
|
61
46
|
if (settled)
|
|
62
47
|
return;
|
|
63
48
|
settled = true;
|
|
49
|
+
signal.removeEventListener('abort', onAbort);
|
|
64
50
|
reject(signal.reason);
|
|
65
51
|
};
|
|
66
|
-
signal.addEventListener('abort', onAbort
|
|
52
|
+
signal.addEventListener('abort', onAbort);
|
|
67
53
|
promise.then((value) => {
|
|
68
54
|
if (settled)
|
|
69
55
|
return;
|
|
70
56
|
settled = true;
|
|
57
|
+
signal.removeEventListener('abort', onAbort);
|
|
71
58
|
resolve(value);
|
|
72
59
|
}, (error) => {
|
|
73
60
|
if (settled)
|
|
74
61
|
return;
|
|
75
62
|
settled = true;
|
|
63
|
+
signal.removeEventListener('abort', onAbort);
|
|
76
64
|
reject(error);
|
|
77
65
|
});
|
|
78
66
|
});
|
|
79
67
|
}
|
|
80
|
-
|
|
81
|
-
* Sleep for `ms` milliseconds, but abort early if `signal` fires.
|
|
82
|
-
*
|
|
83
|
-
* Resolves normally on timer expiry. Rejects with `signal.reason` if the
|
|
84
|
-
* signal aborts before the timer fires. If the signal is already aborted
|
|
85
|
-
* when called, rejects synchronously (in microtask).
|
|
86
|
-
*
|
|
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.
|
|
90
|
-
*/
|
|
91
|
-
function sleep(ms, signal) {
|
|
68
|
+
function sleep(ms, signal, opts) {
|
|
92
69
|
if (ms <= 0) {
|
|
93
70
|
if (signal?.aborted)
|
|
94
71
|
return Promise.reject(signal.reason);
|
|
@@ -101,42 +78,38 @@ function sleep(ms, signal) {
|
|
|
101
78
|
signal?.removeEventListener('abort', onAbort);
|
|
102
79
|
resolve();
|
|
103
80
|
}, ms);
|
|
81
|
+
if (opts?.unref && typeof timer.unref === 'function') {
|
|
82
|
+
timer.unref();
|
|
83
|
+
}
|
|
104
84
|
const onAbort = () => {
|
|
105
85
|
clearTimeout(timer);
|
|
106
86
|
reject(signal.reason);
|
|
107
87
|
};
|
|
108
|
-
signal?.addEventListener('abort', onAbort
|
|
88
|
+
signal?.addEventListener('abort', onAbort);
|
|
109
89
|
});
|
|
110
90
|
}
|
|
111
|
-
/**
|
|
112
|
-
* True if `err` is an `AbortError` (DOMException name or Error name).
|
|
113
|
-
*
|
|
114
|
-
* The default `shouldRetry` predicate uses this to skip retrying on
|
|
115
|
-
* cancellations — if the caller aborted, retrying would just abort again.
|
|
116
|
-
*/
|
|
117
91
|
function isAbortError(err) {
|
|
118
92
|
if (err == null || typeof err !== 'object')
|
|
119
93
|
return false;
|
|
94
|
+
if (err.code === 'ACTLY_ABORT')
|
|
95
|
+
return true;
|
|
120
96
|
const name = err.name;
|
|
121
|
-
|
|
97
|
+
if (name === 'AbortError')
|
|
98
|
+
return true;
|
|
99
|
+
if (name === 'TimeoutError' &&
|
|
122
100
|
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
101
|
typeof DOMException !== 'undefined' &&
|
|
126
|
-
err instanceof DOMException
|
|
102
|
+
err instanceof DOMException) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
127
106
|
}
|
|
128
|
-
/**
|
|
129
|
-
* 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).
|
|
133
|
-
*
|
|
134
|
-
* The listener is `{ once: true }` — no leak.
|
|
135
|
-
*/
|
|
136
107
|
function linkSignal(parent, child) {
|
|
137
108
|
if (parent.aborted) {
|
|
138
109
|
child.abort(parent.reason);
|
|
139
|
-
return;
|
|
110
|
+
return () => { };
|
|
140
111
|
}
|
|
141
|
-
|
|
112
|
+
const onAbort = () => child.abort(parent.reason);
|
|
113
|
+
parent.addEventListener('abort', onAbort);
|
|
114
|
+
return () => parent.removeEventListener('abort', onAbort);
|
|
142
115
|
}
|
package/dist/utils/abort.d.ts
CHANGED
|
@@ -1,55 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Polyfill for `AbortSignal.any(signals)` (Node 20+).
|
|
3
|
-
*
|
|
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.
|
|
7
|
-
*
|
|
8
|
-
* Listener registration is `{ once: true }` — once any signal fires, we stop
|
|
9
|
-
* listening on the others. The composite signal cannot be "un-aborted".
|
|
10
|
-
*/
|
|
11
1
|
export declare function anySignal(signals: ReadonlyArray<AbortSignal>): AbortSignal;
|
|
12
|
-
/**
|
|
13
|
-
* Race a promise against an AbortSignal.
|
|
14
|
-
*
|
|
15
|
-
* - If the signal is already aborted, rejects immediately with `signal.reason`.
|
|
16
|
-
* - If the signal aborts while the promise is pending, rejects with `signal.reason`.
|
|
17
|
-
* - If the promise settles first, returns its value (or rejects with its error).
|
|
18
|
-
*
|
|
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).
|
|
22
|
-
*
|
|
23
|
-
* Used by `dedupePolicy` so joiners can cancel their own `await` even if the
|
|
24
|
-
* originator's `fn` is still running.
|
|
25
|
-
*/
|
|
26
2
|
export declare function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* Resolves normally on timer expiry. Rejects with `signal.reason` if the
|
|
31
|
-
* signal aborts before the timer fires. If the signal is already aborted
|
|
32
|
-
* when called, rejects synchronously (in microtask).
|
|
33
|
-
*
|
|
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.
|
|
37
|
-
*/
|
|
38
|
-
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
39
|
-
/**
|
|
40
|
-
* True if `err` is an `AbortError` (DOMException name or Error name).
|
|
41
|
-
*
|
|
42
|
-
* The default `shouldRetry` predicate uses this to skip retrying on
|
|
43
|
-
* cancellations — if the caller aborted, retrying would just abort again.
|
|
44
|
-
*/
|
|
3
|
+
export declare function sleep(ms: number, signal?: AbortSignal, opts?: {
|
|
4
|
+
unref?: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
45
6
|
export declare function isAbortError(err: unknown): boolean;
|
|
46
|
-
|
|
47
|
-
* 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).
|
|
51
|
-
*
|
|
52
|
-
* The listener is `{ once: true }` — no leak.
|
|
53
|
-
*/
|
|
54
|
-
export declare function linkSignal(parent: AbortSignal, child: AbortController): void;
|
|
7
|
+
export declare function linkSignal(parent: AbortSignal, child: AbortController): () => void;
|
|
55
8
|
//# sourceMappingURL=abort.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"abort.d.ts","sourceRoot":"","sources":["../../src/utils/abort.ts"],"names":[],"mappings":"AAaA,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,CAAC,WAAW,CAAC,GAAG,WAAW,CAgC1E;AAOD,wBAAgB,SAAS,CAAC,CAAC,EACzB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,CAAC,CAAC,CAmCZ;AAWD,wBAAgB,KAAK,CACnB,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,WAAW,EACpB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAyBf;AAQD,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAelD;AASD,wBAAgB,UAAU,CACxB,MAAM,EAAE,WAAW,EACnB,KAAK,EAAE,eAAe,GACrB,MAAM,IAAI,CAQZ"}
|