awaitly 2.0.0 → 3.0.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/dist/di-Biw_plBn.d.cts +15 -0
- package/dist/di-DxyH2N3i.d.ts +15 -0
- package/dist/durable.cjs +4670 -0
- package/dist/durable.cjs.map +1 -0
- package/dist/durable.d.cts +9 -0
- package/dist/durable.d.ts +9 -0
- package/dist/durable.js +4610 -0
- package/dist/durable.js.map +1 -0
- package/dist/{di-BbFFfO8y.d.ts → duration-BjVn3QpB.d.cts} +1 -15
- package/dist/{di-BDlT7InM.d.cts → duration-BjVn3QpB.d.ts} +1 -15
- package/dist/engine.cjs +4501 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +113 -0
- package/dist/engine.d.ts +113 -0
- package/dist/engine.js +4474 -0
- package/dist/engine.js.map +1 -0
- package/dist/{errors-DtXvrCiO.d.cts → errors-sDSeaZBO.d.cts} +1 -1
- package/dist/{errors-DtXvrCiO.d.ts → errors-sDSeaZBO.d.ts} +1 -1
- package/dist/guards-CwQZro1F.d.ts +72 -0
- package/dist/guards-DhhOJZda.d.cts +72 -0
- package/dist/hitl-BnnnLzh2.d.cts +468 -0
- package/dist/hitl-pYfkQMv_.d.ts +468 -0
- package/dist/hitl.cjs +646 -0
- package/dist/hitl.cjs.map +1 -0
- package/dist/hitl.d.cts +440 -0
- package/dist/hitl.d.ts +440 -0
- package/dist/hitl.js +605 -0
- package/dist/hitl.js.map +1 -0
- package/dist/index-Bew12SZ8.d.ts +417 -0
- package/dist/index-Bwt-iz50.d.cts +417 -0
- package/dist/{types-B8NfNRGX.d.ts → index-Cv4K_3sZ.d.ts} +2 -1162
- package/dist/{types-BZ2f4MRR.d.cts → index-ICmfQi08.d.cts} +2 -1162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -1527
- package/dist/index.d.ts +11 -1527
- package/dist/persistence.cjs +552 -0
- package/dist/persistence.cjs.map +1 -0
- package/dist/persistence.d.cts +254 -0
- package/dist/persistence.d.ts +254 -0
- package/dist/persistence.js +499 -0
- package/dist/persistence.js.map +1 -0
- package/dist/reliability.cjs +1651 -0
- package/dist/reliability.cjs.map +1 -0
- package/dist/reliability.d.cts +1442 -0
- package/dist/reliability.d.ts +1442 -0
- package/dist/reliability.js +1589 -0
- package/dist/reliability.js.map +1 -0
- package/dist/result.d.cts +1 -1
- package/dist/result.d.ts +1 -1
- package/dist/run.cjs +2283 -0
- package/dist/run.cjs.map +1 -0
- package/dist/run.d.cts +90 -0
- package/dist/run.d.ts +90 -0
- package/dist/run.js +2247 -0
- package/dist/run.js.map +1 -0
- package/dist/saga.cjs +3699 -0
- package/dist/saga.cjs.map +1 -0
- package/dist/saga.d.cts +162 -0
- package/dist/saga.d.ts +162 -0
- package/dist/saga.js +3670 -0
- package/dist/saga.js.map +1 -0
- package/dist/store-contract-BI98VYmX.d.ts +66 -0
- package/dist/store-contract-C2HyR_o6.d.cts +66 -0
- package/dist/streaming.cjs +895 -0
- package/dist/streaming.cjs.map +1 -0
- package/dist/streaming.d.cts +594 -0
- package/dist/streaming.d.ts +594 -0
- package/dist/streaming.js +832 -0
- package/dist/streaming.js.map +1 -0
- package/dist/testing.d.cts +4 -2
- package/dist/testing.d.ts +4 -2
- package/dist/types-BDTxgKKc.d.ts +845 -0
- package/dist/types-C9Y71dua.d.cts +845 -0
- package/dist/types-d8q8iQlk.d.cts +323 -0
- package/dist/types-sObbY4mX.d.ts +323 -0
- package/dist/webhook.cjs +461 -0
- package/dist/webhook.cjs.map +1 -0
- package/dist/webhook.d.cts +497 -0
- package/dist/webhook.d.ts +497 -0
- package/dist/webhook.js +422 -0
- package/dist/webhook.js.map +1 -0
- package/dist/workflow.cjs +2 -2614
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.d.cts +12 -3023
- package/dist/workflow.d.ts +12 -3023
- package/dist/workflow.js +2 -2535
- package/dist/workflow.js.map +1 -1
- package/package.json +46 -1
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/reliability-entry.ts
|
|
21
|
+
var reliability_entry_exports = {};
|
|
22
|
+
__export(reliability_entry_exports, {
|
|
23
|
+
CircuitOpenError: () => CircuitOpenError,
|
|
24
|
+
cached: () => cached,
|
|
25
|
+
cachedFunction: () => cachedFunction,
|
|
26
|
+
cachedWithTTL: () => cachedWithTTL,
|
|
27
|
+
circuitBreakerPresets: () => circuitBreakerPresets,
|
|
28
|
+
conditionalPolicy: () => conditionalPolicy,
|
|
29
|
+
createCache: () => createCache,
|
|
30
|
+
createCircuitBreaker: () => createCircuitBreaker,
|
|
31
|
+
createCombinedLimiter: () => createCombinedLimiter,
|
|
32
|
+
createConcurrencyLimiter: () => createConcurrencyLimiter,
|
|
33
|
+
createCostBasedRateLimiter: () => createCostBasedRateLimiter,
|
|
34
|
+
createFixedWindowLimiter: () => createFixedWindowLimiter,
|
|
35
|
+
createPolicyApplier: () => createPolicyApplier,
|
|
36
|
+
createPolicyBundle: () => createPolicyBundle,
|
|
37
|
+
createPolicyRegistry: () => createPolicyRegistry,
|
|
38
|
+
createRateLimiter: () => createRateLimiter,
|
|
39
|
+
createSingleflightGroup: () => createSingleflightGroup,
|
|
40
|
+
envPolicy: () => envPolicy,
|
|
41
|
+
fallback: () => fallback,
|
|
42
|
+
isCircuitOpenError: () => isCircuitOpenError,
|
|
43
|
+
isQueueFullError: () => isQueueFullError,
|
|
44
|
+
isRateLimitExceededError: () => isRateLimitExceededError,
|
|
45
|
+
mergePolicies: () => mergePolicies,
|
|
46
|
+
once: () => once,
|
|
47
|
+
rateLimiterPresets: () => rateLimiterPresets,
|
|
48
|
+
retry: () => retry,
|
|
49
|
+
retryPolicies: () => retryPolicies,
|
|
50
|
+
retryPolicy: () => retryPolicy,
|
|
51
|
+
servicePolicies: () => servicePolicies,
|
|
52
|
+
singleflight: () => singleflight,
|
|
53
|
+
stepOptions: () => stepOptions,
|
|
54
|
+
timeout: () => timeout,
|
|
55
|
+
timeoutPolicies: () => timeoutPolicies,
|
|
56
|
+
timeoutPolicy: () => timeoutPolicy,
|
|
57
|
+
withPolicies: () => withPolicies,
|
|
58
|
+
withPolicy: () => withPolicy
|
|
59
|
+
});
|
|
60
|
+
module.exports = __toCommonJS(reliability_entry_exports);
|
|
61
|
+
|
|
62
|
+
// src/slugs.ts
|
|
63
|
+
var AWAITLY_SLUGS = {
|
|
64
|
+
// --- step-* ---
|
|
65
|
+
"step-require-id": "step-require-id",
|
|
66
|
+
"step-no-immediate-execution": "step-no-immediate-execution",
|
|
67
|
+
"step-require-thunk-for-key": "step-require-thunk-for-key",
|
|
68
|
+
"step-no-bare-await": "step-no-bare-await",
|
|
69
|
+
"step-no-try-catch-wrap": "step-no-try-catch-wrap",
|
|
70
|
+
"step-stable-cache-keys": "step-stable-cache-keys",
|
|
71
|
+
// --- workflow-* ---
|
|
72
|
+
"workflow-no-floating": "workflow-no-floating",
|
|
73
|
+
"workflow-options-position": "workflow-options-position",
|
|
74
|
+
"workflow-callback-shape": "workflow-callback-shape",
|
|
75
|
+
"workflow-no-callable-form": "workflow-no-callable-form",
|
|
76
|
+
"workflow-no-dynamic-import": "workflow-no-dynamic-import",
|
|
77
|
+
"workflow-prefer-step-if": "workflow-prefer-step-if",
|
|
78
|
+
"workflow-prefer-step-foreach": "workflow-prefer-step-foreach",
|
|
79
|
+
// --- result-* ---
|
|
80
|
+
"result-no-floating": "result-no-floating",
|
|
81
|
+
"result-require-handling": "result-require-handling",
|
|
82
|
+
"result-no-double-wrap": "result-no-double-wrap",
|
|
83
|
+
"result-no-manual-propagation": "result-no-manual-propagation",
|
|
84
|
+
"result-no-direct-ok-err": "result-no-direct-ok-err",
|
|
85
|
+
// --- error-* ---
|
|
86
|
+
"error-check-unexpected-first": "error-check-unexpected-first",
|
|
87
|
+
"error-access-cause": "error-access-cause",
|
|
88
|
+
"error-normalize": "error-normalize",
|
|
89
|
+
"error-no-throw-in-deps": "error-no-throw-in-deps",
|
|
90
|
+
// --- concurrency-* ---
|
|
91
|
+
"concurrency-no-promise-all": "concurrency-no-promise-all",
|
|
92
|
+
"concurrency-no-promise-race": "concurrency-no-promise-race",
|
|
93
|
+
"concurrency-no-promise-allsettled": "concurrency-no-promise-allsettled",
|
|
94
|
+
// --- runtime-* ---
|
|
95
|
+
"runtime-step-timeout": "runtime-step-timeout",
|
|
96
|
+
"runtime-step-aborted": "runtime-step-aborted",
|
|
97
|
+
"runtime-retry-exhausted": "runtime-retry-exhausted",
|
|
98
|
+
"runtime-rate-limit": "runtime-rate-limit",
|
|
99
|
+
"runtime-circuit-open": "runtime-circuit-open",
|
|
100
|
+
"runtime-unexpected": "runtime-unexpected",
|
|
101
|
+
"runtime-resolver-not-found": "runtime-resolver-not-found",
|
|
102
|
+
"runtime-saga-compensation": "runtime-saga-compensation"
|
|
103
|
+
};
|
|
104
|
+
function slugDocsUrl(slug) {
|
|
105
|
+
return `https://jagreehal.github.io/awaitly/rules/#${slug}`;
|
|
106
|
+
}
|
|
107
|
+
var ALL_SLUGS = Object.keys(
|
|
108
|
+
AWAITLY_SLUGS
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// src/tagged-error.ts
|
|
112
|
+
var InternalTaggedErrorBase = class extends Error {
|
|
113
|
+
type;
|
|
114
|
+
/** @deprecated Use `type`. */
|
|
115
|
+
_tag;
|
|
116
|
+
};
|
|
117
|
+
function TaggedError(tag, options) {
|
|
118
|
+
return class extends InternalTaggedErrorBase {
|
|
119
|
+
type = tag;
|
|
120
|
+
/** @deprecated Use `type`. */
|
|
121
|
+
_tag = tag;
|
|
122
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
123
|
+
constructor(props, errorOptions) {
|
|
124
|
+
const message = options?.message ? options.message(props ?? {}) : tag;
|
|
125
|
+
super(message);
|
|
126
|
+
this.name = tag;
|
|
127
|
+
if (options?.slug !== void 0) {
|
|
128
|
+
if (!options.hint) {
|
|
129
|
+
throw new TypeError(
|
|
130
|
+
`TaggedError: 'hint' is required when 'slug' is set (slug: "${options.slug}")`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
Object.defineProperty(this, "code", {
|
|
134
|
+
value: options.slug,
|
|
135
|
+
enumerable: true,
|
|
136
|
+
writable: false,
|
|
137
|
+
configurable: false
|
|
138
|
+
});
|
|
139
|
+
Object.defineProperty(this, "hint", {
|
|
140
|
+
value: options.hint,
|
|
141
|
+
enumerable: true,
|
|
142
|
+
writable: false,
|
|
143
|
+
configurable: false
|
|
144
|
+
});
|
|
145
|
+
Object.defineProperty(this, "docsUrl", {
|
|
146
|
+
value: slugDocsUrl(options.slug),
|
|
147
|
+
enumerable: true,
|
|
148
|
+
writable: false,
|
|
149
|
+
configurable: false
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
153
|
+
if (props && typeof props === "object") {
|
|
154
|
+
let safeProps;
|
|
155
|
+
if (options?.slug !== void 0) {
|
|
156
|
+
const {
|
|
157
|
+
_tag: _,
|
|
158
|
+
type: _t,
|
|
159
|
+
name: _n,
|
|
160
|
+
message: _m,
|
|
161
|
+
stack: _s,
|
|
162
|
+
code: _c,
|
|
163
|
+
hint: _h,
|
|
164
|
+
docsUrl: _d,
|
|
165
|
+
...rest
|
|
166
|
+
} = props;
|
|
167
|
+
safeProps = rest;
|
|
168
|
+
} else {
|
|
169
|
+
const {
|
|
170
|
+
_tag: _,
|
|
171
|
+
type: _t,
|
|
172
|
+
name: _n,
|
|
173
|
+
message: _m,
|
|
174
|
+
stack: _s,
|
|
175
|
+
...rest
|
|
176
|
+
} = props;
|
|
177
|
+
safeProps = rest;
|
|
178
|
+
}
|
|
179
|
+
const hasUserCause = Object.prototype.hasOwnProperty.call(
|
|
180
|
+
safeProps,
|
|
181
|
+
"cause"
|
|
182
|
+
);
|
|
183
|
+
const userCause = hasUserCause ? safeProps.cause : void 0;
|
|
184
|
+
if (hasUserCause) {
|
|
185
|
+
delete safeProps.cause;
|
|
186
|
+
}
|
|
187
|
+
const hasOptionsCause = errorOptions?.cause !== void 0;
|
|
188
|
+
if (hasUserCause && hasOptionsCause) {
|
|
189
|
+
throw new TypeError(
|
|
190
|
+
"TaggedError: cannot provide 'cause' in props when also setting ErrorOptions.cause"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
Object.assign(this, safeProps);
|
|
194
|
+
if (hasUserCause) {
|
|
195
|
+
this.cause = userCause;
|
|
196
|
+
}
|
|
197
|
+
if (hasOptionsCause) {
|
|
198
|
+
this.cause = errorOptions?.cause;
|
|
199
|
+
}
|
|
200
|
+
} else if (errorOptions?.cause !== void 0) {
|
|
201
|
+
this.cause = errorOptions.cause;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
Object.defineProperty(TaggedError, Symbol.hasInstance, {
|
|
207
|
+
value: (instance) => instance instanceof InternalTaggedErrorBase
|
|
208
|
+
});
|
|
209
|
+
((TaggedError2) => {
|
|
210
|
+
function isError(value) {
|
|
211
|
+
return value instanceof Error;
|
|
212
|
+
}
|
|
213
|
+
TaggedError2.isError = isError;
|
|
214
|
+
function isTaggedError(value) {
|
|
215
|
+
return value instanceof InternalTaggedErrorBase;
|
|
216
|
+
}
|
|
217
|
+
TaggedError2.isTaggedError = isTaggedError;
|
|
218
|
+
function match(error, handlers) {
|
|
219
|
+
const tag = error._tag;
|
|
220
|
+
const handler = handlers[tag];
|
|
221
|
+
return handler(
|
|
222
|
+
error
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
TaggedError2.match = match;
|
|
226
|
+
function matchPartial(error, handlers, otherwise) {
|
|
227
|
+
const tag = error._tag;
|
|
228
|
+
const handler = handlers[tag];
|
|
229
|
+
if (handler) {
|
|
230
|
+
return handler(
|
|
231
|
+
error
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
return otherwise(error);
|
|
235
|
+
}
|
|
236
|
+
TaggedError2.matchPartial = matchPartial;
|
|
237
|
+
})(TaggedError || (TaggedError = {}));
|
|
238
|
+
|
|
239
|
+
// src/errors.ts
|
|
240
|
+
var TimeoutError = class extends (/* @__PURE__ */ TaggedError("TimeoutError", {
|
|
241
|
+
slug: "runtime-step-timeout",
|
|
242
|
+
hint: "Increase the step's timeout option, or check why the upstream operation is slow.",
|
|
243
|
+
message: (p) => p.operation ? `TimeoutError: ${p.operation} timed out after ${p.ms}ms` : `TimeoutError: Operation timed out after ${p.ms}ms`
|
|
244
|
+
})) {
|
|
245
|
+
};
|
|
246
|
+
var UnexpectedError = class extends (/* @__PURE__ */ TaggedError("UnexpectedError", {
|
|
247
|
+
slug: "runtime-unexpected",
|
|
248
|
+
hint: "An unexpected exception escaped a step. Inspect cause; consider returning a typed Result instead of throwing.",
|
|
249
|
+
message: (p) => `UnexpectedError: ${p.cause instanceof Error ? p.cause.message : String(p.cause ?? "unknown")}`
|
|
250
|
+
})) {
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
// src/result/index.ts
|
|
254
|
+
function ok(value) {
|
|
255
|
+
return { ok: true, value };
|
|
256
|
+
}
|
|
257
|
+
function err(error, options) {
|
|
258
|
+
const cause = options?.cause;
|
|
259
|
+
return { ok: false, error, ...cause !== void 0 ? { cause } : {} };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// src/duration.ts
|
|
263
|
+
function millis(ms) {
|
|
264
|
+
return { _tag: "Duration", millis: ms };
|
|
265
|
+
}
|
|
266
|
+
function seconds(s) {
|
|
267
|
+
return { _tag: "Duration", millis: s * 1e3 };
|
|
268
|
+
}
|
|
269
|
+
function minutes(m) {
|
|
270
|
+
return { _tag: "Duration", millis: m * 60 * 1e3 };
|
|
271
|
+
}
|
|
272
|
+
function hours(h) {
|
|
273
|
+
return { _tag: "Duration", millis: h * 60 * 60 * 1e3 };
|
|
274
|
+
}
|
|
275
|
+
function days(d) {
|
|
276
|
+
return { _tag: "Duration", millis: d * 24 * 60 * 60 * 1e3 };
|
|
277
|
+
}
|
|
278
|
+
function toMillis(duration) {
|
|
279
|
+
return duration.millis;
|
|
280
|
+
}
|
|
281
|
+
function parse(input) {
|
|
282
|
+
const match = input.trim().match(/^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)$/i);
|
|
283
|
+
if (!match) return void 0;
|
|
284
|
+
const value = parseFloat(match[1]);
|
|
285
|
+
const unit = match[2].toLowerCase();
|
|
286
|
+
switch (unit) {
|
|
287
|
+
case "ms":
|
|
288
|
+
return millis(value);
|
|
289
|
+
case "s":
|
|
290
|
+
return seconds(value);
|
|
291
|
+
case "m":
|
|
292
|
+
return minutes(value);
|
|
293
|
+
case "h":
|
|
294
|
+
return hours(value);
|
|
295
|
+
case "d":
|
|
296
|
+
return days(value);
|
|
297
|
+
default:
|
|
298
|
+
return void 0;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/core/bound-steps.ts
|
|
303
|
+
var isDepResultShaped = (value) => typeof value === "object" && value !== null && "ok" in value && typeof value.ok === "boolean" && (value.ok ? "value" in value : "error" in value);
|
|
304
|
+
|
|
305
|
+
// src/core/policies.ts
|
|
306
|
+
var toMs = (d) => typeof d === "number" ? d : toMillis(d);
|
|
307
|
+
var attemptCall = async (fn, args) => {
|
|
308
|
+
try {
|
|
309
|
+
const value = await fn(...args);
|
|
310
|
+
if (isDepResultShaped(value)) {
|
|
311
|
+
return value.ok ? { kind: "ok", value: value.value } : { kind: "err", error: value.error, result: value };
|
|
312
|
+
}
|
|
313
|
+
return { kind: "ok", value };
|
|
314
|
+
} catch (thrown) {
|
|
315
|
+
return { kind: "threw", thrown };
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
var named = (wrapped, source) => {
|
|
319
|
+
const name = source.name;
|
|
320
|
+
if (name) {
|
|
321
|
+
Object.defineProperty(wrapped, "name", { value: name, configurable: true });
|
|
322
|
+
}
|
|
323
|
+
return wrapped;
|
|
324
|
+
};
|
|
325
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
326
|
+
function retry(fn, options) {
|
|
327
|
+
const attempts = Math.max(1, Math.trunc(options.attempts));
|
|
328
|
+
const baseDelay = options.delay === void 0 ? 0 : toMs(options.delay);
|
|
329
|
+
const maxDelay = options.maxDelay === void 0 ? Infinity : toMs(options.maxDelay);
|
|
330
|
+
const backoff = options.backoff ?? "fixed";
|
|
331
|
+
const delayFor = (attempt) => {
|
|
332
|
+
const raw = backoff === "exponential" ? baseDelay * 2 ** (attempt - 1) : backoff === "linear" ? baseDelay * attempt : baseDelay;
|
|
333
|
+
return Math.min(raw, maxDelay);
|
|
334
|
+
};
|
|
335
|
+
const wrapped = async (...args) => {
|
|
336
|
+
let last = { kind: "threw", thrown: void 0 };
|
|
337
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
338
|
+
last = await attemptCall(fn, args);
|
|
339
|
+
if (last.kind === "ok") return ok(last.value);
|
|
340
|
+
const failure = last.kind === "err" ? last.error : last.thrown;
|
|
341
|
+
if (options.retryIf && !options.retryIf(failure)) break;
|
|
342
|
+
if (attempt < attempts) {
|
|
343
|
+
options.onRetry?.({ attempt, failure });
|
|
344
|
+
const ms = delayFor(attempt);
|
|
345
|
+
if (ms > 0) await sleep(ms);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (last.kind === "err") return last.result;
|
|
349
|
+
throw last.thrown;
|
|
350
|
+
};
|
|
351
|
+
return named(wrapped, fn);
|
|
352
|
+
}
|
|
353
|
+
function timeout(fn, after) {
|
|
354
|
+
const ms = toMs(after);
|
|
355
|
+
const TIMED_OUT = /* @__PURE__ */ Symbol("timed-out");
|
|
356
|
+
const wrapped = async (...args) => {
|
|
357
|
+
let timer;
|
|
358
|
+
try {
|
|
359
|
+
const outcome = await Promise.race([
|
|
360
|
+
attemptCall(fn, args),
|
|
361
|
+
new Promise((resolve) => {
|
|
362
|
+
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
363
|
+
})
|
|
364
|
+
]);
|
|
365
|
+
if (outcome === TIMED_OUT) {
|
|
366
|
+
return err(new TimeoutError({ operation: fn.name || void 0, ms }));
|
|
367
|
+
}
|
|
368
|
+
if (outcome.kind === "ok") return ok(outcome.value);
|
|
369
|
+
if (outcome.kind === "err") return outcome.result;
|
|
370
|
+
throw outcome.thrown;
|
|
371
|
+
} finally {
|
|
372
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
return named(wrapped, fn);
|
|
376
|
+
}
|
|
377
|
+
function fallback(fn, onFailure) {
|
|
378
|
+
const wrapped = async (...args) => {
|
|
379
|
+
const outcome = await attemptCall(fn, args);
|
|
380
|
+
if (outcome.kind === "ok") return ok(outcome.value);
|
|
381
|
+
const failure = outcome.kind === "err" ? outcome.error : new UnexpectedError({ cause: outcome.thrown });
|
|
382
|
+
const recovered = await attemptCall(onFailure, [failure, ...args]);
|
|
383
|
+
if (recovered.kind === "ok") return ok(recovered.value);
|
|
384
|
+
if (recovered.kind === "err") return recovered.result;
|
|
385
|
+
throw recovered.thrown;
|
|
386
|
+
};
|
|
387
|
+
return named(wrapped, fn);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// src/core/index.ts
|
|
391
|
+
function err2(error, options) {
|
|
392
|
+
const cause = options?.cause;
|
|
393
|
+
return { ok: false, error, ...cause !== void 0 ? { cause } : {} };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// src/circuit-breaker.ts
|
|
397
|
+
var CircuitOpenError = class extends Error {
|
|
398
|
+
type = "CIRCUIT_OPEN";
|
|
399
|
+
circuitName;
|
|
400
|
+
state;
|
|
401
|
+
retryAfterMs;
|
|
402
|
+
constructor(options) {
|
|
403
|
+
super(
|
|
404
|
+
options.message ?? `Circuit breaker "${options.circuitName}" is ${options.state}. Retry after ${Math.ceil(options.retryAfterMs / 1e3)}s`
|
|
405
|
+
);
|
|
406
|
+
this.name = "CircuitOpenError";
|
|
407
|
+
this.circuitName = options.circuitName;
|
|
408
|
+
this.state = options.state;
|
|
409
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
function isCircuitOpenError(error) {
|
|
413
|
+
return typeof error === "object" && error !== null && error.type === "CIRCUIT_OPEN";
|
|
414
|
+
}
|
|
415
|
+
var DEFAULT_CONFIG = {
|
|
416
|
+
failureThreshold: 5,
|
|
417
|
+
resetTimeout: 3e4,
|
|
418
|
+
windowSize: 6e4,
|
|
419
|
+
halfOpenMax: 3
|
|
420
|
+
};
|
|
421
|
+
function createCircuitBreaker(name, config) {
|
|
422
|
+
const effectiveConfig = {
|
|
423
|
+
...DEFAULT_CONFIG,
|
|
424
|
+
...config
|
|
425
|
+
};
|
|
426
|
+
let state = "CLOSED";
|
|
427
|
+
let failures = [];
|
|
428
|
+
let lastFailureTime = null;
|
|
429
|
+
let lastSuccessTime = null;
|
|
430
|
+
let successCount = 0;
|
|
431
|
+
let halfOpenSuccesses = 0;
|
|
432
|
+
function cleanupFailures() {
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
failures = failures.filter(
|
|
435
|
+
(f) => now - f.timestamp < effectiveConfig.windowSize
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
function transitionTo(newState) {
|
|
439
|
+
if (state !== newState) {
|
|
440
|
+
const oldState = state;
|
|
441
|
+
state = newState;
|
|
442
|
+
if (newState === "HALF_OPEN") {
|
|
443
|
+
halfOpenSuccesses = 0;
|
|
444
|
+
}
|
|
445
|
+
effectiveConfig.onStateChange?.(oldState, newState, name);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
function checkOpenToHalfOpen() {
|
|
449
|
+
if (state !== "OPEN" || lastFailureTime === null) {
|
|
450
|
+
return false;
|
|
451
|
+
}
|
|
452
|
+
const now = Date.now();
|
|
453
|
+
if (now - lastFailureTime >= effectiveConfig.resetTimeout) {
|
|
454
|
+
transitionTo("HALF_OPEN");
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
function handleSuccess() {
|
|
460
|
+
lastSuccessTime = Date.now();
|
|
461
|
+
successCount++;
|
|
462
|
+
if (state === "HALF_OPEN") {
|
|
463
|
+
halfOpenSuccesses++;
|
|
464
|
+
if (halfOpenSuccesses >= effectiveConfig.halfOpenMax) {
|
|
465
|
+
transitionTo("CLOSED");
|
|
466
|
+
failures = [];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
function handleFailure(error) {
|
|
471
|
+
const now = Date.now();
|
|
472
|
+
lastFailureTime = now;
|
|
473
|
+
cleanupFailures();
|
|
474
|
+
failures.push({ timestamp: now, error });
|
|
475
|
+
if (state === "HALF_OPEN") {
|
|
476
|
+
transitionTo("OPEN");
|
|
477
|
+
} else if (state === "CLOSED") {
|
|
478
|
+
if (failures.length >= effectiveConfig.failureThreshold) {
|
|
479
|
+
transitionTo("OPEN");
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
function canExecute() {
|
|
484
|
+
if (state === "CLOSED") {
|
|
485
|
+
return 0;
|
|
486
|
+
}
|
|
487
|
+
if (state === "OPEN") {
|
|
488
|
+
if (checkOpenToHalfOpen()) {
|
|
489
|
+
return 0;
|
|
490
|
+
}
|
|
491
|
+
const now = Date.now();
|
|
492
|
+
const elapsed = lastFailureTime ? now - lastFailureTime : 0;
|
|
493
|
+
return Math.max(0, effectiveConfig.resetTimeout - elapsed);
|
|
494
|
+
}
|
|
495
|
+
return 0;
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
async execute(operation, _options) {
|
|
499
|
+
const waitTime = canExecute();
|
|
500
|
+
if (waitTime > 0) {
|
|
501
|
+
throw new CircuitOpenError({
|
|
502
|
+
circuitName: name,
|
|
503
|
+
state,
|
|
504
|
+
retryAfterMs: waitTime
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
508
|
+
const result = await operation();
|
|
509
|
+
handleSuccess();
|
|
510
|
+
return result;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
handleFailure(error);
|
|
513
|
+
throw error;
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
async executeResult(operation, _options) {
|
|
517
|
+
const waitTime = canExecute();
|
|
518
|
+
if (waitTime > 0) {
|
|
519
|
+
return err2(
|
|
520
|
+
new CircuitOpenError({
|
|
521
|
+
circuitName: name,
|
|
522
|
+
state,
|
|
523
|
+
retryAfterMs: waitTime
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
try {
|
|
528
|
+
const result = await operation();
|
|
529
|
+
if (result.ok) {
|
|
530
|
+
handleSuccess();
|
|
531
|
+
} else {
|
|
532
|
+
handleFailure(result.error);
|
|
533
|
+
}
|
|
534
|
+
return result;
|
|
535
|
+
} catch (error) {
|
|
536
|
+
handleFailure(error);
|
|
537
|
+
throw error;
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
getState() {
|
|
541
|
+
if (state === "OPEN") {
|
|
542
|
+
checkOpenToHalfOpen();
|
|
543
|
+
}
|
|
544
|
+
return state;
|
|
545
|
+
},
|
|
546
|
+
getStats() {
|
|
547
|
+
cleanupFailures();
|
|
548
|
+
return {
|
|
549
|
+
state: this.getState(),
|
|
550
|
+
failureCount: failures.length,
|
|
551
|
+
successCount,
|
|
552
|
+
lastFailureTime,
|
|
553
|
+
lastSuccessTime,
|
|
554
|
+
halfOpenSuccesses
|
|
555
|
+
};
|
|
556
|
+
},
|
|
557
|
+
reset() {
|
|
558
|
+
transitionTo("CLOSED");
|
|
559
|
+
failures = [];
|
|
560
|
+
halfOpenSuccesses = 0;
|
|
561
|
+
},
|
|
562
|
+
forceOpen() {
|
|
563
|
+
lastFailureTime = Date.now();
|
|
564
|
+
transitionTo("OPEN");
|
|
565
|
+
},
|
|
566
|
+
recordSuccess() {
|
|
567
|
+
handleSuccess();
|
|
568
|
+
},
|
|
569
|
+
recordFailure(error) {
|
|
570
|
+
handleFailure(error ?? new Error("Manual failure"));
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
var circuitBreakerPresets = {
|
|
575
|
+
/**
|
|
576
|
+
* Aggressive circuit breaker for critical paths.
|
|
577
|
+
* Opens quickly (3 failures) and recovers slowly (60s).
|
|
578
|
+
*/
|
|
579
|
+
critical: {
|
|
580
|
+
failureThreshold: 3,
|
|
581
|
+
resetTimeout: 6e4,
|
|
582
|
+
windowSize: 3e4,
|
|
583
|
+
halfOpenMax: 1
|
|
584
|
+
},
|
|
585
|
+
/**
|
|
586
|
+
* Standard circuit breaker for typical API calls.
|
|
587
|
+
* Balanced between stability and availability.
|
|
588
|
+
*/
|
|
589
|
+
standard: {
|
|
590
|
+
failureThreshold: 5,
|
|
591
|
+
resetTimeout: 3e4,
|
|
592
|
+
windowSize: 6e4,
|
|
593
|
+
halfOpenMax: 3
|
|
594
|
+
},
|
|
595
|
+
/**
|
|
596
|
+
* Lenient circuit breaker for non-critical operations.
|
|
597
|
+
* Opens slowly (10 failures) and recovers quickly (15s).
|
|
598
|
+
*/
|
|
599
|
+
lenient: {
|
|
600
|
+
failureThreshold: 10,
|
|
601
|
+
resetTimeout: 15e3,
|
|
602
|
+
windowSize: 12e4,
|
|
603
|
+
halfOpenMax: 5
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
// src/rate-limiter/index.ts
|
|
608
|
+
function isRateLimitExceededError(error) {
|
|
609
|
+
return typeof error === "object" && error !== null && error.type === "RATE_LIMIT_EXCEEDED";
|
|
610
|
+
}
|
|
611
|
+
function isQueueFullError(error) {
|
|
612
|
+
return typeof error === "object" && error !== null && error.type === "QUEUE_FULL";
|
|
613
|
+
}
|
|
614
|
+
function createRateLimiter(name, config) {
|
|
615
|
+
const { maxPerSecond, strategy = "wait" } = config;
|
|
616
|
+
const maxTokens = config.burstCapacity ?? maxPerSecond * 2;
|
|
617
|
+
let tokens = maxTokens;
|
|
618
|
+
let lastRefill = Date.now();
|
|
619
|
+
const refillRate = maxPerSecond / 1e3;
|
|
620
|
+
const waitQueue = [];
|
|
621
|
+
function refill() {
|
|
622
|
+
const now = Date.now();
|
|
623
|
+
const elapsed = now - lastRefill;
|
|
624
|
+
const tokensToAdd = elapsed * refillRate;
|
|
625
|
+
tokens = Math.min(maxTokens, tokens + tokensToAdd);
|
|
626
|
+
lastRefill = now;
|
|
627
|
+
}
|
|
628
|
+
function tryConsume() {
|
|
629
|
+
refill();
|
|
630
|
+
if (tokens >= 1) {
|
|
631
|
+
tokens -= 1;
|
|
632
|
+
return 0;
|
|
633
|
+
}
|
|
634
|
+
const tokensNeeded = 1 - tokens;
|
|
635
|
+
return Math.ceil(tokensNeeded / refillRate);
|
|
636
|
+
}
|
|
637
|
+
async function waitForToken() {
|
|
638
|
+
return new Promise((resolve) => {
|
|
639
|
+
const check = () => {
|
|
640
|
+
const waitTime = tryConsume();
|
|
641
|
+
if (waitTime === 0) {
|
|
642
|
+
resolve();
|
|
643
|
+
} else {
|
|
644
|
+
waitQueue.push(check);
|
|
645
|
+
setTimeout(() => {
|
|
646
|
+
const idx = waitQueue.indexOf(check);
|
|
647
|
+
if (idx !== -1) {
|
|
648
|
+
waitQueue.splice(idx, 1);
|
|
649
|
+
check();
|
|
650
|
+
}
|
|
651
|
+
}, waitTime);
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
check();
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
async execute(operation) {
|
|
659
|
+
const waitTime = tryConsume();
|
|
660
|
+
if (waitTime > 0) {
|
|
661
|
+
if (strategy === "reject") {
|
|
662
|
+
throw {
|
|
663
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
664
|
+
limiterName: name,
|
|
665
|
+
retryAfterMs: waitTime
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
await waitForToken();
|
|
669
|
+
}
|
|
670
|
+
return operation();
|
|
671
|
+
},
|
|
672
|
+
async executeResult(operation) {
|
|
673
|
+
const waitTime = tryConsume();
|
|
674
|
+
if (waitTime > 0) {
|
|
675
|
+
if (strategy === "reject") {
|
|
676
|
+
return err2({
|
|
677
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
678
|
+
limiterName: name,
|
|
679
|
+
retryAfterMs: waitTime
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
await waitForToken();
|
|
683
|
+
}
|
|
684
|
+
return operation();
|
|
685
|
+
},
|
|
686
|
+
getStats() {
|
|
687
|
+
refill();
|
|
688
|
+
return {
|
|
689
|
+
availableTokens: Math.floor(tokens),
|
|
690
|
+
maxTokens,
|
|
691
|
+
tokensPerSecond: maxPerSecond,
|
|
692
|
+
waitingCount: waitQueue.length
|
|
693
|
+
};
|
|
694
|
+
},
|
|
695
|
+
reset() {
|
|
696
|
+
tokens = maxTokens;
|
|
697
|
+
lastRefill = Date.now();
|
|
698
|
+
waitQueue.length = 0;
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
function createConcurrencyLimiter(name, config) {
|
|
703
|
+
const { maxConcurrent, strategy = "queue", maxQueueSize = Infinity } = config;
|
|
704
|
+
let activeCount = 0;
|
|
705
|
+
const queue = [];
|
|
706
|
+
async function acquire() {
|
|
707
|
+
if (activeCount < maxConcurrent) {
|
|
708
|
+
activeCount++;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (strategy === "reject") {
|
|
712
|
+
throw {
|
|
713
|
+
type: "QUEUE_FULL",
|
|
714
|
+
limiterName: name,
|
|
715
|
+
queueSize: queue.length,
|
|
716
|
+
maxQueueSize
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
if (queue.length >= maxQueueSize) {
|
|
720
|
+
throw {
|
|
721
|
+
type: "QUEUE_FULL",
|
|
722
|
+
limiterName: name,
|
|
723
|
+
queueSize: queue.length,
|
|
724
|
+
maxQueueSize
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
return new Promise((resolve, reject) => {
|
|
728
|
+
queue.push({ resolve, reject });
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function release() {
|
|
732
|
+
activeCount--;
|
|
733
|
+
if (queue.length > 0 && activeCount < maxConcurrent) {
|
|
734
|
+
activeCount++;
|
|
735
|
+
const next = queue.shift();
|
|
736
|
+
next?.resolve();
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
async execute(operation) {
|
|
741
|
+
await acquire();
|
|
742
|
+
try {
|
|
743
|
+
return await operation();
|
|
744
|
+
} finally {
|
|
745
|
+
release();
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
async executeAll(operations) {
|
|
749
|
+
const results = new Array(operations.length);
|
|
750
|
+
const executing = [];
|
|
751
|
+
for (let i = 0; i < operations.length; i++) {
|
|
752
|
+
const index = i;
|
|
753
|
+
const promise = this.execute(operations[index]).then((result) => {
|
|
754
|
+
results[index] = result;
|
|
755
|
+
});
|
|
756
|
+
executing.push(promise);
|
|
757
|
+
}
|
|
758
|
+
await Promise.all(executing);
|
|
759
|
+
return results;
|
|
760
|
+
},
|
|
761
|
+
async executeResult(operation) {
|
|
762
|
+
try {
|
|
763
|
+
await acquire();
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (isQueueFullError(error)) {
|
|
766
|
+
return err2(error);
|
|
767
|
+
}
|
|
768
|
+
throw error;
|
|
769
|
+
}
|
|
770
|
+
try {
|
|
771
|
+
return await operation();
|
|
772
|
+
} finally {
|
|
773
|
+
release();
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
getStats() {
|
|
777
|
+
return {
|
|
778
|
+
activeCount,
|
|
779
|
+
maxConcurrent,
|
|
780
|
+
queueSize: queue.length,
|
|
781
|
+
maxQueueSize
|
|
782
|
+
};
|
|
783
|
+
},
|
|
784
|
+
reset() {
|
|
785
|
+
activeCount = 0;
|
|
786
|
+
while (queue.length > 0) {
|
|
787
|
+
const item = queue.shift();
|
|
788
|
+
item?.reject(new Error("Limiter reset"));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
function createCombinedLimiter(name, config) {
|
|
794
|
+
const rate = config.rate ? createRateLimiter(`${name}-rate`, config.rate) : void 0;
|
|
795
|
+
const concurrency = config.concurrency ? createConcurrencyLimiter(`${name}-concurrency`, config.concurrency) : void 0;
|
|
796
|
+
return {
|
|
797
|
+
rate,
|
|
798
|
+
concurrency,
|
|
799
|
+
async execute(operation) {
|
|
800
|
+
let op = operation;
|
|
801
|
+
if (rate) {
|
|
802
|
+
const originalOp = op;
|
|
803
|
+
op = () => rate.execute(originalOp);
|
|
804
|
+
}
|
|
805
|
+
if (concurrency) {
|
|
806
|
+
return concurrency.execute(op);
|
|
807
|
+
}
|
|
808
|
+
return op();
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
}
|
|
812
|
+
function createFixedWindowLimiter(name, config) {
|
|
813
|
+
const { limit, windowMs = 1e3, strategy = "wait" } = config;
|
|
814
|
+
let windowStart = Date.now();
|
|
815
|
+
let requestCount = 0;
|
|
816
|
+
const waitQueue = [];
|
|
817
|
+
function checkWindow() {
|
|
818
|
+
const now = Date.now();
|
|
819
|
+
const elapsed = now - windowStart;
|
|
820
|
+
if (elapsed >= windowMs) {
|
|
821
|
+
windowStart = now;
|
|
822
|
+
requestCount = 0;
|
|
823
|
+
return 0;
|
|
824
|
+
}
|
|
825
|
+
return windowMs - elapsed;
|
|
826
|
+
}
|
|
827
|
+
function tryConsume(cost) {
|
|
828
|
+
const remainingMs = checkWindow();
|
|
829
|
+
if (requestCount + cost <= limit) {
|
|
830
|
+
requestCount += cost;
|
|
831
|
+
return 0;
|
|
832
|
+
}
|
|
833
|
+
return remainingMs;
|
|
834
|
+
}
|
|
835
|
+
async function waitForWindow(cost) {
|
|
836
|
+
return new Promise((resolve) => {
|
|
837
|
+
const check = () => {
|
|
838
|
+
const waitTime = tryConsume(cost);
|
|
839
|
+
if (waitTime === 0) {
|
|
840
|
+
resolve();
|
|
841
|
+
} else {
|
|
842
|
+
waitQueue.push({ resolve: check, cost });
|
|
843
|
+
setTimeout(() => {
|
|
844
|
+
const idx = waitQueue.findIndex((w) => w.resolve === check);
|
|
845
|
+
if (idx !== -1) {
|
|
846
|
+
waitQueue.splice(idx, 1);
|
|
847
|
+
check();
|
|
848
|
+
}
|
|
849
|
+
}, waitTime);
|
|
850
|
+
}
|
|
851
|
+
};
|
|
852
|
+
check();
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
async execute(operation, cost = 1) {
|
|
857
|
+
if (cost > limit) {
|
|
858
|
+
throw {
|
|
859
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
860
|
+
limiterName: name,
|
|
861
|
+
retryAfterMs: windowMs
|
|
862
|
+
};
|
|
863
|
+
}
|
|
864
|
+
const waitTime = tryConsume(cost);
|
|
865
|
+
if (waitTime > 0) {
|
|
866
|
+
if (strategy === "reject") {
|
|
867
|
+
throw {
|
|
868
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
869
|
+
limiterName: name,
|
|
870
|
+
retryAfterMs: waitTime
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
await waitForWindow(cost);
|
|
874
|
+
}
|
|
875
|
+
return operation();
|
|
876
|
+
},
|
|
877
|
+
async executeResult(operation, cost = 1) {
|
|
878
|
+
if (cost > limit) {
|
|
879
|
+
return err2({
|
|
880
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
881
|
+
limiterName: name,
|
|
882
|
+
retryAfterMs: windowMs
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
const waitTime = tryConsume(cost);
|
|
886
|
+
if (waitTime > 0) {
|
|
887
|
+
if (strategy === "reject") {
|
|
888
|
+
return err2({
|
|
889
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
890
|
+
limiterName: name,
|
|
891
|
+
retryAfterMs: waitTime
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
await waitForWindow(cost);
|
|
895
|
+
}
|
|
896
|
+
return operation();
|
|
897
|
+
},
|
|
898
|
+
getStats() {
|
|
899
|
+
const remainingMs = checkWindow();
|
|
900
|
+
return {
|
|
901
|
+
requestCount,
|
|
902
|
+
limit,
|
|
903
|
+
windowMs,
|
|
904
|
+
remainingMs,
|
|
905
|
+
waitingCount: waitQueue.length
|
|
906
|
+
};
|
|
907
|
+
},
|
|
908
|
+
reset() {
|
|
909
|
+
windowStart = Date.now();
|
|
910
|
+
requestCount = 0;
|
|
911
|
+
waitQueue.length = 0;
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
function createCostBasedRateLimiter(name, config) {
|
|
916
|
+
const { tokensPerSecond, strategy = "wait" } = config;
|
|
917
|
+
const maxTokens = config.maxTokens ?? tokensPerSecond * 2;
|
|
918
|
+
let tokens = maxTokens;
|
|
919
|
+
let lastRefill = Date.now();
|
|
920
|
+
const refillRate = tokensPerSecond / 1e3;
|
|
921
|
+
const waitQueue = [];
|
|
922
|
+
function refill() {
|
|
923
|
+
const now = Date.now();
|
|
924
|
+
const elapsed = now - lastRefill;
|
|
925
|
+
const tokensToAdd = elapsed * refillRate;
|
|
926
|
+
tokens = Math.min(maxTokens, tokens + tokensToAdd);
|
|
927
|
+
lastRefill = now;
|
|
928
|
+
}
|
|
929
|
+
function tryConsume(cost) {
|
|
930
|
+
refill();
|
|
931
|
+
if (tokens >= cost) {
|
|
932
|
+
tokens -= cost;
|
|
933
|
+
return 0;
|
|
934
|
+
}
|
|
935
|
+
const tokensNeeded = cost - tokens;
|
|
936
|
+
return Math.ceil(tokensNeeded / refillRate);
|
|
937
|
+
}
|
|
938
|
+
async function waitForTokens(cost) {
|
|
939
|
+
return new Promise((resolve) => {
|
|
940
|
+
const check = () => {
|
|
941
|
+
const waitTime = tryConsume(cost);
|
|
942
|
+
if (waitTime === 0) {
|
|
943
|
+
resolve();
|
|
944
|
+
} else {
|
|
945
|
+
waitQueue.push({ check, cost });
|
|
946
|
+
setTimeout(() => {
|
|
947
|
+
const idx = waitQueue.findIndex((w) => w.check === check);
|
|
948
|
+
if (idx !== -1) {
|
|
949
|
+
waitQueue.splice(idx, 1);
|
|
950
|
+
check();
|
|
951
|
+
}
|
|
952
|
+
}, waitTime);
|
|
953
|
+
}
|
|
954
|
+
};
|
|
955
|
+
check();
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
async execute(operation, cost = 1) {
|
|
960
|
+
if (cost > maxTokens) {
|
|
961
|
+
throw {
|
|
962
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
963
|
+
limiterName: name,
|
|
964
|
+
retryAfterMs: Math.ceil(cost / refillRate)
|
|
965
|
+
};
|
|
966
|
+
}
|
|
967
|
+
const waitTime = tryConsume(cost);
|
|
968
|
+
if (waitTime > 0) {
|
|
969
|
+
if (strategy === "reject") {
|
|
970
|
+
throw {
|
|
971
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
972
|
+
limiterName: name,
|
|
973
|
+
retryAfterMs: waitTime
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
await waitForTokens(cost);
|
|
977
|
+
}
|
|
978
|
+
return operation();
|
|
979
|
+
},
|
|
980
|
+
async executeResult(operation, cost = 1) {
|
|
981
|
+
if (cost > maxTokens) {
|
|
982
|
+
return err2({
|
|
983
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
984
|
+
limiterName: name,
|
|
985
|
+
retryAfterMs: Math.ceil(cost / refillRate)
|
|
986
|
+
});
|
|
987
|
+
}
|
|
988
|
+
const waitTime = tryConsume(cost);
|
|
989
|
+
if (waitTime > 0) {
|
|
990
|
+
if (strategy === "reject") {
|
|
991
|
+
return err2({
|
|
992
|
+
type: "RATE_LIMIT_EXCEEDED",
|
|
993
|
+
limiterName: name,
|
|
994
|
+
retryAfterMs: waitTime
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
await waitForTokens(cost);
|
|
998
|
+
}
|
|
999
|
+
return operation();
|
|
1000
|
+
},
|
|
1001
|
+
getStats() {
|
|
1002
|
+
refill();
|
|
1003
|
+
return {
|
|
1004
|
+
availableTokens: tokens,
|
|
1005
|
+
maxTokens,
|
|
1006
|
+
tokensPerSecond,
|
|
1007
|
+
waitingCount: waitQueue.length
|
|
1008
|
+
};
|
|
1009
|
+
},
|
|
1010
|
+
reset() {
|
|
1011
|
+
tokens = maxTokens;
|
|
1012
|
+
lastRefill = Date.now();
|
|
1013
|
+
waitQueue.length = 0;
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
var rateLimiterPresets = {
|
|
1018
|
+
/**
|
|
1019
|
+
* Typical API rate limit (10 req/s).
|
|
1020
|
+
*/
|
|
1021
|
+
api: {
|
|
1022
|
+
maxPerSecond: 10,
|
|
1023
|
+
burstCapacity: 20,
|
|
1024
|
+
strategy: "wait"
|
|
1025
|
+
},
|
|
1026
|
+
/**
|
|
1027
|
+
* Database pool limit (concurrent connections).
|
|
1028
|
+
*/
|
|
1029
|
+
database: {
|
|
1030
|
+
maxConcurrent: 10,
|
|
1031
|
+
strategy: "queue",
|
|
1032
|
+
maxQueueSize: 100
|
|
1033
|
+
},
|
|
1034
|
+
/**
|
|
1035
|
+
* Aggressive rate limit for external APIs (5 req/s).
|
|
1036
|
+
*/
|
|
1037
|
+
external: {
|
|
1038
|
+
maxPerSecond: 5,
|
|
1039
|
+
burstCapacity: 10,
|
|
1040
|
+
strategy: "wait"
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
// src/cache.ts
|
|
1045
|
+
function toMs2(duration) {
|
|
1046
|
+
if (typeof duration === "string") {
|
|
1047
|
+
const parsed = parse(duration);
|
|
1048
|
+
if (!parsed) {
|
|
1049
|
+
throw new Error(`Invalid duration string: ${duration}`);
|
|
1050
|
+
}
|
|
1051
|
+
return parsed.millis;
|
|
1052
|
+
}
|
|
1053
|
+
return duration.millis;
|
|
1054
|
+
}
|
|
1055
|
+
function cached(fn) {
|
|
1056
|
+
let state = { status: "empty" };
|
|
1057
|
+
return async () => {
|
|
1058
|
+
if (state.status === "filled") {
|
|
1059
|
+
return state.value;
|
|
1060
|
+
}
|
|
1061
|
+
if (state.status === "pending") {
|
|
1062
|
+
return state.promise;
|
|
1063
|
+
}
|
|
1064
|
+
const promise = Promise.resolve(fn()).then((value) => {
|
|
1065
|
+
state = { status: "filled", value };
|
|
1066
|
+
return value;
|
|
1067
|
+
});
|
|
1068
|
+
state = { status: "pending", promise };
|
|
1069
|
+
return promise;
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function cachedWithTTL(fn, options) {
|
|
1073
|
+
const ttlMs = toMs2(options.ttl);
|
|
1074
|
+
let state = { status: "empty" };
|
|
1075
|
+
return async () => {
|
|
1076
|
+
const now = Date.now();
|
|
1077
|
+
if (state.status === "filled" && now < state.expiresAt) {
|
|
1078
|
+
return state.value;
|
|
1079
|
+
}
|
|
1080
|
+
if (state.status === "pending") {
|
|
1081
|
+
return state.promise;
|
|
1082
|
+
}
|
|
1083
|
+
const promise = Promise.resolve(fn()).then((value) => {
|
|
1084
|
+
state = {
|
|
1085
|
+
status: "filled",
|
|
1086
|
+
value,
|
|
1087
|
+
expiresAt: Date.now() + ttlMs
|
|
1088
|
+
};
|
|
1089
|
+
return value;
|
|
1090
|
+
});
|
|
1091
|
+
state = { status: "pending", promise };
|
|
1092
|
+
return promise;
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function cachedFunction(fn, options = {}) {
|
|
1096
|
+
const {
|
|
1097
|
+
keyFn = (...args) => JSON.stringify(args),
|
|
1098
|
+
maxSize = Infinity
|
|
1099
|
+
} = options;
|
|
1100
|
+
const ttlMs = options.ttl ? toMs2(options.ttl) : void 0;
|
|
1101
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1102
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1103
|
+
let hits = 0;
|
|
1104
|
+
let misses = 0;
|
|
1105
|
+
function evictOldest() {
|
|
1106
|
+
if (cache.size <= maxSize) return;
|
|
1107
|
+
let oldestKey = null;
|
|
1108
|
+
let oldestTime = Infinity;
|
|
1109
|
+
for (const [key, entry] of cache.entries()) {
|
|
1110
|
+
if (entry.timestamp < oldestTime) {
|
|
1111
|
+
oldestTime = entry.timestamp;
|
|
1112
|
+
oldestKey = key;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (oldestKey) {
|
|
1116
|
+
cache.delete(oldestKey);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
const memoized = async (...args) => {
|
|
1120
|
+
const key = keyFn(...args);
|
|
1121
|
+
const now = Date.now();
|
|
1122
|
+
const cached2 = cache.get(key);
|
|
1123
|
+
if (cached2) {
|
|
1124
|
+
if (cached2.expiresAt && now >= cached2.expiresAt) {
|
|
1125
|
+
cache.delete(key);
|
|
1126
|
+
} else {
|
|
1127
|
+
hits++;
|
|
1128
|
+
return cached2.value;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const pendingPromise = pending.get(key);
|
|
1132
|
+
if (pendingPromise) {
|
|
1133
|
+
return pendingPromise;
|
|
1134
|
+
}
|
|
1135
|
+
misses++;
|
|
1136
|
+
const promise = Promise.resolve(fn(...args)).then((value) => {
|
|
1137
|
+
const entry = {
|
|
1138
|
+
value,
|
|
1139
|
+
timestamp: Date.now(),
|
|
1140
|
+
expiresAt: ttlMs ? Date.now() + ttlMs : void 0
|
|
1141
|
+
};
|
|
1142
|
+
cache.set(key, entry);
|
|
1143
|
+
pending.delete(key);
|
|
1144
|
+
evictOldest();
|
|
1145
|
+
return value;
|
|
1146
|
+
});
|
|
1147
|
+
pending.set(key, promise);
|
|
1148
|
+
try {
|
|
1149
|
+
return await promise;
|
|
1150
|
+
} catch (error) {
|
|
1151
|
+
pending.delete(key);
|
|
1152
|
+
throw error;
|
|
1153
|
+
}
|
|
1154
|
+
};
|
|
1155
|
+
memoized.clear = () => {
|
|
1156
|
+
cache.clear();
|
|
1157
|
+
pending.clear();
|
|
1158
|
+
};
|
|
1159
|
+
memoized.delete = (...args) => {
|
|
1160
|
+
const key = keyFn(...args);
|
|
1161
|
+
return cache.delete(key);
|
|
1162
|
+
};
|
|
1163
|
+
memoized.has = (...args) => {
|
|
1164
|
+
const key = keyFn(...args);
|
|
1165
|
+
const entry = cache.get(key);
|
|
1166
|
+
if (!entry) return false;
|
|
1167
|
+
if (entry.expiresAt && Date.now() >= entry.expiresAt) {
|
|
1168
|
+
cache.delete(key);
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
return true;
|
|
1172
|
+
};
|
|
1173
|
+
memoized.getStats = () => ({
|
|
1174
|
+
hits,
|
|
1175
|
+
misses,
|
|
1176
|
+
size: cache.size
|
|
1177
|
+
});
|
|
1178
|
+
return memoized;
|
|
1179
|
+
}
|
|
1180
|
+
function once(fn) {
|
|
1181
|
+
let state = { status: "idle" };
|
|
1182
|
+
const onceFn = async () => {
|
|
1183
|
+
if (state.status === "done") {
|
|
1184
|
+
return state.value;
|
|
1185
|
+
}
|
|
1186
|
+
if (state.status === "failed") {
|
|
1187
|
+
throw state.error;
|
|
1188
|
+
}
|
|
1189
|
+
if (state.status === "running") {
|
|
1190
|
+
return state.promise;
|
|
1191
|
+
}
|
|
1192
|
+
const promise = Promise.resolve(fn()).then((value) => {
|
|
1193
|
+
state = { status: "done", value };
|
|
1194
|
+
return value;
|
|
1195
|
+
}).catch((error) => {
|
|
1196
|
+
state = { status: "failed", error };
|
|
1197
|
+
throw error;
|
|
1198
|
+
});
|
|
1199
|
+
state = { status: "running", promise };
|
|
1200
|
+
return promise;
|
|
1201
|
+
};
|
|
1202
|
+
Object.defineProperty(onceFn, "called", {
|
|
1203
|
+
get: () => state.status !== "idle"
|
|
1204
|
+
});
|
|
1205
|
+
Object.defineProperty(onceFn, "completed", {
|
|
1206
|
+
get: () => state.status === "done"
|
|
1207
|
+
});
|
|
1208
|
+
Object.defineProperty(onceFn, "failed", {
|
|
1209
|
+
get: () => state.status === "failed"
|
|
1210
|
+
});
|
|
1211
|
+
onceFn.reset = () => {
|
|
1212
|
+
state = { status: "idle" };
|
|
1213
|
+
};
|
|
1214
|
+
return onceFn;
|
|
1215
|
+
}
|
|
1216
|
+
function createCache(config = {}) {
|
|
1217
|
+
const { maxSize = Infinity } = config;
|
|
1218
|
+
const defaultTTLMs = config.defaultTTL ? toMs2(config.defaultTTL) : void 0;
|
|
1219
|
+
const store = /* @__PURE__ */ new Map();
|
|
1220
|
+
let hits = 0;
|
|
1221
|
+
let misses = 0;
|
|
1222
|
+
function isExpired(entry) {
|
|
1223
|
+
return entry.expiresAt !== void 0 && Date.now() >= entry.expiresAt;
|
|
1224
|
+
}
|
|
1225
|
+
function evictOldest() {
|
|
1226
|
+
if (store.size <= maxSize) return;
|
|
1227
|
+
let oldestKey = null;
|
|
1228
|
+
let oldestTime = Infinity;
|
|
1229
|
+
for (const [key, entry] of store.entries()) {
|
|
1230
|
+
if (entry.timestamp < oldestTime) {
|
|
1231
|
+
oldestTime = entry.timestamp;
|
|
1232
|
+
oldestKey = key;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
if (oldestKey !== null) {
|
|
1236
|
+
store.delete(oldestKey);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
return {
|
|
1240
|
+
get(key) {
|
|
1241
|
+
const entry = store.get(key);
|
|
1242
|
+
if (!entry) {
|
|
1243
|
+
misses++;
|
|
1244
|
+
return void 0;
|
|
1245
|
+
}
|
|
1246
|
+
if (isExpired(entry)) {
|
|
1247
|
+
store.delete(key);
|
|
1248
|
+
misses++;
|
|
1249
|
+
return void 0;
|
|
1250
|
+
}
|
|
1251
|
+
hits++;
|
|
1252
|
+
return entry.value;
|
|
1253
|
+
},
|
|
1254
|
+
set(key, value, options) {
|
|
1255
|
+
const ttlMs = options?.ttl ? toMs2(options.ttl) : defaultTTLMs;
|
|
1256
|
+
const now = Date.now();
|
|
1257
|
+
store.set(key, {
|
|
1258
|
+
value,
|
|
1259
|
+
timestamp: now,
|
|
1260
|
+
expiresAt: ttlMs ? now + ttlMs : void 0
|
|
1261
|
+
});
|
|
1262
|
+
evictOldest();
|
|
1263
|
+
},
|
|
1264
|
+
has(key) {
|
|
1265
|
+
const entry = store.get(key);
|
|
1266
|
+
if (!entry) return false;
|
|
1267
|
+
if (isExpired(entry)) {
|
|
1268
|
+
store.delete(key);
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
return true;
|
|
1272
|
+
},
|
|
1273
|
+
delete(key) {
|
|
1274
|
+
return store.delete(key);
|
|
1275
|
+
},
|
|
1276
|
+
clear() {
|
|
1277
|
+
store.clear();
|
|
1278
|
+
},
|
|
1279
|
+
get size() {
|
|
1280
|
+
return store.size;
|
|
1281
|
+
},
|
|
1282
|
+
getStats() {
|
|
1283
|
+
return { hits, misses, size: store.size };
|
|
1284
|
+
}
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
// src/singleflight.ts
|
|
1289
|
+
function singleflight(operation, options) {
|
|
1290
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
1291
|
+
const cache = options.ttl ? /* @__PURE__ */ new Map() : null;
|
|
1292
|
+
return async (...args) => {
|
|
1293
|
+
const key = options.key(...args);
|
|
1294
|
+
if (cache) {
|
|
1295
|
+
const cached2 = cache.get(key);
|
|
1296
|
+
if (cached2 && cached2.expiresAt > Date.now()) {
|
|
1297
|
+
return cached2.result;
|
|
1298
|
+
}
|
|
1299
|
+
if (cached2) {
|
|
1300
|
+
cache.delete(key);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
const existing = inflight.get(key);
|
|
1304
|
+
if (existing) {
|
|
1305
|
+
return existing;
|
|
1306
|
+
}
|
|
1307
|
+
const promise = operation(...args).then((result) => {
|
|
1308
|
+
if (cache && options.ttl && result.ok) {
|
|
1309
|
+
cache.set(key, {
|
|
1310
|
+
result,
|
|
1311
|
+
expiresAt: Date.now() + options.ttl
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
return result;
|
|
1315
|
+
}).finally(() => {
|
|
1316
|
+
inflight.delete(key);
|
|
1317
|
+
});
|
|
1318
|
+
inflight.set(key, promise);
|
|
1319
|
+
return promise;
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
function createSingleflightGroup() {
|
|
1323
|
+
const inflight = /* @__PURE__ */ new Map();
|
|
1324
|
+
return {
|
|
1325
|
+
execute: async (key, operation) => {
|
|
1326
|
+
const existing = inflight.get(key);
|
|
1327
|
+
if (existing) {
|
|
1328
|
+
return existing;
|
|
1329
|
+
}
|
|
1330
|
+
const promise = operation().then((result) => result).finally(() => {
|
|
1331
|
+
inflight.delete(key);
|
|
1332
|
+
});
|
|
1333
|
+
inflight.set(key, promise);
|
|
1334
|
+
return promise;
|
|
1335
|
+
},
|
|
1336
|
+
isInflight: (key) => inflight.has(key),
|
|
1337
|
+
size: () => inflight.size,
|
|
1338
|
+
clear: () => inflight.clear()
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// src/policies.ts
|
|
1343
|
+
function mergePolicies(...policies) {
|
|
1344
|
+
const result = {};
|
|
1345
|
+
for (const policy of policies) {
|
|
1346
|
+
if (policy.key !== void 0) result.key = policy.key;
|
|
1347
|
+
if (policy.retry !== void 0) {
|
|
1348
|
+
result.retry = result.retry ? { ...result.retry, ...policy.retry } : { ...policy.retry };
|
|
1349
|
+
}
|
|
1350
|
+
if (policy.timeout !== void 0) {
|
|
1351
|
+
result.timeout = result.timeout ? { ...result.timeout, ...policy.timeout } : { ...policy.timeout };
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return result;
|
|
1355
|
+
}
|
|
1356
|
+
function createPolicyApplier(...basePolicies) {
|
|
1357
|
+
const basePolicy = mergePolicies(...basePolicies);
|
|
1358
|
+
return (stepOptions2) => {
|
|
1359
|
+
const opts = stepOptions2 ?? {};
|
|
1360
|
+
return mergePolicies(basePolicy, opts);
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
function createPolicyBundle(name, ...policies) {
|
|
1364
|
+
return {
|
|
1365
|
+
name,
|
|
1366
|
+
policy: mergePolicies(...policies)
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function retryPolicy(options) {
|
|
1370
|
+
return { retry: options };
|
|
1371
|
+
}
|
|
1372
|
+
var retryPolicies = {
|
|
1373
|
+
/**
|
|
1374
|
+
* No retry - fail immediately on error.
|
|
1375
|
+
*/
|
|
1376
|
+
none: retryPolicy({ attempts: 1 }),
|
|
1377
|
+
/**
|
|
1378
|
+
* Quick retry for transient errors (3 attempts, fast backoff).
|
|
1379
|
+
*/
|
|
1380
|
+
transient: retryPolicy({
|
|
1381
|
+
attempts: 3,
|
|
1382
|
+
backoff: "exponential",
|
|
1383
|
+
initialDelay: 100,
|
|
1384
|
+
maxDelay: 1e3,
|
|
1385
|
+
jitter: true
|
|
1386
|
+
}),
|
|
1387
|
+
/**
|
|
1388
|
+
* Standard retry for API calls (3 attempts, moderate backoff).
|
|
1389
|
+
*/
|
|
1390
|
+
standard: retryPolicy({
|
|
1391
|
+
attempts: 3,
|
|
1392
|
+
backoff: "exponential",
|
|
1393
|
+
initialDelay: 200,
|
|
1394
|
+
maxDelay: 5e3,
|
|
1395
|
+
jitter: true
|
|
1396
|
+
}),
|
|
1397
|
+
/**
|
|
1398
|
+
* Aggressive retry for critical operations (5 attempts, longer backoff).
|
|
1399
|
+
*/
|
|
1400
|
+
aggressive: retryPolicy({
|
|
1401
|
+
attempts: 5,
|
|
1402
|
+
backoff: "exponential",
|
|
1403
|
+
initialDelay: 500,
|
|
1404
|
+
maxDelay: 3e4,
|
|
1405
|
+
jitter: true
|
|
1406
|
+
}),
|
|
1407
|
+
/**
|
|
1408
|
+
* Fixed interval retry (useful for polling).
|
|
1409
|
+
*/
|
|
1410
|
+
fixed: (attempts, delayMs) => retryPolicy({
|
|
1411
|
+
attempts,
|
|
1412
|
+
backoff: "fixed",
|
|
1413
|
+
initialDelay: delayMs,
|
|
1414
|
+
jitter: false
|
|
1415
|
+
}),
|
|
1416
|
+
/**
|
|
1417
|
+
* Linear backoff retry.
|
|
1418
|
+
*/
|
|
1419
|
+
linear: (attempts, initialDelay) => retryPolicy({
|
|
1420
|
+
attempts,
|
|
1421
|
+
backoff: "linear",
|
|
1422
|
+
initialDelay,
|
|
1423
|
+
jitter: true
|
|
1424
|
+
}),
|
|
1425
|
+
/**
|
|
1426
|
+
* Custom retry policy builder.
|
|
1427
|
+
*/
|
|
1428
|
+
custom: (options) => retryPolicy({
|
|
1429
|
+
backoff: "exponential",
|
|
1430
|
+
initialDelay: 100,
|
|
1431
|
+
maxDelay: 3e4,
|
|
1432
|
+
jitter: true,
|
|
1433
|
+
...options
|
|
1434
|
+
})
|
|
1435
|
+
};
|
|
1436
|
+
function timeoutPolicy(options) {
|
|
1437
|
+
return { timeout: options };
|
|
1438
|
+
}
|
|
1439
|
+
var timeoutPolicies = {
|
|
1440
|
+
/**
|
|
1441
|
+
* No timeout.
|
|
1442
|
+
*/
|
|
1443
|
+
none: {},
|
|
1444
|
+
/**
|
|
1445
|
+
* Fast timeout for quick operations (1 second).
|
|
1446
|
+
*/
|
|
1447
|
+
fast: timeoutPolicy({ ms: 1e3 }),
|
|
1448
|
+
/**
|
|
1449
|
+
* Standard API timeout (5 seconds).
|
|
1450
|
+
*/
|
|
1451
|
+
api: timeoutPolicy({ ms: 5e3 }),
|
|
1452
|
+
/**
|
|
1453
|
+
* Extended timeout for slower operations (30 seconds).
|
|
1454
|
+
*/
|
|
1455
|
+
extended: timeoutPolicy({ ms: 3e4 }),
|
|
1456
|
+
/**
|
|
1457
|
+
* Long timeout for batch operations (2 minutes).
|
|
1458
|
+
*/
|
|
1459
|
+
long: timeoutPolicy({ ms: 12e4 }),
|
|
1460
|
+
/**
|
|
1461
|
+
* Custom timeout in milliseconds.
|
|
1462
|
+
*/
|
|
1463
|
+
ms: (ms) => timeoutPolicy({ ms }),
|
|
1464
|
+
/**
|
|
1465
|
+
* Custom timeout in seconds.
|
|
1466
|
+
*/
|
|
1467
|
+
seconds: (seconds2) => timeoutPolicy({ ms: seconds2 * 1e3 }),
|
|
1468
|
+
/**
|
|
1469
|
+
* Timeout with custom error.
|
|
1470
|
+
*/
|
|
1471
|
+
withError: (ms, error) => timeoutPolicy({ ms, error }),
|
|
1472
|
+
/**
|
|
1473
|
+
* Timeout with AbortSignal support.
|
|
1474
|
+
*/
|
|
1475
|
+
withSignal: (ms) => timeoutPolicy({ ms, signal: true })
|
|
1476
|
+
};
|
|
1477
|
+
var servicePolicies = {
|
|
1478
|
+
/**
|
|
1479
|
+
* Policy for external HTTP APIs.
|
|
1480
|
+
* - 5 second timeout
|
|
1481
|
+
* - 3 retries with exponential backoff
|
|
1482
|
+
*/
|
|
1483
|
+
httpApi: mergePolicies(
|
|
1484
|
+
timeoutPolicies.api,
|
|
1485
|
+
retryPolicies.standard
|
|
1486
|
+
),
|
|
1487
|
+
/**
|
|
1488
|
+
* Policy for database operations.
|
|
1489
|
+
* - 30 second timeout
|
|
1490
|
+
* - 2 retries for transient errors
|
|
1491
|
+
*/
|
|
1492
|
+
database: mergePolicies(
|
|
1493
|
+
timeoutPolicies.extended,
|
|
1494
|
+
retryPolicy({
|
|
1495
|
+
attempts: 2,
|
|
1496
|
+
backoff: "exponential",
|
|
1497
|
+
initialDelay: 100,
|
|
1498
|
+
maxDelay: 2e3,
|
|
1499
|
+
jitter: true
|
|
1500
|
+
})
|
|
1501
|
+
),
|
|
1502
|
+
/**
|
|
1503
|
+
* Policy for cache operations.
|
|
1504
|
+
* - 1 second timeout
|
|
1505
|
+
* - No retry (cache misses are not errors)
|
|
1506
|
+
*/
|
|
1507
|
+
cache: mergePolicies(
|
|
1508
|
+
timeoutPolicies.fast,
|
|
1509
|
+
retryPolicies.none
|
|
1510
|
+
),
|
|
1511
|
+
/**
|
|
1512
|
+
* Policy for message queue operations.
|
|
1513
|
+
* - 30 second timeout
|
|
1514
|
+
* - 5 retries with longer backoff
|
|
1515
|
+
*/
|
|
1516
|
+
messageQueue: mergePolicies(
|
|
1517
|
+
timeoutPolicies.extended,
|
|
1518
|
+
retryPolicies.aggressive
|
|
1519
|
+
),
|
|
1520
|
+
/**
|
|
1521
|
+
* Policy for file operations.
|
|
1522
|
+
* - 2 minute timeout
|
|
1523
|
+
* - 3 retries
|
|
1524
|
+
*/
|
|
1525
|
+
fileSystem: mergePolicies(
|
|
1526
|
+
timeoutPolicies.long,
|
|
1527
|
+
retryPolicies.standard
|
|
1528
|
+
),
|
|
1529
|
+
/**
|
|
1530
|
+
* Policy for third-party services with rate limits.
|
|
1531
|
+
* - 10 second timeout
|
|
1532
|
+
* - 5 retries with linear backoff
|
|
1533
|
+
*/
|
|
1534
|
+
rateLimited: mergePolicies(
|
|
1535
|
+
timeoutPolicy({ ms: 1e4 }),
|
|
1536
|
+
retryPolicy({
|
|
1537
|
+
attempts: 5,
|
|
1538
|
+
backoff: "linear",
|
|
1539
|
+
initialDelay: 1e3,
|
|
1540
|
+
maxDelay: 1e4,
|
|
1541
|
+
jitter: true
|
|
1542
|
+
})
|
|
1543
|
+
)
|
|
1544
|
+
};
|
|
1545
|
+
function withPolicy(policy, stepOptions2) {
|
|
1546
|
+
const opts = stepOptions2 ?? {};
|
|
1547
|
+
return mergePolicies(policy, opts);
|
|
1548
|
+
}
|
|
1549
|
+
function withPolicies(policies, stepOptions2) {
|
|
1550
|
+
const opts = stepOptions2 ?? {};
|
|
1551
|
+
return mergePolicies(...policies, opts);
|
|
1552
|
+
}
|
|
1553
|
+
function conditionalPolicy(condition, policy, elsePolicy = {}) {
|
|
1554
|
+
return condition ? policy : elsePolicy;
|
|
1555
|
+
}
|
|
1556
|
+
function envPolicy(envPolicies, currentEnv = process.env.NODE_ENV ?? "development", defaultPolicy = {}) {
|
|
1557
|
+
return envPolicies[currentEnv] ?? defaultPolicy;
|
|
1558
|
+
}
|
|
1559
|
+
function createPolicyRegistry() {
|
|
1560
|
+
const policies = /* @__PURE__ */ new Map();
|
|
1561
|
+
return {
|
|
1562
|
+
register(name, policy) {
|
|
1563
|
+
policies.set(name, policy);
|
|
1564
|
+
},
|
|
1565
|
+
get(name) {
|
|
1566
|
+
return policies.get(name);
|
|
1567
|
+
},
|
|
1568
|
+
has(name) {
|
|
1569
|
+
return policies.has(name);
|
|
1570
|
+
},
|
|
1571
|
+
names() {
|
|
1572
|
+
return Array.from(policies.keys());
|
|
1573
|
+
},
|
|
1574
|
+
apply(policyName, stepOptions2) {
|
|
1575
|
+
const policy = policies.get(policyName);
|
|
1576
|
+
if (!policy) {
|
|
1577
|
+
throw new Error(`Policy not found: ${policyName}`);
|
|
1578
|
+
}
|
|
1579
|
+
return withPolicy(policy, stepOptions2);
|
|
1580
|
+
}
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
function stepOptions() {
|
|
1584
|
+
const policies = [];
|
|
1585
|
+
const builder = {
|
|
1586
|
+
key(key) {
|
|
1587
|
+
policies.push({ key });
|
|
1588
|
+
return builder;
|
|
1589
|
+
},
|
|
1590
|
+
policy(policy) {
|
|
1591
|
+
policies.push(policy);
|
|
1592
|
+
return builder;
|
|
1593
|
+
},
|
|
1594
|
+
timeout(ms) {
|
|
1595
|
+
policies.push(timeoutPolicy({ ms }));
|
|
1596
|
+
return builder;
|
|
1597
|
+
},
|
|
1598
|
+
retry(options) {
|
|
1599
|
+
policies.push(retryPolicy(options));
|
|
1600
|
+
return builder;
|
|
1601
|
+
},
|
|
1602
|
+
retries(attempts) {
|
|
1603
|
+
policies.push(retryPolicies.custom({ attempts }));
|
|
1604
|
+
return builder;
|
|
1605
|
+
},
|
|
1606
|
+
build() {
|
|
1607
|
+
return mergePolicies(...policies);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
return builder;
|
|
1611
|
+
}
|
|
1612
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1613
|
+
0 && (module.exports = {
|
|
1614
|
+
CircuitOpenError,
|
|
1615
|
+
cached,
|
|
1616
|
+
cachedFunction,
|
|
1617
|
+
cachedWithTTL,
|
|
1618
|
+
circuitBreakerPresets,
|
|
1619
|
+
conditionalPolicy,
|
|
1620
|
+
createCache,
|
|
1621
|
+
createCircuitBreaker,
|
|
1622
|
+
createCombinedLimiter,
|
|
1623
|
+
createConcurrencyLimiter,
|
|
1624
|
+
createCostBasedRateLimiter,
|
|
1625
|
+
createFixedWindowLimiter,
|
|
1626
|
+
createPolicyApplier,
|
|
1627
|
+
createPolicyBundle,
|
|
1628
|
+
createPolicyRegistry,
|
|
1629
|
+
createRateLimiter,
|
|
1630
|
+
createSingleflightGroup,
|
|
1631
|
+
envPolicy,
|
|
1632
|
+
fallback,
|
|
1633
|
+
isCircuitOpenError,
|
|
1634
|
+
isQueueFullError,
|
|
1635
|
+
isRateLimitExceededError,
|
|
1636
|
+
mergePolicies,
|
|
1637
|
+
once,
|
|
1638
|
+
rateLimiterPresets,
|
|
1639
|
+
retry,
|
|
1640
|
+
retryPolicies,
|
|
1641
|
+
retryPolicy,
|
|
1642
|
+
servicePolicies,
|
|
1643
|
+
singleflight,
|
|
1644
|
+
stepOptions,
|
|
1645
|
+
timeout,
|
|
1646
|
+
timeoutPolicies,
|
|
1647
|
+
timeoutPolicy,
|
|
1648
|
+
withPolicies,
|
|
1649
|
+
withPolicy
|
|
1650
|
+
});
|
|
1651
|
+
//# sourceMappingURL=reliability.cjs.map
|