@fetchkit/ffetch 5.2.0 → 5.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/README.md +27 -15
- package/dist/chunk-X64PXWPN.min.js +2 -0
- package/dist/chunk-X64PXWPN.min.js.map +1 -0
- package/dist/{chunk-RV6LC73N.js → chunk-ZIJ2DZYZ.js} +7 -1
- package/dist/chunk-ZIJ2DZYZ.js.map +1 -0
- package/dist/{error-XBHPSMAQ.js → error-H4GGJSCM.js} +4 -2
- package/dist/error-HT5UPPWM.min.js +2 -0
- package/dist/index.cjs +53 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +49 -39
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/plugins/bulkhead.cjs +137 -0
- package/dist/plugins/bulkhead.cjs.map +1 -0
- package/dist/plugins/bulkhead.d.cts +15 -0
- package/dist/plugins/bulkhead.d.ts +15 -0
- package/dist/plugins/bulkhead.js +96 -0
- package/dist/plugins/bulkhead.js.map +1 -0
- package/dist/plugins/circuit.cjs +14 -8
- package/dist/plugins/circuit.cjs.map +1 -1
- package/dist/plugins/circuit.d.cts +16 -3
- package/dist/plugins/circuit.d.ts +16 -3
- package/dist/plugins/circuit.js +15 -9
- package/dist/plugins/circuit.js.map +1 -1
- package/dist/plugins/hedge.cjs +127 -0
- package/dist/plugins/hedge.cjs.map +1 -0
- package/dist/plugins/hedge.d.cts +12 -0
- package/dist/plugins/hedge.d.ts +12 -0
- package/dist/plugins/hedge.js +102 -0
- package/dist/plugins/hedge.js.map +1 -0
- package/package.json +12 -2
- package/dist/chunk-RV6LC73N.js.map +0 -1
- package/dist/chunk-UP35S5ZH.min.js +0 -2
- package/dist/chunk-UP35S5ZH.min.js.map +0 -1
- package/dist/error-7EEQP46E.min.js +0 -2
- /package/dist/{error-7EEQP46E.min.js.map → error-H4GGJSCM.js.map} +0 -0
- /package/dist/{error-XBHPSMAQ.js.map → error-HT5UPPWM.min.js.map} +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
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/plugins/bulkhead.ts
|
|
21
|
+
var bulkhead_exports = {};
|
|
22
|
+
__export(bulkhead_exports, {
|
|
23
|
+
bulkheadPlugin: () => bulkheadPlugin
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(bulkhead_exports);
|
|
26
|
+
|
|
27
|
+
// src/error.ts
|
|
28
|
+
var BaseError = class extends Error {
|
|
29
|
+
constructor(name, message, cause) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.name = name;
|
|
32
|
+
if (cause !== void 0) this.cause = cause;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var BulkheadFullError = class extends BaseError {
|
|
36
|
+
constructor(message = "Bulkhead queue is full", cause) {
|
|
37
|
+
super("BulkheadFullError", message, cause);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var AbortError = class extends BaseError {
|
|
41
|
+
constructor(message = "Request was aborted", cause) {
|
|
42
|
+
super("AbortError", message, cause);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/plugins/bulkhead.ts
|
|
47
|
+
function bulkheadPlugin(options) {
|
|
48
|
+
const { maxConcurrent, maxQueue, onReject, order = 5 } = options;
|
|
49
|
+
let activeCount = 0;
|
|
50
|
+
const queue = [];
|
|
51
|
+
function makeAbortError(signal) {
|
|
52
|
+
return new AbortError("Request was aborted", signal.reason);
|
|
53
|
+
}
|
|
54
|
+
function drainQueue() {
|
|
55
|
+
while (activeCount < maxConcurrent && queue.length > 0) {
|
|
56
|
+
const next = queue.shift();
|
|
57
|
+
next.cleanup();
|
|
58
|
+
if (next.request.signal.aborted) {
|
|
59
|
+
next.reject(makeAbortError(next.request.signal));
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
activeCount++;
|
|
63
|
+
next.resolve();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function release() {
|
|
67
|
+
activeCount = Math.max(0, activeCount - 1);
|
|
68
|
+
drainQueue();
|
|
69
|
+
}
|
|
70
|
+
async function acquire(request) {
|
|
71
|
+
if (activeCount < maxConcurrent) {
|
|
72
|
+
activeCount++;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (typeof maxQueue === "number" && maxQueue >= 0 && queue.length >= maxQueue) {
|
|
76
|
+
await onReject?.(request);
|
|
77
|
+
throw new BulkheadFullError("Bulkhead queue is full");
|
|
78
|
+
}
|
|
79
|
+
await new Promise((resolve, reject) => {
|
|
80
|
+
const entry = {
|
|
81
|
+
request,
|
|
82
|
+
resolve,
|
|
83
|
+
reject,
|
|
84
|
+
cleanup: () => {
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const onAbort = () => {
|
|
88
|
+
const idx = queue.indexOf(entry);
|
|
89
|
+
if (idx >= 0) {
|
|
90
|
+
queue.splice(idx, 1);
|
|
91
|
+
}
|
|
92
|
+
entry.cleanup();
|
|
93
|
+
reject(makeAbortError(request.signal));
|
|
94
|
+
};
|
|
95
|
+
entry.cleanup = () => {
|
|
96
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
97
|
+
};
|
|
98
|
+
if (request.signal.aborted) {
|
|
99
|
+
reject(makeAbortError(request.signal));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
103
|
+
queue.push(entry);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
name: "bulkhead",
|
|
108
|
+
order,
|
|
109
|
+
setup: ({ defineExtension }) => {
|
|
110
|
+
defineExtension("activeCount", {
|
|
111
|
+
get: () => activeCount,
|
|
112
|
+
enumerable: true
|
|
113
|
+
});
|
|
114
|
+
defineExtension("queueDepth", {
|
|
115
|
+
get: () => queue.length,
|
|
116
|
+
enumerable: true
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
wrapDispatch: (next) => async (ctx) => {
|
|
120
|
+
let acquired = false;
|
|
121
|
+
try {
|
|
122
|
+
await acquire(ctx.request);
|
|
123
|
+
acquired = true;
|
|
124
|
+
return await next(ctx);
|
|
125
|
+
} finally {
|
|
126
|
+
if (acquired) {
|
|
127
|
+
release();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
134
|
+
0 && (module.exports = {
|
|
135
|
+
bulkheadPlugin
|
|
136
|
+
});
|
|
137
|
+
//# sourceMappingURL=bulkhead.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/bulkhead.ts","../../src/error.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\nimport { AbortError, BulkheadFullError } from '../error.js'\n\nexport type BulkheadPluginExtension = {\n activeCount: number\n queueDepth: number\n}\n\nexport type BulkheadPluginOptions = {\n maxConcurrent: number\n maxQueue?: number\n onReject?: (req: Request) => void | Promise<void>\n order?: number\n}\n\ntype QueueEntry = {\n request: Request\n resolve: () => void\n reject: (err: unknown) => void\n cleanup: () => void\n}\n\nexport function bulkheadPlugin(\n options: BulkheadPluginOptions\n): ClientPlugin<BulkheadPluginExtension> {\n const { maxConcurrent, maxQueue, onReject, order = 5 } = options\n\n let activeCount = 0\n const queue: QueueEntry[] = []\n\n function makeAbortError(signal: AbortSignal): AbortError {\n return new AbortError('Request was aborted', signal.reason)\n }\n\n function drainQueue(): void {\n while (activeCount < maxConcurrent && queue.length > 0) {\n const next = queue.shift()!\n next.cleanup()\n\n if (next.request.signal.aborted) {\n next.reject(makeAbortError(next.request.signal))\n continue\n }\n\n activeCount++\n next.resolve()\n }\n }\n\n function release(): void {\n activeCount = Math.max(0, activeCount - 1)\n drainQueue()\n }\n\n async function acquire(request: Request): Promise<void> {\n if (activeCount < maxConcurrent) {\n activeCount++\n return\n }\n\n if (\n typeof maxQueue === 'number' &&\n maxQueue >= 0 &&\n queue.length >= maxQueue\n ) {\n await onReject?.(request)\n throw new BulkheadFullError('Bulkhead queue is full')\n }\n\n await new Promise<void>((resolve, reject) => {\n const entry: QueueEntry = {\n request,\n resolve,\n reject,\n cleanup: () => {},\n }\n\n const onAbort = () => {\n const idx = queue.indexOf(entry)\n if (idx >= 0) {\n queue.splice(idx, 1)\n }\n entry.cleanup()\n reject(makeAbortError(request.signal))\n }\n\n entry.cleanup = () => {\n request.signal.removeEventListener('abort', onAbort)\n }\n\n if (request.signal.aborted) {\n reject(makeAbortError(request.signal))\n return\n }\n\n request.signal.addEventListener('abort', onAbort, { once: true })\n queue.push(entry)\n })\n }\n\n return {\n name: 'bulkhead',\n order,\n setup: ({ defineExtension }) => {\n defineExtension('activeCount', {\n get: () => activeCount,\n enumerable: true,\n })\n defineExtension('queueDepth', {\n get: () => queue.length,\n enumerable: true,\n })\n },\n wrapDispatch: (next) => async (ctx) => {\n let acquired = false\n try {\n await acquire(ctx.request)\n acquired = true\n return await next(ctx)\n } finally {\n if (acquired) {\n release()\n }\n }\n },\n }\n}\n","// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class BulkheadFullError extends BaseError {\n constructor(message = 'Bulkhead queue is full', cause?: unknown) {\n super('BulkheadFullError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C as ClientPlugin } from '../plugins-DqBQVM4I.cjs';
|
|
2
|
+
|
|
3
|
+
type BulkheadPluginExtension = {
|
|
4
|
+
activeCount: number;
|
|
5
|
+
queueDepth: number;
|
|
6
|
+
};
|
|
7
|
+
type BulkheadPluginOptions = {
|
|
8
|
+
maxConcurrent: number;
|
|
9
|
+
maxQueue?: number;
|
|
10
|
+
onReject?: (req: Request) => void | Promise<void>;
|
|
11
|
+
order?: number;
|
|
12
|
+
};
|
|
13
|
+
declare function bulkheadPlugin(options: BulkheadPluginOptions): ClientPlugin<BulkheadPluginExtension>;
|
|
14
|
+
|
|
15
|
+
export { type BulkheadPluginExtension, type BulkheadPluginOptions, bulkheadPlugin };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { C as ClientPlugin } from '../plugins-DqBQVM4I.js';
|
|
2
|
+
|
|
3
|
+
type BulkheadPluginExtension = {
|
|
4
|
+
activeCount: number;
|
|
5
|
+
queueDepth: number;
|
|
6
|
+
};
|
|
7
|
+
type BulkheadPluginOptions = {
|
|
8
|
+
maxConcurrent: number;
|
|
9
|
+
maxQueue?: number;
|
|
10
|
+
onReject?: (req: Request) => void | Promise<void>;
|
|
11
|
+
order?: number;
|
|
12
|
+
};
|
|
13
|
+
declare function bulkheadPlugin(options: BulkheadPluginOptions): ClientPlugin<BulkheadPluginExtension>;
|
|
14
|
+
|
|
15
|
+
export { type BulkheadPluginExtension, type BulkheadPluginOptions, bulkheadPlugin };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbortError,
|
|
3
|
+
BulkheadFullError
|
|
4
|
+
} from "../chunk-ZIJ2DZYZ.js";
|
|
5
|
+
|
|
6
|
+
// src/plugins/bulkhead.ts
|
|
7
|
+
function bulkheadPlugin(options) {
|
|
8
|
+
const { maxConcurrent, maxQueue, onReject, order = 5 } = options;
|
|
9
|
+
let activeCount = 0;
|
|
10
|
+
const queue = [];
|
|
11
|
+
function makeAbortError(signal) {
|
|
12
|
+
return new AbortError("Request was aborted", signal.reason);
|
|
13
|
+
}
|
|
14
|
+
function drainQueue() {
|
|
15
|
+
while (activeCount < maxConcurrent && queue.length > 0) {
|
|
16
|
+
const next = queue.shift();
|
|
17
|
+
next.cleanup();
|
|
18
|
+
if (next.request.signal.aborted) {
|
|
19
|
+
next.reject(makeAbortError(next.request.signal));
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
activeCount++;
|
|
23
|
+
next.resolve();
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function release() {
|
|
27
|
+
activeCount = Math.max(0, activeCount - 1);
|
|
28
|
+
drainQueue();
|
|
29
|
+
}
|
|
30
|
+
async function acquire(request) {
|
|
31
|
+
if (activeCount < maxConcurrent) {
|
|
32
|
+
activeCount++;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (typeof maxQueue === "number" && maxQueue >= 0 && queue.length >= maxQueue) {
|
|
36
|
+
await onReject?.(request);
|
|
37
|
+
throw new BulkheadFullError("Bulkhead queue is full");
|
|
38
|
+
}
|
|
39
|
+
await new Promise((resolve, reject) => {
|
|
40
|
+
const entry = {
|
|
41
|
+
request,
|
|
42
|
+
resolve,
|
|
43
|
+
reject,
|
|
44
|
+
cleanup: () => {
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const onAbort = () => {
|
|
48
|
+
const idx = queue.indexOf(entry);
|
|
49
|
+
if (idx >= 0) {
|
|
50
|
+
queue.splice(idx, 1);
|
|
51
|
+
}
|
|
52
|
+
entry.cleanup();
|
|
53
|
+
reject(makeAbortError(request.signal));
|
|
54
|
+
};
|
|
55
|
+
entry.cleanup = () => {
|
|
56
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
57
|
+
};
|
|
58
|
+
if (request.signal.aborted) {
|
|
59
|
+
reject(makeAbortError(request.signal));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
63
|
+
queue.push(entry);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
name: "bulkhead",
|
|
68
|
+
order,
|
|
69
|
+
setup: ({ defineExtension }) => {
|
|
70
|
+
defineExtension("activeCount", {
|
|
71
|
+
get: () => activeCount,
|
|
72
|
+
enumerable: true
|
|
73
|
+
});
|
|
74
|
+
defineExtension("queueDepth", {
|
|
75
|
+
get: () => queue.length,
|
|
76
|
+
enumerable: true
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
wrapDispatch: (next) => async (ctx) => {
|
|
80
|
+
let acquired = false;
|
|
81
|
+
try {
|
|
82
|
+
await acquire(ctx.request);
|
|
83
|
+
acquired = true;
|
|
84
|
+
return await next(ctx);
|
|
85
|
+
} finally {
|
|
86
|
+
if (acquired) {
|
|
87
|
+
release();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export {
|
|
94
|
+
bulkheadPlugin
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=bulkhead.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/bulkhead.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\nimport { AbortError, BulkheadFullError } from '../error.js'\n\nexport type BulkheadPluginExtension = {\n activeCount: number\n queueDepth: number\n}\n\nexport type BulkheadPluginOptions = {\n maxConcurrent: number\n maxQueue?: number\n onReject?: (req: Request) => void | Promise<void>\n order?: number\n}\n\ntype QueueEntry = {\n request: Request\n resolve: () => void\n reject: (err: unknown) => void\n cleanup: () => void\n}\n\nexport function bulkheadPlugin(\n options: BulkheadPluginOptions\n): ClientPlugin<BulkheadPluginExtension> {\n const { maxConcurrent, maxQueue, onReject, order = 5 } = options\n\n let activeCount = 0\n const queue: QueueEntry[] = []\n\n function makeAbortError(signal: AbortSignal): AbortError {\n return new AbortError('Request was aborted', signal.reason)\n }\n\n function drainQueue(): void {\n while (activeCount < maxConcurrent && queue.length > 0) {\n const next = queue.shift()!\n next.cleanup()\n\n if (next.request.signal.aborted) {\n next.reject(makeAbortError(next.request.signal))\n continue\n }\n\n activeCount++\n next.resolve()\n }\n }\n\n function release(): void {\n activeCount = Math.max(0, activeCount - 1)\n drainQueue()\n }\n\n async function acquire(request: Request): Promise<void> {\n if (activeCount < maxConcurrent) {\n activeCount++\n return\n }\n\n if (\n typeof maxQueue === 'number' &&\n maxQueue >= 0 &&\n queue.length >= maxQueue\n ) {\n await onReject?.(request)\n throw new BulkheadFullError('Bulkhead queue is full')\n }\n\n await new Promise<void>((resolve, reject) => {\n const entry: QueueEntry = {\n request,\n resolve,\n reject,\n cleanup: () => {},\n }\n\n const onAbort = () => {\n const idx = queue.indexOf(entry)\n if (idx >= 0) {\n queue.splice(idx, 1)\n }\n entry.cleanup()\n reject(makeAbortError(request.signal))\n }\n\n entry.cleanup = () => {\n request.signal.removeEventListener('abort', onAbort)\n }\n\n if (request.signal.aborted) {\n reject(makeAbortError(request.signal))\n return\n }\n\n request.signal.addEventListener('abort', onAbort, { once: true })\n queue.push(entry)\n })\n }\n\n return {\n name: 'bulkhead',\n order,\n setup: ({ defineExtension }) => {\n defineExtension('activeCount', {\n get: () => activeCount,\n enumerable: true,\n })\n defineExtension('queueDepth', {\n get: () => queue.length,\n enumerable: true,\n })\n },\n wrapDispatch: (next) => async (ctx) => {\n let acquired = false\n try {\n await acquire(ctx.request)\n acquired = true\n return await next(ctx)\n } finally {\n if (acquired) {\n release()\n }\n }\n },\n }\n}\n"],"mappings":";;;;;;AAsBO,SAAS,eACd,SACuC;AACvC,QAAM,EAAE,eAAe,UAAU,UAAU,QAAQ,EAAE,IAAI;AAEzD,MAAI,cAAc;AAClB,QAAM,QAAsB,CAAC;AAE7B,WAAS,eAAe,QAAiC;AACvD,WAAO,IAAI,WAAW,uBAAuB,OAAO,MAAM;AAAA,EAC5D;AAEA,WAAS,aAAmB;AAC1B,WAAO,cAAc,iBAAiB,MAAM,SAAS,GAAG;AACtD,YAAM,OAAO,MAAM,MAAM;AACzB,WAAK,QAAQ;AAEb,UAAI,KAAK,QAAQ,OAAO,SAAS;AAC/B,aAAK,OAAO,eAAe,KAAK,QAAQ,MAAM,CAAC;AAC/C;AAAA,MACF;AAEA;AACA,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,kBAAc,KAAK,IAAI,GAAG,cAAc,CAAC;AACzC,eAAW;AAAA,EACb;AAEA,iBAAe,QAAQ,SAAiC;AACtD,QAAI,cAAc,eAAe;AAC/B;AACA;AAAA,IACF;AAEA,QACE,OAAO,aAAa,YACpB,YAAY,KACZ,MAAM,UAAU,UAChB;AACA,YAAM,WAAW,OAAO;AACxB,YAAM,IAAI,kBAAkB,wBAAwB;AAAA,IACtD;AAEA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,QAAoB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,MAAM;AAAA,QAAC;AAAA,MAClB;AAEA,YAAM,UAAU,MAAM;AACpB,cAAM,MAAM,MAAM,QAAQ,KAAK;AAC/B,YAAI,OAAO,GAAG;AACZ,gBAAM,OAAO,KAAK,CAAC;AAAA,QACrB;AACA,cAAM,QAAQ;AACd,eAAO,eAAe,QAAQ,MAAM,CAAC;AAAA,MACvC;AAEA,YAAM,UAAU,MAAM;AACpB,gBAAQ,OAAO,oBAAoB,SAAS,OAAO;AAAA,MACrD;AAEA,UAAI,QAAQ,OAAO,SAAS;AAC1B,eAAO,eAAe,QAAQ,MAAM,CAAC;AACrC;AAAA,MACF;AAEA,cAAQ,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAChE,YAAM,KAAK,KAAK;AAAA,IAClB,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO,CAAC,EAAE,gBAAgB,MAAM;AAC9B,sBAAgB,eAAe;AAAA,QAC7B,KAAK,MAAM;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AACD,sBAAgB,cAAc;AAAA,QAC5B,KAAK,MAAM,MAAM;AAAA,QACjB,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IACA,cAAc,CAAC,SAAS,OAAO,QAAQ;AACrC,UAAI,WAAW;AACf,UAAI;AACF,cAAM,QAAQ,IAAI,OAAO;AACzB,mBAAW;AACX,eAAO,MAAM,KAAK,GAAG;AAAA,MACvB,UAAE;AACA,YAAI,UAAU;AACZ,kBAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/plugins/circuit.cjs
CHANGED
|
@@ -59,20 +59,23 @@ function circuitPlugin(options) {
|
|
|
59
59
|
}
|
|
60
60
|
return false;
|
|
61
61
|
};
|
|
62
|
-
const onSuccess = async (req) => {
|
|
62
|
+
const onSuccess = async (req, response) => {
|
|
63
63
|
const wasOpen = isOpen;
|
|
64
64
|
failures = 0;
|
|
65
65
|
if (wasOpen) {
|
|
66
66
|
isOpen = false;
|
|
67
|
-
await onCircuitClose?.(req);
|
|
67
|
+
await onCircuitClose?.({ request: req, response });
|
|
68
68
|
}
|
|
69
69
|
};
|
|
70
|
-
const onFailure = async (req) => {
|
|
70
|
+
const onFailure = async (req, reason) => {
|
|
71
71
|
failures++;
|
|
72
72
|
if (failures >= threshold) {
|
|
73
73
|
nextAttempt = Date.now() + reset;
|
|
74
74
|
isOpen = true;
|
|
75
|
-
await onCircuitOpen?.(
|
|
75
|
+
await onCircuitOpen?.({
|
|
76
|
+
request: req,
|
|
77
|
+
reason: { type: "threshold-reached", ...reason }
|
|
78
|
+
});
|
|
76
79
|
return true;
|
|
77
80
|
}
|
|
78
81
|
return false;
|
|
@@ -88,18 +91,21 @@ function circuitPlugin(options) {
|
|
|
88
91
|
},
|
|
89
92
|
preRequest: async (ctx) => {
|
|
90
93
|
if (Date.now() < nextAttempt) {
|
|
91
|
-
await onCircuitOpen?.(
|
|
94
|
+
await onCircuitOpen?.({
|
|
95
|
+
request: ctx.request,
|
|
96
|
+
reason: { type: "already-open" }
|
|
97
|
+
});
|
|
92
98
|
throw new CircuitOpenError("Circuit is open");
|
|
93
99
|
}
|
|
94
100
|
},
|
|
95
101
|
onSuccess: async (ctx, response) => {
|
|
96
102
|
if (shouldCountFailure(response, void 0)) {
|
|
97
|
-
const opened = await onFailure(ctx.request);
|
|
103
|
+
const opened = await onFailure(ctx.request, { response });
|
|
98
104
|
if (opened) {
|
|
99
105
|
throw new CircuitOpenError("Circuit is open");
|
|
100
106
|
}
|
|
101
107
|
} else {
|
|
102
|
-
await onSuccess(ctx.request);
|
|
108
|
+
await onSuccess(ctx.request, response);
|
|
103
109
|
}
|
|
104
110
|
},
|
|
105
111
|
onError: async (ctx, error) => {
|
|
@@ -107,7 +113,7 @@ function circuitPlugin(options) {
|
|
|
107
113
|
return;
|
|
108
114
|
}
|
|
109
115
|
if (shouldCountFailure(void 0, error)) {
|
|
110
|
-
const opened = await onFailure(ctx.request);
|
|
116
|
+
const opened = await onFailure(ctx.request, { error });
|
|
111
117
|
if (opened) {
|
|
112
118
|
throw new CircuitOpenError("Circuit is open");
|
|
113
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/circuit.ts","../../src/error.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/circuit.ts","../../src/error.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\nimport { CircuitOpenError } from '../error.js'\n\nexport type CircuitPluginExtension = {\n circuitOpen: boolean\n}\n\nexport type CircuitOpenReason =\n | { type: 'already-open' }\n | {\n type: 'threshold-reached'\n response?: Response\n error?: unknown\n }\n\nexport type CircuitPluginOptions = {\n threshold: number\n reset: number\n onCircuitOpen?: (ctx: {\n request: Request\n reason: CircuitOpenReason\n }) => void | Promise<void>\n onCircuitClose?: (ctx: {\n request: Request\n response: Response\n }) => void | Promise<void>\n order?: number\n}\n\nexport function circuitPlugin(\n options: CircuitPluginOptions\n): ClientPlugin<CircuitPluginExtension> {\n const {\n threshold,\n reset,\n onCircuitOpen,\n onCircuitClose,\n order = 20,\n } = options\n\n let failures = 0\n let nextAttempt = 0\n let isOpen = false\n\n const shouldCountFailure = (\n response?: Response,\n error?: unknown\n ): boolean => {\n if (error) {\n return true\n }\n if (response && (response.status >= 500 || response.status === 429)) {\n return true\n }\n return false\n }\n\n const onSuccess = async (req: Request, response: Response) => {\n const wasOpen = isOpen\n failures = 0\n if (wasOpen) {\n isOpen = false\n await onCircuitClose?.({ request: req, response })\n }\n }\n\n const onFailure = async (\n req: Request,\n reason: Omit<\n Extract<CircuitOpenReason, { type: 'threshold-reached' }>,\n 'type'\n >\n ): Promise<boolean> => {\n failures++\n if (failures >= threshold) {\n nextAttempt = Date.now() + reset\n isOpen = true\n await onCircuitOpen?.({\n request: req,\n reason: { type: 'threshold-reached', ...reason },\n })\n return true\n }\n return false\n }\n\n return {\n name: 'circuit',\n order,\n setup: ({ defineExtension }) => {\n defineExtension('circuitOpen', {\n get: () => isOpen,\n enumerable: true,\n })\n },\n preRequest: async (ctx) => {\n if (Date.now() < nextAttempt) {\n await onCircuitOpen?.({\n request: ctx.request,\n reason: { type: 'already-open' },\n })\n throw new CircuitOpenError('Circuit is open')\n }\n },\n onSuccess: async (ctx, response) => {\n if (shouldCountFailure(response, undefined)) {\n const opened = await onFailure(ctx.request, { response })\n if (opened) {\n throw new CircuitOpenError('Circuit is open')\n }\n } else {\n await onSuccess(ctx.request, response)\n }\n },\n onError: async (ctx, error) => {\n if (error instanceof CircuitOpenError) {\n return\n }\n if (shouldCountFailure(undefined, error)) {\n const opened = await onFailure(ctx.request, { error })\n if (opened) {\n throw new CircuitOpenError('Circuit is open')\n }\n }\n },\n }\n}\n","// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class BulkheadFullError extends BaseError {\n constructor(message = 'Bulkhead queue is full', cause?: unknown) {\n super('BulkheadFullError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,IAAM,YAAN,cAAwB,MAAM;AAAA,EAE5B,YAAY,MAAc,SAAiB,OAAiB;AAC1D,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,QAAI,UAAU,OAAW,MAAK,QAAQ;AAAA,EACxC;AACF;AAQO,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC9C,YAAY,UAAU,mBAAmB,OAAiB;AACxD,UAAM,oBAAoB,SAAS,KAAK;AAAA,EAC1C;AACF;;;ADSO,SAAS,cACd,SACsC;AACtC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACV,IAAI;AAEJ,MAAI,WAAW;AACf,MAAI,cAAc;AAClB,MAAI,SAAS;AAEb,QAAM,qBAAqB,CACzB,UACA,UACY;AACZ,QAAI,OAAO;AACT,aAAO;AAAA,IACT;AACA,QAAI,aAAa,SAAS,UAAU,OAAO,SAAS,WAAW,MAAM;AACnE,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,OAAO,KAAc,aAAuB;AAC5D,UAAM,UAAU;AAChB,eAAW;AACX,QAAI,SAAS;AACX,eAAS;AACT,YAAM,iBAAiB,EAAE,SAAS,KAAK,SAAS,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,YAAY,OAChB,KACA,WAIqB;AACrB;AACA,QAAI,YAAY,WAAW;AACzB,oBAAc,KAAK,IAAI,IAAI;AAC3B,eAAS;AACT,YAAM,gBAAgB;AAAA,QACpB,SAAS;AAAA,QACT,QAAQ,EAAE,MAAM,qBAAqB,GAAG,OAAO;AAAA,MACjD,CAAC;AACD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO,CAAC,EAAE,gBAAgB,MAAM;AAC9B,sBAAgB,eAAe;AAAA,QAC7B,KAAK,MAAM;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IACA,YAAY,OAAO,QAAQ;AACzB,UAAI,KAAK,IAAI,IAAI,aAAa;AAC5B,cAAM,gBAAgB;AAAA,UACpB,SAAS,IAAI;AAAA,UACb,QAAQ,EAAE,MAAM,eAAe;AAAA,QACjC,CAAC;AACD,cAAM,IAAI,iBAAiB,iBAAiB;AAAA,MAC9C;AAAA,IACF;AAAA,IACA,WAAW,OAAO,KAAK,aAAa;AAClC,UAAI,mBAAmB,UAAU,MAAS,GAAG;AAC3C,cAAM,SAAS,MAAM,UAAU,IAAI,SAAS,EAAE,SAAS,CAAC;AACxD,YAAI,QAAQ;AACV,gBAAM,IAAI,iBAAiB,iBAAiB;AAAA,QAC9C;AAAA,MACF,OAAO;AACL,cAAM,UAAU,IAAI,SAAS,QAAQ;AAAA,MACvC;AAAA,IACF;AAAA,IACA,SAAS,OAAO,KAAK,UAAU;AAC7B,UAAI,iBAAiB,kBAAkB;AACrC;AAAA,MACF;AACA,UAAI,mBAAmB,QAAW,KAAK,GAAG;AACxC,cAAM,SAAS,MAAM,UAAU,IAAI,SAAS,EAAE,MAAM,CAAC;AACrD,YAAI,QAAQ;AACV,gBAAM,IAAI,iBAAiB,iBAAiB;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -3,13 +3,26 @@ import { C as ClientPlugin } from '../plugins-DqBQVM4I.cjs';
|
|
|
3
3
|
type CircuitPluginExtension = {
|
|
4
4
|
circuitOpen: boolean;
|
|
5
5
|
};
|
|
6
|
+
type CircuitOpenReason = {
|
|
7
|
+
type: 'already-open';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'threshold-reached';
|
|
10
|
+
response?: Response;
|
|
11
|
+
error?: unknown;
|
|
12
|
+
};
|
|
6
13
|
type CircuitPluginOptions = {
|
|
7
14
|
threshold: number;
|
|
8
15
|
reset: number;
|
|
9
|
-
onCircuitOpen?: (
|
|
10
|
-
|
|
16
|
+
onCircuitOpen?: (ctx: {
|
|
17
|
+
request: Request;
|
|
18
|
+
reason: CircuitOpenReason;
|
|
19
|
+
}) => void | Promise<void>;
|
|
20
|
+
onCircuitClose?: (ctx: {
|
|
21
|
+
request: Request;
|
|
22
|
+
response: Response;
|
|
23
|
+
}) => void | Promise<void>;
|
|
11
24
|
order?: number;
|
|
12
25
|
};
|
|
13
26
|
declare function circuitPlugin(options: CircuitPluginOptions): ClientPlugin<CircuitPluginExtension>;
|
|
14
27
|
|
|
15
|
-
export { type CircuitPluginExtension, type CircuitPluginOptions, circuitPlugin };
|
|
28
|
+
export { type CircuitOpenReason, type CircuitPluginExtension, type CircuitPluginOptions, circuitPlugin };
|
|
@@ -3,13 +3,26 @@ import { C as ClientPlugin } from '../plugins-DqBQVM4I.js';
|
|
|
3
3
|
type CircuitPluginExtension = {
|
|
4
4
|
circuitOpen: boolean;
|
|
5
5
|
};
|
|
6
|
+
type CircuitOpenReason = {
|
|
7
|
+
type: 'already-open';
|
|
8
|
+
} | {
|
|
9
|
+
type: 'threshold-reached';
|
|
10
|
+
response?: Response;
|
|
11
|
+
error?: unknown;
|
|
12
|
+
};
|
|
6
13
|
type CircuitPluginOptions = {
|
|
7
14
|
threshold: number;
|
|
8
15
|
reset: number;
|
|
9
|
-
onCircuitOpen?: (
|
|
10
|
-
|
|
16
|
+
onCircuitOpen?: (ctx: {
|
|
17
|
+
request: Request;
|
|
18
|
+
reason: CircuitOpenReason;
|
|
19
|
+
}) => void | Promise<void>;
|
|
20
|
+
onCircuitClose?: (ctx: {
|
|
21
|
+
request: Request;
|
|
22
|
+
response: Response;
|
|
23
|
+
}) => void | Promise<void>;
|
|
11
24
|
order?: number;
|
|
12
25
|
};
|
|
13
26
|
declare function circuitPlugin(options: CircuitPluginOptions): ClientPlugin<CircuitPluginExtension>;
|
|
14
27
|
|
|
15
|
-
export { type CircuitPluginExtension, type CircuitPluginOptions, circuitPlugin };
|
|
28
|
+
export { type CircuitOpenReason, type CircuitPluginExtension, type CircuitPluginOptions, circuitPlugin };
|
package/dist/plugins/circuit.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CircuitOpenError
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-ZIJ2DZYZ.js";
|
|
4
4
|
|
|
5
5
|
// src/plugins/circuit.ts
|
|
6
6
|
function circuitPlugin(options) {
|
|
@@ -23,20 +23,23 @@ function circuitPlugin(options) {
|
|
|
23
23
|
}
|
|
24
24
|
return false;
|
|
25
25
|
};
|
|
26
|
-
const onSuccess = async (req) => {
|
|
26
|
+
const onSuccess = async (req, response) => {
|
|
27
27
|
const wasOpen = isOpen;
|
|
28
28
|
failures = 0;
|
|
29
29
|
if (wasOpen) {
|
|
30
30
|
isOpen = false;
|
|
31
|
-
await onCircuitClose?.(req);
|
|
31
|
+
await onCircuitClose?.({ request: req, response });
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
-
const onFailure = async (req) => {
|
|
34
|
+
const onFailure = async (req, reason) => {
|
|
35
35
|
failures++;
|
|
36
36
|
if (failures >= threshold) {
|
|
37
37
|
nextAttempt = Date.now() + reset;
|
|
38
38
|
isOpen = true;
|
|
39
|
-
await onCircuitOpen?.(
|
|
39
|
+
await onCircuitOpen?.({
|
|
40
|
+
request: req,
|
|
41
|
+
reason: { type: "threshold-reached", ...reason }
|
|
42
|
+
});
|
|
40
43
|
return true;
|
|
41
44
|
}
|
|
42
45
|
return false;
|
|
@@ -52,18 +55,21 @@ function circuitPlugin(options) {
|
|
|
52
55
|
},
|
|
53
56
|
preRequest: async (ctx) => {
|
|
54
57
|
if (Date.now() < nextAttempt) {
|
|
55
|
-
await onCircuitOpen?.(
|
|
58
|
+
await onCircuitOpen?.({
|
|
59
|
+
request: ctx.request,
|
|
60
|
+
reason: { type: "already-open" }
|
|
61
|
+
});
|
|
56
62
|
throw new CircuitOpenError("Circuit is open");
|
|
57
63
|
}
|
|
58
64
|
},
|
|
59
65
|
onSuccess: async (ctx, response) => {
|
|
60
66
|
if (shouldCountFailure(response, void 0)) {
|
|
61
|
-
const opened = await onFailure(ctx.request);
|
|
67
|
+
const opened = await onFailure(ctx.request, { response });
|
|
62
68
|
if (opened) {
|
|
63
69
|
throw new CircuitOpenError("Circuit is open");
|
|
64
70
|
}
|
|
65
71
|
} else {
|
|
66
|
-
await onSuccess(ctx.request);
|
|
72
|
+
await onSuccess(ctx.request, response);
|
|
67
73
|
}
|
|
68
74
|
},
|
|
69
75
|
onError: async (ctx, error) => {
|
|
@@ -71,7 +77,7 @@ function circuitPlugin(options) {
|
|
|
71
77
|
return;
|
|
72
78
|
}
|
|
73
79
|
if (shouldCountFailure(void 0, error)) {
|
|
74
|
-
const opened = await onFailure(ctx.request);
|
|
80
|
+
const opened = await onFailure(ctx.request, { error });
|
|
75
81
|
if (opened) {
|
|
76
82
|
throw new CircuitOpenError("Circuit is open");
|
|
77
83
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/circuit.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/circuit.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\nimport { CircuitOpenError } from '../error.js'\n\nexport type CircuitPluginExtension = {\n circuitOpen: boolean\n}\n\nexport type CircuitOpenReason =\n | { type: 'already-open' }\n | {\n type: 'threshold-reached'\n response?: Response\n error?: unknown\n }\n\nexport type CircuitPluginOptions = {\n threshold: number\n reset: number\n onCircuitOpen?: (ctx: {\n request: Request\n reason: CircuitOpenReason\n }) => void | Promise<void>\n onCircuitClose?: (ctx: {\n request: Request\n response: Response\n }) => void | Promise<void>\n order?: number\n}\n\nexport function circuitPlugin(\n options: CircuitPluginOptions\n): ClientPlugin<CircuitPluginExtension> {\n const {\n threshold,\n reset,\n onCircuitOpen,\n onCircuitClose,\n order = 20,\n } = options\n\n let failures = 0\n let nextAttempt = 0\n let isOpen = false\n\n const shouldCountFailure = (\n response?: Response,\n error?: unknown\n ): boolean => {\n if (error) {\n return true\n }\n if (response && (response.status >= 500 || response.status === 429)) {\n return true\n }\n return false\n }\n\n const onSuccess = async (req: Request, response: Response) => {\n const wasOpen = isOpen\n failures = 0\n if (wasOpen) {\n isOpen = false\n await onCircuitClose?.({ request: req, response })\n }\n }\n\n const onFailure = async (\n req: Request,\n reason: Omit<\n Extract<CircuitOpenReason, { type: 'threshold-reached' }>,\n 'type'\n >\n ): Promise<boolean> => {\n failures++\n if (failures >= threshold) {\n nextAttempt = Date.now() + reset\n isOpen = true\n await onCircuitOpen?.({\n request: req,\n reason: { type: 'threshold-reached', ...reason },\n })\n return true\n }\n return false\n }\n\n return {\n name: 'circuit',\n order,\n setup: ({ defineExtension }) => {\n defineExtension('circuitOpen', {\n get: () => isOpen,\n enumerable: true,\n })\n },\n preRequest: async (ctx) => {\n if (Date.now() < nextAttempt) {\n await onCircuitOpen?.({\n request: ctx.request,\n reason: { type: 'already-open' },\n })\n throw new CircuitOpenError('Circuit is open')\n }\n },\n onSuccess: async (ctx, response) => {\n if (shouldCountFailure(response, undefined)) {\n const opened = await onFailure(ctx.request, { response })\n if (opened) {\n throw new CircuitOpenError('Circuit is open')\n }\n } else {\n await onSuccess(ctx.request, response)\n }\n },\n onError: async (ctx, error) => {\n if (error instanceof CircuitOpenError) {\n return\n }\n if (shouldCountFailure(undefined, error)) {\n const opened = await onFailure(ctx.request, { error })\n if (opened) {\n throw new CircuitOpenError('Circuit is open')\n }\n }\n },\n }\n}\n"],"mappings":";;;;;AA6BO,SAAS,cACd,SACsC;AACtC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACV,IAAI;AAEJ,MAAI,WAAW;AACf,MAAI,cAAc;AAClB,MAAI,SAAS;AAEb,QAAM,qBAAqB,CACzB,UACA,UACY;AACZ,QAAI,OAAO;AACT,aAAO;AAAA,IACT;AACA,QAAI,aAAa,SAAS,UAAU,OAAO,SAAS,WAAW,MAAM;AACnE,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,QAAM,YAAY,OAAO,KAAc,aAAuB;AAC5D,UAAM,UAAU;AAChB,eAAW;AACX,QAAI,SAAS;AACX,eAAS;AACT,YAAM,iBAAiB,EAAE,SAAS,KAAK,SAAS,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,YAAY,OAChB,KACA,WAIqB;AACrB;AACA,QAAI,YAAY,WAAW;AACzB,oBAAc,KAAK,IAAI,IAAI;AAC3B,eAAS;AACT,YAAM,gBAAgB;AAAA,QACpB,SAAS;AAAA,QACT,QAAQ,EAAE,MAAM,qBAAqB,GAAG,OAAO;AAAA,MACjD,CAAC;AACD,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO,CAAC,EAAE,gBAAgB,MAAM;AAC9B,sBAAgB,eAAe;AAAA,QAC7B,KAAK,MAAM;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IACA,YAAY,OAAO,QAAQ;AACzB,UAAI,KAAK,IAAI,IAAI,aAAa;AAC5B,cAAM,gBAAgB;AAAA,UACpB,SAAS,IAAI;AAAA,UACb,QAAQ,EAAE,MAAM,eAAe;AAAA,QACjC,CAAC;AACD,cAAM,IAAI,iBAAiB,iBAAiB;AAAA,MAC9C;AAAA,IACF;AAAA,IACA,WAAW,OAAO,KAAK,aAAa;AAClC,UAAI,mBAAmB,UAAU,MAAS,GAAG;AAC3C,cAAM,SAAS,MAAM,UAAU,IAAI,SAAS,EAAE,SAAS,CAAC;AACxD,YAAI,QAAQ;AACV,gBAAM,IAAI,iBAAiB,iBAAiB;AAAA,QAC9C;AAAA,MACF,OAAO;AACL,cAAM,UAAU,IAAI,SAAS,QAAQ;AAAA,MACvC;AAAA,IACF;AAAA,IACA,SAAS,OAAO,KAAK,UAAU;AAC7B,UAAI,iBAAiB,kBAAkB;AACrC;AAAA,MACF;AACA,UAAI,mBAAmB,QAAW,KAAK,GAAG;AACxC,cAAM,SAAS,MAAM,UAAU,IAAI,SAAS,EAAE,MAAM,CAAC;AACrD,YAAI,QAAQ;AACV,gBAAM,IAAI,iBAAiB,iBAAiB;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|