@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,127 @@
|
|
|
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/hedge.ts
|
|
21
|
+
var hedge_exports = {};
|
|
22
|
+
__export(hedge_exports, {
|
|
23
|
+
hedgePlugin: () => hedgePlugin
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(hedge_exports);
|
|
26
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "PUT", "DELETE"]);
|
|
27
|
+
function hedgePlugin(options) {
|
|
28
|
+
const {
|
|
29
|
+
delay,
|
|
30
|
+
maxHedges = 1,
|
|
31
|
+
shouldHedge = (req) => SAFE_METHODS.has(req.method),
|
|
32
|
+
onHedge,
|
|
33
|
+
order = 15
|
|
34
|
+
} = options;
|
|
35
|
+
return {
|
|
36
|
+
name: "hedge",
|
|
37
|
+
order,
|
|
38
|
+
wrapDispatch: (next) => async (ctx) => {
|
|
39
|
+
if (!shouldHedge(ctx.request)) {
|
|
40
|
+
return next(ctx);
|
|
41
|
+
}
|
|
42
|
+
const delayMs = typeof delay === "function" ? delay(ctx.request) : delay;
|
|
43
|
+
const controllers = [];
|
|
44
|
+
const attempts = [];
|
|
45
|
+
function launch(attemptIndex) {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
controllers.push(controller);
|
|
48
|
+
const signal = AbortSignal.any([ctx.request.signal, controller.signal]);
|
|
49
|
+
const req = new Request(ctx.request, { signal });
|
|
50
|
+
attempts.push(next({ ...ctx, request: req }));
|
|
51
|
+
if (attemptIndex > 0) {
|
|
52
|
+
onHedge?.(ctx.request, attemptIndex);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function abortLosers(winnerIndex) {
|
|
56
|
+
controllers.forEach((c, i) => {
|
|
57
|
+
if (i !== winnerIndex) c.abort();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
launch(0);
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
let settled = false;
|
|
63
|
+
let launched = 1;
|
|
64
|
+
const timers = [];
|
|
65
|
+
function settle(winnerIndex, value) {
|
|
66
|
+
settled = true;
|
|
67
|
+
timers.forEach(clearTimeout);
|
|
68
|
+
abortLosers(winnerIndex);
|
|
69
|
+
resolve(value);
|
|
70
|
+
}
|
|
71
|
+
function tryReject(err) {
|
|
72
|
+
Promise.allSettled(attempts).then((results) => {
|
|
73
|
+
if (settled) return;
|
|
74
|
+
settled = true;
|
|
75
|
+
timers.forEach(clearTimeout);
|
|
76
|
+
let lastError = err;
|
|
77
|
+
for (const r of results) {
|
|
78
|
+
if (r.status === "rejected") lastError = r.reason;
|
|
79
|
+
}
|
|
80
|
+
reject(lastError);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function onAttemptSettled(index, result) {
|
|
84
|
+
if (settled) return;
|
|
85
|
+
if (result.status === "fulfilled") {
|
|
86
|
+
const res = result.value;
|
|
87
|
+
const isLastAttempt = launched >= maxHedges + 1 && index === attempts.length - 1;
|
|
88
|
+
if (res.ok || res.status < 500 && res.status !== 429 || isLastAttempt) {
|
|
89
|
+
settle(index, res);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
Promise.allSettled(attempts).then((results) => {
|
|
93
|
+
if (settled) return;
|
|
94
|
+
const pending = results.some(
|
|
95
|
+
(r) => r.status === "pending"
|
|
96
|
+
);
|
|
97
|
+
if (!pending) {
|
|
98
|
+
tryReject(result.reason);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function watchAttempt(index) {
|
|
104
|
+
attempts[index].then(
|
|
105
|
+
(res) => onAttemptSettled(index, { status: "fulfilled", value: res }),
|
|
106
|
+
(err) => onAttemptSettled(index, { status: "rejected", reason: err })
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
watchAttempt(0);
|
|
110
|
+
for (let h = 1; h <= maxHedges; h++) {
|
|
111
|
+
const hedgeIndex = h;
|
|
112
|
+
const t = setTimeout(() => {
|
|
113
|
+
launch(hedgeIndex);
|
|
114
|
+
watchAttempt(hedgeIndex);
|
|
115
|
+
launched++;
|
|
116
|
+
}, delayMs * hedgeIndex);
|
|
117
|
+
timers.push(t);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
124
|
+
0 && (module.exports = {
|
|
125
|
+
hedgePlugin
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=hedge.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/hedge.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\n\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'])\n\nexport type HedgePluginOptions = {\n delay: number | ((req: Request) => number)\n maxHedges?: number\n shouldHedge?: (req: Request) => boolean\n onHedge?: (req: Request, attempt: number) => void | Promise<void>\n order?: number\n}\n\nexport function hedgePlugin(options: HedgePluginOptions): ClientPlugin {\n const {\n delay,\n maxHedges = 1,\n shouldHedge = (req) => SAFE_METHODS.has(req.method),\n onHedge,\n order = 15,\n } = options\n\n return {\n name: 'hedge',\n order,\n wrapDispatch: (next) => async (ctx) => {\n if (!shouldHedge(ctx.request)) {\n return next(ctx)\n }\n\n const delayMs = typeof delay === 'function' ? delay(ctx.request) : delay\n\n const controllers: AbortController[] = []\n const attempts: Promise<Response>[] = []\n\n function launch(attemptIndex: number): void {\n const controller = new AbortController()\n controllers.push(controller)\n\n const signal = AbortSignal.any([ctx.request.signal, controller.signal])\n\n const req = new Request(ctx.request, { signal })\n attempts.push(next({ ...ctx, request: req }))\n\n if (attemptIndex > 0) {\n onHedge?.(ctx.request, attemptIndex)\n }\n }\n\n function abortLosers(winnerIndex: number): void {\n controllers.forEach((c, i) => {\n if (i !== winnerIndex) c.abort()\n })\n }\n\n // Launch initial attempt\n launch(0)\n\n return new Promise<Response>((resolve, reject) => {\n let settled = false\n let launched = 1\n const timers: ReturnType<typeof setTimeout>[] = []\n\n function settle(winnerIndex: number, value: Response): void {\n settled = true\n timers.forEach(clearTimeout)\n abortLosers(winnerIndex)\n resolve(value)\n }\n\n function tryReject(err: unknown): void {\n Promise.allSettled(attempts).then((results) => {\n if (settled) return\n settled = true\n timers.forEach(clearTimeout)\n let lastError = err\n for (const r of results) {\n if (r.status === 'rejected') lastError = r.reason\n }\n reject(lastError)\n })\n }\n\n function onAttemptSettled(\n index: number,\n result: PromiseSettledResult<Response>\n ): void {\n if (settled) return\n\n if (result.status === 'fulfilled') {\n const res = result.value\n // Treat 5xx and 429 as non-winners unless it's the last attempt\n const isLastAttempt =\n launched >= maxHedges + 1 && index === attempts.length - 1\n if (\n res.ok ||\n (res.status < 500 && res.status !== 429) ||\n isLastAttempt\n ) {\n settle(index, res)\n }\n // Otherwise wait for other attempts; if all settle without a winner\n // the last settler will resolve with whatever is left\n } else {\n // Check if all currently launched attempts have settled\n Promise.allSettled(attempts).then((results) => {\n if (settled) return\n const pending = results.some(\n (r) => r.status === ('pending' as never)\n )\n if (!pending) {\n tryReject(result.reason)\n }\n })\n }\n }\n\n function watchAttempt(index: number): void {\n attempts[index].then(\n (res) =>\n onAttemptSettled(index, { status: 'fulfilled', value: res }),\n (err) =>\n onAttemptSettled(index, { status: 'rejected', reason: err })\n )\n }\n\n watchAttempt(0)\n\n for (let h = 1; h <= maxHedges; h++) {\n const hedgeIndex = h\n const t = setTimeout(() => {\n launch(hedgeIndex)\n watchAttempt(hedgeIndex)\n launched++\n }, delayMs * hedgeIndex)\n timers.push(t)\n }\n })\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { C as ClientPlugin } from '../plugins-DqBQVM4I.cjs';
|
|
2
|
+
|
|
3
|
+
type HedgePluginOptions = {
|
|
4
|
+
delay: number | ((req: Request) => number);
|
|
5
|
+
maxHedges?: number;
|
|
6
|
+
shouldHedge?: (req: Request) => boolean;
|
|
7
|
+
onHedge?: (req: Request, attempt: number) => void | Promise<void>;
|
|
8
|
+
order?: number;
|
|
9
|
+
};
|
|
10
|
+
declare function hedgePlugin(options: HedgePluginOptions): ClientPlugin;
|
|
11
|
+
|
|
12
|
+
export { type HedgePluginOptions, hedgePlugin };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { C as ClientPlugin } from '../plugins-DqBQVM4I.js';
|
|
2
|
+
|
|
3
|
+
type HedgePluginOptions = {
|
|
4
|
+
delay: number | ((req: Request) => number);
|
|
5
|
+
maxHedges?: number;
|
|
6
|
+
shouldHedge?: (req: Request) => boolean;
|
|
7
|
+
onHedge?: (req: Request, attempt: number) => void | Promise<void>;
|
|
8
|
+
order?: number;
|
|
9
|
+
};
|
|
10
|
+
declare function hedgePlugin(options: HedgePluginOptions): ClientPlugin;
|
|
11
|
+
|
|
12
|
+
export { type HedgePluginOptions, hedgePlugin };
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// src/plugins/hedge.ts
|
|
2
|
+
var SAFE_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS", "PUT", "DELETE"]);
|
|
3
|
+
function hedgePlugin(options) {
|
|
4
|
+
const {
|
|
5
|
+
delay,
|
|
6
|
+
maxHedges = 1,
|
|
7
|
+
shouldHedge = (req) => SAFE_METHODS.has(req.method),
|
|
8
|
+
onHedge,
|
|
9
|
+
order = 15
|
|
10
|
+
} = options;
|
|
11
|
+
return {
|
|
12
|
+
name: "hedge",
|
|
13
|
+
order,
|
|
14
|
+
wrapDispatch: (next) => async (ctx) => {
|
|
15
|
+
if (!shouldHedge(ctx.request)) {
|
|
16
|
+
return next(ctx);
|
|
17
|
+
}
|
|
18
|
+
const delayMs = typeof delay === "function" ? delay(ctx.request) : delay;
|
|
19
|
+
const controllers = [];
|
|
20
|
+
const attempts = [];
|
|
21
|
+
function launch(attemptIndex) {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
controllers.push(controller);
|
|
24
|
+
const signal = AbortSignal.any([ctx.request.signal, controller.signal]);
|
|
25
|
+
const req = new Request(ctx.request, { signal });
|
|
26
|
+
attempts.push(next({ ...ctx, request: req }));
|
|
27
|
+
if (attemptIndex > 0) {
|
|
28
|
+
onHedge?.(ctx.request, attemptIndex);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function abortLosers(winnerIndex) {
|
|
32
|
+
controllers.forEach((c, i) => {
|
|
33
|
+
if (i !== winnerIndex) c.abort();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
launch(0);
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
let settled = false;
|
|
39
|
+
let launched = 1;
|
|
40
|
+
const timers = [];
|
|
41
|
+
function settle(winnerIndex, value) {
|
|
42
|
+
settled = true;
|
|
43
|
+
timers.forEach(clearTimeout);
|
|
44
|
+
abortLosers(winnerIndex);
|
|
45
|
+
resolve(value);
|
|
46
|
+
}
|
|
47
|
+
function tryReject(err) {
|
|
48
|
+
Promise.allSettled(attempts).then((results) => {
|
|
49
|
+
if (settled) return;
|
|
50
|
+
settled = true;
|
|
51
|
+
timers.forEach(clearTimeout);
|
|
52
|
+
let lastError = err;
|
|
53
|
+
for (const r of results) {
|
|
54
|
+
if (r.status === "rejected") lastError = r.reason;
|
|
55
|
+
}
|
|
56
|
+
reject(lastError);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function onAttemptSettled(index, result) {
|
|
60
|
+
if (settled) return;
|
|
61
|
+
if (result.status === "fulfilled") {
|
|
62
|
+
const res = result.value;
|
|
63
|
+
const isLastAttempt = launched >= maxHedges + 1 && index === attempts.length - 1;
|
|
64
|
+
if (res.ok || res.status < 500 && res.status !== 429 || isLastAttempt) {
|
|
65
|
+
settle(index, res);
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
Promise.allSettled(attempts).then((results) => {
|
|
69
|
+
if (settled) return;
|
|
70
|
+
const pending = results.some(
|
|
71
|
+
(r) => r.status === "pending"
|
|
72
|
+
);
|
|
73
|
+
if (!pending) {
|
|
74
|
+
tryReject(result.reason);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function watchAttempt(index) {
|
|
80
|
+
attempts[index].then(
|
|
81
|
+
(res) => onAttemptSettled(index, { status: "fulfilled", value: res }),
|
|
82
|
+
(err) => onAttemptSettled(index, { status: "rejected", reason: err })
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
watchAttempt(0);
|
|
86
|
+
for (let h = 1; h <= maxHedges; h++) {
|
|
87
|
+
const hedgeIndex = h;
|
|
88
|
+
const t = setTimeout(() => {
|
|
89
|
+
launch(hedgeIndex);
|
|
90
|
+
watchAttempt(hedgeIndex);
|
|
91
|
+
launched++;
|
|
92
|
+
}, delayMs * hedgeIndex);
|
|
93
|
+
timers.push(t);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
hedgePlugin
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=hedge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/hedge.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\n\nconst SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'])\n\nexport type HedgePluginOptions = {\n delay: number | ((req: Request) => number)\n maxHedges?: number\n shouldHedge?: (req: Request) => boolean\n onHedge?: (req: Request, attempt: number) => void | Promise<void>\n order?: number\n}\n\nexport function hedgePlugin(options: HedgePluginOptions): ClientPlugin {\n const {\n delay,\n maxHedges = 1,\n shouldHedge = (req) => SAFE_METHODS.has(req.method),\n onHedge,\n order = 15,\n } = options\n\n return {\n name: 'hedge',\n order,\n wrapDispatch: (next) => async (ctx) => {\n if (!shouldHedge(ctx.request)) {\n return next(ctx)\n }\n\n const delayMs = typeof delay === 'function' ? delay(ctx.request) : delay\n\n const controllers: AbortController[] = []\n const attempts: Promise<Response>[] = []\n\n function launch(attemptIndex: number): void {\n const controller = new AbortController()\n controllers.push(controller)\n\n const signal = AbortSignal.any([ctx.request.signal, controller.signal])\n\n const req = new Request(ctx.request, { signal })\n attempts.push(next({ ...ctx, request: req }))\n\n if (attemptIndex > 0) {\n onHedge?.(ctx.request, attemptIndex)\n }\n }\n\n function abortLosers(winnerIndex: number): void {\n controllers.forEach((c, i) => {\n if (i !== winnerIndex) c.abort()\n })\n }\n\n // Launch initial attempt\n launch(0)\n\n return new Promise<Response>((resolve, reject) => {\n let settled = false\n let launched = 1\n const timers: ReturnType<typeof setTimeout>[] = []\n\n function settle(winnerIndex: number, value: Response): void {\n settled = true\n timers.forEach(clearTimeout)\n abortLosers(winnerIndex)\n resolve(value)\n }\n\n function tryReject(err: unknown): void {\n Promise.allSettled(attempts).then((results) => {\n if (settled) return\n settled = true\n timers.forEach(clearTimeout)\n let lastError = err\n for (const r of results) {\n if (r.status === 'rejected') lastError = r.reason\n }\n reject(lastError)\n })\n }\n\n function onAttemptSettled(\n index: number,\n result: PromiseSettledResult<Response>\n ): void {\n if (settled) return\n\n if (result.status === 'fulfilled') {\n const res = result.value\n // Treat 5xx and 429 as non-winners unless it's the last attempt\n const isLastAttempt =\n launched >= maxHedges + 1 && index === attempts.length - 1\n if (\n res.ok ||\n (res.status < 500 && res.status !== 429) ||\n isLastAttempt\n ) {\n settle(index, res)\n }\n // Otherwise wait for other attempts; if all settle without a winner\n // the last settler will resolve with whatever is left\n } else {\n // Check if all currently launched attempts have settled\n Promise.allSettled(attempts).then((results) => {\n if (settled) return\n const pending = results.some(\n (r) => r.status === ('pending' as never)\n )\n if (!pending) {\n tryReject(result.reason)\n }\n })\n }\n }\n\n function watchAttempt(index: number): void {\n attempts[index].then(\n (res) =>\n onAttemptSettled(index, { status: 'fulfilled', value: res }),\n (err) =>\n onAttemptSettled(index, { status: 'rejected', reason: err })\n )\n }\n\n watchAttempt(0)\n\n for (let h = 1; h <= maxHedges; h++) {\n const hedgeIndex = h\n const t = setTimeout(() => {\n launch(hedgeIndex)\n watchAttempt(hedgeIndex)\n launched++\n }, delayMs * hedgeIndex)\n timers.push(t)\n }\n })\n },\n }\n}\n"],"mappings":";AAEA,IAAM,eAAe,oBAAI,IAAI,CAAC,OAAO,QAAQ,WAAW,OAAO,QAAQ,CAAC;AAUjE,SAAS,YAAY,SAA2C;AACrE,QAAM;AAAA,IACJ;AAAA,IACA,YAAY;AAAA,IACZ,cAAc,CAAC,QAAQ,aAAa,IAAI,IAAI,MAAM;AAAA,IAClD;AAAA,IACA,QAAQ;AAAA,EACV,IAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,cAAc,CAAC,SAAS,OAAO,QAAQ;AACrC,UAAI,CAAC,YAAY,IAAI,OAAO,GAAG;AAC7B,eAAO,KAAK,GAAG;AAAA,MACjB;AAEA,YAAM,UAAU,OAAO,UAAU,aAAa,MAAM,IAAI,OAAO,IAAI;AAEnE,YAAM,cAAiC,CAAC;AACxC,YAAM,WAAgC,CAAC;AAEvC,eAAS,OAAO,cAA4B;AAC1C,cAAM,aAAa,IAAI,gBAAgB;AACvC,oBAAY,KAAK,UAAU;AAE3B,cAAM,SAAS,YAAY,IAAI,CAAC,IAAI,QAAQ,QAAQ,WAAW,MAAM,CAAC;AAEtE,cAAM,MAAM,IAAI,QAAQ,IAAI,SAAS,EAAE,OAAO,CAAC;AAC/C,iBAAS,KAAK,KAAK,EAAE,GAAG,KAAK,SAAS,IAAI,CAAC,CAAC;AAE5C,YAAI,eAAe,GAAG;AACpB,oBAAU,IAAI,SAAS,YAAY;AAAA,QACrC;AAAA,MACF;AAEA,eAAS,YAAY,aAA2B;AAC9C,oBAAY,QAAQ,CAAC,GAAG,MAAM;AAC5B,cAAI,MAAM,YAAa,GAAE,MAAM;AAAA,QACjC,CAAC;AAAA,MACH;AAGA,aAAO,CAAC;AAER,aAAO,IAAI,QAAkB,CAAC,SAAS,WAAW;AAChD,YAAI,UAAU;AACd,YAAI,WAAW;AACf,cAAM,SAA0C,CAAC;AAEjD,iBAAS,OAAO,aAAqB,OAAuB;AAC1D,oBAAU;AACV,iBAAO,QAAQ,YAAY;AAC3B,sBAAY,WAAW;AACvB,kBAAQ,KAAK;AAAA,QACf;AAEA,iBAAS,UAAU,KAAoB;AACrC,kBAAQ,WAAW,QAAQ,EAAE,KAAK,CAAC,YAAY;AAC7C,gBAAI,QAAS;AACb,sBAAU;AACV,mBAAO,QAAQ,YAAY;AAC3B,gBAAI,YAAY;AAChB,uBAAW,KAAK,SAAS;AACvB,kBAAI,EAAE,WAAW,WAAY,aAAY,EAAE;AAAA,YAC7C;AACA,mBAAO,SAAS;AAAA,UAClB,CAAC;AAAA,QACH;AAEA,iBAAS,iBACP,OACA,QACM;AACN,cAAI,QAAS;AAEb,cAAI,OAAO,WAAW,aAAa;AACjC,kBAAM,MAAM,OAAO;AAEnB,kBAAM,gBACJ,YAAY,YAAY,KAAK,UAAU,SAAS,SAAS;AAC3D,gBACE,IAAI,MACH,IAAI,SAAS,OAAO,IAAI,WAAW,OACpC,eACA;AACA,qBAAO,OAAO,GAAG;AAAA,YACnB;AAAA,UAGF,OAAO;AAEL,oBAAQ,WAAW,QAAQ,EAAE,KAAK,CAAC,YAAY;AAC7C,kBAAI,QAAS;AACb,oBAAM,UAAU,QAAQ;AAAA,gBACtB,CAAC,MAAM,EAAE,WAAY;AAAA,cACvB;AACA,kBAAI,CAAC,SAAS;AACZ,0BAAU,OAAO,MAAM;AAAA,cACzB;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAEA,iBAAS,aAAa,OAAqB;AACzC,mBAAS,KAAK,EAAE;AAAA,YACd,CAAC,QACC,iBAAiB,OAAO,EAAE,QAAQ,aAAa,OAAO,IAAI,CAAC;AAAA,YAC7D,CAAC,QACC,iBAAiB,OAAO,EAAE,QAAQ,YAAY,QAAQ,IAAI,CAAC;AAAA,UAC/D;AAAA,QACF;AAEA,qBAAa,CAAC;AAEd,iBAAS,IAAI,GAAG,KAAK,WAAW,KAAK;AACnC,gBAAM,aAAa;AACnB,gBAAM,IAAI,WAAW,MAAM;AACzB,mBAAO,UAAU;AACjB,yBAAa,UAAU;AACvB;AAAA,UACF,GAAG,UAAU,UAAU;AACvB,iBAAO,KAAK,CAAC;AAAA,QACf;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fetchkit/ffetch",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Fetch wrapper with configurable timeouts, retries, and TypeScript-first DX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"homepage": "https://github.com/fetch-kit/ffetch#readme",
|
|
18
18
|
"bugs": "https://github.com/fetch-kit/ffetch/issues",
|
|
19
|
-
"author": "
|
|
19
|
+
"author": "Gabor Koos <gabor@gaborkoos.com>",
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"main": "./dist/index.cjs",
|
|
22
22
|
"module": "./dist/index.js",
|
|
@@ -32,6 +32,16 @@
|
|
|
32
32
|
"import": "./dist/plugins/dedupe.js",
|
|
33
33
|
"require": "./dist/plugins/dedupe.cjs"
|
|
34
34
|
},
|
|
35
|
+
"./plugins/bulkhead": {
|
|
36
|
+
"types": "./dist/plugins/bulkhead.d.ts",
|
|
37
|
+
"import": "./dist/plugins/bulkhead.js",
|
|
38
|
+
"require": "./dist/plugins/bulkhead.cjs"
|
|
39
|
+
},
|
|
40
|
+
"./plugins/hedge": {
|
|
41
|
+
"types": "./dist/plugins/hedge.d.ts",
|
|
42
|
+
"import": "./dist/plugins/hedge.js",
|
|
43
|
+
"require": "./dist/plugins/hedge.cjs"
|
|
44
|
+
},
|
|
35
45
|
"./plugins/circuit": {
|
|
36
46
|
"types": "./dist/plugins/circuit.d.ts",
|
|
37
47
|
"import": "./dist/plugins/circuit.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts"],"sourcesContent":["// 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 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":";AACA,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;AAEO,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC1C,YAAY,UAAU,qBAAqB,OAAiB;AAC1D,UAAM,gBAAgB,SAAS,KAAK;AAAA,EACtC;AACF;AAEO,IAAM,mBAAN,cAA+B,UAAU;AAAA,EAC9C,YAAY,UAAU,mBAAmB,OAAiB;AACxD,UAAM,oBAAoB,SAAS,KAAK;AAAA,EAC1C;AACF;AAEO,IAAM,aAAN,cAAyB,UAAU;AAAA,EACxC,YAAY,UAAU,uBAAuB,OAAiB;AAC5D,UAAM,cAAc,SAAS,KAAK;AAAA,EACpC;AACF;AAEO,IAAM,kBAAN,cAA8B,UAAU;AAAA,EAC7C,YAAY,UAAU,uBAAuB,OAAiB;AAC5D,UAAM,mBAAmB,SAAS,KAAK;AAAA,EACzC;AACF;AAEO,IAAM,eAAN,cAA2B,UAAU;AAAA,EAC1C,YAAY,UAAU,0BAA0B,OAAiB;AAC/D,UAAM,gBAAgB,SAAS,KAAK;AAAA,EACtC;AACF;AAEO,IAAM,YAAN,cAAwB,UAAU;AAAA,EACvC,YAAY,UAAU,uBAAuB,OAAiB;AAC5D,UAAM,aAAa,SAAS,KAAK;AAAA,EACnC;AACF;","names":[]}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var r=class extends Error{constructor(e,n,t){super(n),this.name=e,t!==void 0&&(this.cause=t)}},o=class extends r{constructor(e="Request timed out",n){super("TimeoutError",e,n)}},u=class extends r{constructor(e="Circuit is open",n){super("CircuitOpenError",e,n)}},c=class extends r{constructor(e="Request was aborted",n){super("AbortError",e,n)}},p=class extends r{constructor(e="Retry limit reached",n){super("RetryLimitError",e,n)}},d=class extends r{constructor(e="Network error occurred",n){super("NetworkError",e,n)}},i=class extends r{constructor(e="HTTP error occurred",n){super("HttpError",e,n)}};export{o as a,u as b,c,p as d,d as e,i as f};
|
|
2
|
-
//# sourceMappingURL=chunk-UP35S5ZH.min.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts"],"sourcesContent":["// 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 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":"AACA,IAAMA,EAAN,cAAwB,KAAM,CAE5B,YAAYC,EAAcC,EAAiBC,EAAiB,CAC1D,MAAMD,CAAO,EACb,KAAK,KAAOD,EACRE,IAAU,SAAW,KAAK,MAAQA,EACxC,CACF,EAEaC,EAAN,cAA2BJ,CAAU,CAC1C,YAAYE,EAAU,oBAAqBC,EAAiB,CAC1D,MAAM,eAAgBD,EAASC,CAAK,CACtC,CACF,EAEaE,EAAN,cAA+BL,CAAU,CAC9C,YAAYE,EAAU,kBAAmBC,EAAiB,CACxD,MAAM,mBAAoBD,EAASC,CAAK,CAC1C,CACF,EAEaG,EAAN,cAAyBN,CAAU,CACxC,YAAYE,EAAU,sBAAuBC,EAAiB,CAC5D,MAAM,aAAcD,EAASC,CAAK,CACpC,CACF,EAEaI,EAAN,cAA8BP,CAAU,CAC7C,YAAYE,EAAU,sBAAuBC,EAAiB,CAC5D,MAAM,kBAAmBD,EAASC,CAAK,CACzC,CACF,EAEaK,EAAN,cAA2BR,CAAU,CAC1C,YAAYE,EAAU,yBAA0BC,EAAiB,CAC/D,MAAM,eAAgBD,EAASC,CAAK,CACtC,CACF,EAEaM,EAAN,cAAwBT,CAAU,CACvC,YAAYE,EAAU,sBAAuBC,EAAiB,CAC5D,MAAM,YAAaD,EAASC,CAAK,CACnC,CACF","names":["BaseError","name","message","cause","TimeoutError","CircuitOpenError","AbortError","RetryLimitError","NetworkError","HttpError"]}
|
|
File without changes
|
|
File without changes
|