@fetchkit/ffetch 5.5.0 → 5.5.3
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/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/plugins/dedupe.cjs +17 -3
- package/dist/plugins/dedupe.cjs.map +1 -1
- package/dist/plugins/dedupe.js +17 -3
- package/dist/plugins/dedupe.js.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -270,7 +270,7 @@ function createClient(opts = {}) {
|
|
|
270
270
|
}
|
|
271
271
|
let timeoutSignal = void 0;
|
|
272
272
|
let combinedSignal = void 0;
|
|
273
|
-
let controller
|
|
273
|
+
let controller;
|
|
274
274
|
if (effectiveTimeout > 0) {
|
|
275
275
|
timeoutSignal = createTimeoutSignal(effectiveTimeout);
|
|
276
276
|
pluginContext.metadata.signals.timeout = timeoutSignal;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/error.ts","../src/index.ts","../src/retry.ts","../src/should-retry.ts","../src/client.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 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","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\nexport type {\n ClientPlugin,\n PluginRequestContext,\n PluginDispatch,\n PluginSetupContext,\n} from './plugins'\n\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n BulkheadFullError,\n AbortError,\n RetryLimitError,\n NetworkError,\n HttpError,\n} from './error'\n","import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type PluginRequestPromiseExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n type PluginRequestPromiseExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[] = readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = (input: RequestInfo | URL, init: FFetchRequestInit = {}) => {\n const execute = async () => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async (\n dispatchCtx: PluginRequestContext,\n dispatchSignal: AbortSignal | undefined\n ) => {\n const requestForAttempt = dispatchCtx.request\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n dispatchCtx.metadata.retry.attempt = attempt\n dispatchCtx.metadata.retry.lastError = ctx.error\n dispatchCtx.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n dispatchCtx.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n requestForAttempt,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n }\n if (typeof dispatchSignal?.throwIfAborted === 'function') {\n dispatchSignal.throwIfAborted()\n } else if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt, {\n signal: dispatchSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n dispatchCtx.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n dispatchCtx.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n requestForAttempt,\n dispatchSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, requestForAttempt)\n }\n await effectiveHooks.after?.(requestForAttempt, res)\n await effectiveHooks.onComplete?.(requestForAttempt, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n dispatchCtx.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 &&\n resp.status < 500 &&\n resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(requestForAttempt, retryErr)\n await effectiveHooks.onComplete?.(\n requestForAttempt,\n undefined,\n retryErr\n )\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async (ctx) => {\n const dispatchSignal =\n ctx === pluginContext ? combinedSignal : ctx.request.signal\n return retryWithHooks(ctx, dispatchSignal)\n }\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n let promise = execute() as Promise<Response>\n for (const plugin of plugins) {\n if (plugin.decoratePromise) {\n promise = plugin.decoratePromise(promise)\n }\n }\n return promise as Promise<Response> &\n PluginRequestPromiseExtensions<TPlugins>\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n >\n}\n"],"mappings":"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","names":["shouldRetry","controller","HttpError"]}
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/index.ts","../src/retry.ts","../src/should-retry.ts","../src/client.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 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","export type { FFetch, FFetchOptions } from './types'\nexport type { Hooks } from './hooks'\nexport type {\n ClientPlugin,\n PluginRequestContext,\n PluginDispatch,\n PluginSetupContext,\n} from './plugins'\n\nexport { createClient } from './client'\n\nexport {\n TimeoutError,\n CircuitOpenError,\n BulkheadFullError,\n AbortError,\n RetryLimitError,\n NetworkError,\n HttpError,\n} from './error'\n","import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type PluginRequestPromiseExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n type PluginRequestPromiseExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[] = readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = (input: RequestInfo | URL, init: FFetchRequestInit = {}) => {\n const execute = async () => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async (\n dispatchCtx: PluginRequestContext,\n dispatchSignal: AbortSignal | undefined\n ) => {\n const requestForAttempt = dispatchCtx.request\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n dispatchCtx.metadata.retry.attempt = attempt\n dispatchCtx.metadata.retry.lastError = ctx.error\n dispatchCtx.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n dispatchCtx.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n requestForAttempt,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n }\n if (typeof dispatchSignal?.throwIfAborted === 'function') {\n dispatchSignal.throwIfAborted()\n } else if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt, {\n signal: dispatchSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n dispatchCtx.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n dispatchCtx.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n requestForAttempt,\n dispatchSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, requestForAttempt)\n }\n await effectiveHooks.after?.(requestForAttempt, res)\n await effectiveHooks.onComplete?.(requestForAttempt, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n dispatchCtx.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 &&\n resp.status < 500 &&\n resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(requestForAttempt, retryErr)\n await effectiveHooks.onComplete?.(\n requestForAttempt,\n undefined,\n retryErr\n )\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async (ctx) => {\n const dispatchSignal =\n ctx === pluginContext ? combinedSignal : ctx.request.signal\n return retryWithHooks(ctx, dispatchSignal)\n }\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n let promise = execute() as Promise<Response>\n for (const plugin of plugins) {\n if (plugin.decoratePromise) {\n promise = plugin.decoratePromise(promise)\n }\n }\n return promise as Promise<Response> &\n PluginRequestPromiseExtensions<TPlugins>\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n >\n}\n"],"mappings":"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","names":["shouldRetry","controller","HttpError"]}
|
package/dist/index.js
CHANGED
|
@@ -182,7 +182,7 @@ function createClient(opts = {}) {
|
|
|
182
182
|
}
|
|
183
183
|
let timeoutSignal = void 0;
|
|
184
184
|
let combinedSignal = void 0;
|
|
185
|
-
let controller
|
|
185
|
+
let controller;
|
|
186
186
|
if (effectiveTimeout > 0) {
|
|
187
187
|
timeoutSignal = createTimeoutSignal(effectiveTimeout);
|
|
188
188
|
pluginContext.metadata.signals.timeout = timeoutSignal;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type PluginRequestPromiseExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n type PluginRequestPromiseExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[] = readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = (input: RequestInfo | URL, init: FFetchRequestInit = {}) => {\n const execute = async () => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async (\n dispatchCtx: PluginRequestContext,\n dispatchSignal: AbortSignal | undefined\n ) => {\n const requestForAttempt = dispatchCtx.request\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n dispatchCtx.metadata.retry.attempt = attempt\n dispatchCtx.metadata.retry.lastError = ctx.error\n dispatchCtx.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n dispatchCtx.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n requestForAttempt,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n }\n if (typeof dispatchSignal?.throwIfAborted === 'function') {\n dispatchSignal.throwIfAborted()\n } else if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt, {\n signal: dispatchSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n dispatchCtx.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n dispatchCtx.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n requestForAttempt,\n dispatchSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, requestForAttempt)\n }\n await effectiveHooks.after?.(requestForAttempt, res)\n await effectiveHooks.onComplete?.(requestForAttempt, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n dispatchCtx.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 &&\n resp.status < 500 &&\n resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(requestForAttempt, retryErr)\n await effectiveHooks.onComplete?.(\n requestForAttempt,\n undefined,\n retryErr\n )\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async (ctx) => {\n const dispatchSignal =\n ctx === pluginContext ? combinedSignal : ctx.request.signal\n return retryWithHooks(ctx, dispatchSignal)\n }\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n let promise = execute() as Promise<Response>\n for (const plugin of plugins) {\n if (plugin.decoratePromise) {\n promise = plugin.decoratePromise(promise)\n }\n }\n return promise as Promise<Response> &\n PluginRequestPromiseExtensions<TPlugins>\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n >\n}\n"],"mappings":";;;;;;;;;;;AAIO,IAAM,eAA2B,CAAC,QAAQ;AAC/C,QAAM,aAAa,IAAI,UAAU,QAAQ,IAAI,aAAa;AAC1D,MAAI,YAAY;AACd,UAAM,UAAU,SAAS,YAAY,EAAE;AACvC,QAAI,CAAC,MAAM,OAAO,EAAG,QAAO,UAAU;AACtC,UAAM,OAAO,KAAK,MAAM,UAAU;AAClC,QAAI,CAAC,MAAM,IAAI,EAAG,QAAO,KAAK,IAAI,GAAG,OAAO,KAAK,IAAI,CAAC;AAAA,EACxD;AACA,SAAO,KAAK,IAAI,UAAU,MAAM,KAAK,OAAO,IAAI;AAClD;AAEA,SAAS,kBAAkB,IAAY,QAAqC;AAC1E,MAAI,MAAM,EAAG,QAAO,QAAQ,QAAQ;AACpC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,QAAI,CAAC,QAAQ;AACX,iBAAW,SAAS,EAAE;AACtB;AAAA,IACF;AAEA,QAAI,OAAO,SAAS;AAClB,cAAQ;AACR;AAAA,IACF;AAEA,UAAM,UAAU,MAAM;AACpB,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AAEA,UAAM,QAAQ,WAAW,MAAM;AAC7B,aAAO,oBAAoB,SAAS,OAAO;AAC3C,cAAQ;AAAA,IACV,GAAG,EAAE;AAEL,WAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AAAA,EAC1D,CAAC;AACH;AAEA,eAAsB,MACpB,IACA,SACA,OACAA,eAA8C,MAAM,MACpD,SACA,QACmB;AACnB,MAAI;AACJ,MAAI;AAEJ,WAAS,IAAI,GAAG,KAAK,SAAS,KAAK;AACjC,UAAM,MAAoB;AAAA,MACxB,SAAS,IAAI;AAAA,MACb;AAAA,MACA,UAAU;AAAA,MACV,OAAO;AAAA,IACT;AACA,QAAI;AACF,gBAAU,MAAM,GAAG;AACnB,UAAI,WAAW;AACf,UAAI,QAAQ;AACZ,UAAI,IAAI,WAAWA,aAAY,GAAG,GAAG;AACnC,cAAM,OAAO,OAAO,UAAU,aAAa,MAAM,GAAG,IAAI;AACxD,cAAM,kBAAkB,MAAM,MAAM;AACpC;AAAA,MACF;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,gBAAU;AACV,UAAI,QAAQ;AACZ,UAAI,MAAM,WAAW,CAACA,aAAY,GAAG,EAAG,OAAM;AAC9C,YAAM,OAAO,OAAO,UAAU,aAAa,MAAM,GAAG,IAAI;AACxD,YAAM,kBAAkB,MAAM,MAAM;AAAA,IACtC;AAAA,EACF;AACA,QAAM;AACR;;;AC5EO,SAAS,YAAY,KAA4B;AACtD,QAAM,EAAE,OAAO,SAAS,IAAI;AAC5B,MACE,iBAAiB,cACjB,iBAAiB,oBACjB,iBAAiB;AAEjB,WAAO;AACT,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,UAAU,OAAO,SAAS,WAAW;AACvD;;;ACWO,SAAS,aASd,OAAgC,CAAC,GAIjC;AACA,QAAM;AAAA,IACJ,SAAS,uBAAuB;AAAA,IAChC,SAAS,uBAAuB;AAAA,IAChC,YAAY,0BAA0B;AAAA,IACtC,aAAa,2BAA2B;AAAA,IACxC,OAAO,qBAAqB,CAAC;AAAA,IAC7B;AAAA,IACA,SAAS,eAAe,CAAC;AAAA,EAC3B,IAAI;AAEJ,QAAM,uBAA8C,uBAAO,OAAO,IAAI;AAEtE,QAAM,UAAU,aACb,IAAI,CAAC,QAAQ,WAAW,EAAE,QAAQ,MAAM,EAAE,EAC1C,KAAK,CAAC,GAAG,MAAM;AACd,UAAM,SAAS,EAAE,OAAO,SAAS;AACjC,UAAM,SAAS,EAAE,OAAO,SAAS;AACjC,QAAI,WAAW,OAAQ,QAAO,SAAS;AACvC,WAAO,EAAE,QAAQ,EAAE;AAAA,EACrB,CAAC,EACA,IAAI,CAAC,UAAU,MAAM,MAAM;AAE9B,aAAW,UAAU,SAAS;AAC5B,WAAO,QAAQ;AAAA,MACb,iBAAiB,CAAC,KAAK,eAAe;AACpC,cAAM,cAAc;AACpB,YAAI,eAAe,sBAAsB;AACvC,gBAAM,IAAI;AAAA,YACR,4CAA4C,OAAO,WAAW,CAAC;AAAA,UACjE;AAAA,QACF;AACA,YAAI,SAAS,YAAY;AACvB,+BAAqB,WAAW,IAAI;AAAA,YAClC,KAAK,WAAW;AAAA,YAChB,YAAY,WAAW,cAAc;AAAA,YACrC,cAAc;AAAA,UAChB;AACA;AAAA,QACF;AACA,6BAAqB,WAAW,IAAI;AAAA,UAClC,OAAO,WAAW;AAAA,UAClB,UAAU;AAAA,UACV,YAAY,WAAW,cAAc;AAAA,UACrC,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,kBAAoC,CAAC;AAG3C,WAAS,WAAW;AAClB,eAAW,SAAS,iBAAiB;AACnC,YAAM,YAAY,MAAM;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,SAAS,CAAC,OAA0B,OAA0B,CAAC,MAAM;AACzE,UAAM,UAAU,YAAY;AAC1B,UAAI,UAAU,IAAI,QAAQ,OAAO,IAAI;AAGrC,YAAM,iBAAiB,EAAE,GAAG,oBAAoB,GAAI,KAAK,SAAS,CAAC,EAAG;AACtE,UAAI,eAAe,kBAAkB;AACnC,kBAAU,MAAM,eAAe,iBAAiB,OAAO;AAAA,MACzD;AACA,YAAM,eAAe,SAAS,OAAO;AAGrC,YAAM,mBAAmB,KAAK,WAAW;AACzC,YAAM,sBACJ,OAAO,KAAK,eAAe,cACvB,KAAK,aACL;AACN,YAAM,uBAAuB,KAAK,eAAe;AAGjD,YAAM,mBAAmB,KAAK,WAAW;AACzC,YAAM,aAAa,KAAK;AACxB,YAAM,oBAAoB,QAAQ;AAElC,YAAM,gBAAsC;AAAA,QAC1C;AAAA,QACA;AAAA,QACA,OAAO,uBAAO,OAAO,IAAI;AAAA,QACzB,UAAU;AAAA,UACR,WAAW,KAAK,IAAI;AAAA,UACpB,WAAW;AAAA,UACX,SAAS;AAAA,YACP,MACE,eAAe,UAAa,eAAe,OACvC,SACA;AAAA,YACN,aAAa;AAAA,UACf;AAAA,UACA,OAAO;AAAA,YACL,mBAAmB;AAAA,YACnB,iBAAiB;AAAA,YACjB,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAEA,iBAAW,UAAU,SAAS;AAC5B,cAAM,OAAO,aAAa,aAAa;AAAA,MACzC;AAGA,YAAM,4BACJ,OAAO,KAAK,qBAAqB,cAC7B,KAAK,mBACJ,KAAK,oBAAoB;AAGhC,eAAS,oBAAoB,SAA8B;AACzD,YAAI,OAAO,aAAa,YAAY,YAAY;AAC9C,iBAAO,YAAY,QAAQ,OAAO;AAAA,QACpC;AACA,cAAMC,cAAa,IAAI,gBAAgB;AACvC,cAAM,YAAY,WAAW,MAAMA,YAAW,MAAM,GAAG,OAAO;AAC9D,QAAAA,YAAW,OAAO;AAAA,UAChB;AAAA,UACA,MAAM,aAAa,SAAS;AAAA,UAC5B,EAAE,MAAM,KAAK;AAAA,QACf;AACA,eAAOA,YAAW;AAAA,MACpB;AAEA,UAAI,gBAAyC;AAC7C,UAAI,iBAA0C;AAC9C,UAAI,aAA0C;AAE9C,UAAI,mBAAmB,GAAG;AACxB,wBAAgB,oBAAoB,gBAAgB;AACpD,sBAAc,SAAS,QAAQ,UAAU;AAAA,MAC3C;AAEA,YAAM,UAAyB,CAAC;AAChC,UAAI,WAAY,SAAQ,KAAK,UAAU;AACvC,UAAI,qBAAqB,sBAAsB,YAAY;AACzD,gBAAQ,KAAK,iBAAiB;AAAA,MAChC;AACA,UAAI,cAAe,SAAQ,KAAK,aAAa;AAE7C,UAAI,QAAQ,WAAW,GAAG;AACxB,yBAAiB,QAAQ,CAAC;AAC1B,qBAAa,IAAI,gBAAgB;AAAA,MACnC,OAAO;AACL,YAAI,OAAO,YAAY,QAAQ,YAAY;AACzC,gBAAM,IAAI;AAAA,YACR;AAAA,UACF;AAAA,QACF;AACA,yBAAiB,YAAY,IAAI,OAAO;AACxC,qBAAa,IAAI,gBAAgB;AAAA,MACnC;AACA,oBAAc,SAAS,QAAQ,WAAW;AAE1C,YAAM,iBAAiB,OACrB,aACA,mBACG;AACH,cAAM,oBAAoB,YAAY;AACtC,YAAI,UAAU;AACd,cAAM,sBAAsB,CAAC,QAAwC;AACnE,oBAAU,IAAI;AACd,sBAAY,SAAS,MAAM,UAAU;AACrC,sBAAY,SAAS,MAAM,YAAY,IAAI;AAC3C,sBAAY,SAAS,MAAM,eAAe,IAAI;AAC9C,gBAAM,WAAW,qBAAqB,GAAG;AACzC,sBAAY,SAAS,MAAM,oBAAoB;AAC/C,cAAI,YAAY,WAAW,kBAAkB;AAC3C,2BAAe;AAAA,cACb;AAAA,cACA,UAAU;AAAA,cACV,IAAI;AAAA,cACJ,IAAI;AAAA,YACN;AAAA,UACF;AACA,iBAAO;AAAA,QACT;AAEA,YAAI,eAAqC;AACzC,YAAI;AACF,cAAI,MAAM,MAAM;AAAA,YACd,YAAY;AACV,kBAAI,YAAY,SAAS;AACvB,+BAAe,UAAU,iBAAiB;AAC1C,sBAAM,IAAI,WAAW,6BAA6B;AAAA,cACpD;AACA,kBAAI,eAAe,SAAS;AAC1B,+BAAe,YAAY,iBAAiB;AAC5C,sBAAM,IAAI,aAAa,kBAAkB;AAAA,cAC3C;AACA,kBAAI,OAAO,gBAAgB,mBAAmB,YAAY;AACxD,+BAAe,eAAe;AAAA,cAChC,WAAW,gBAAgB,SAAS;AAClC,oBAAI,YAAY,SAAS;AACvB,iCAAe,UAAU,iBAAiB;AAC1C,wBAAM,IAAI,WAAW,6BAA6B;AAAA,gBACpD,WAAW,eAAe,SAAS;AACjC,iCAAe,YAAY,iBAAiB;AAC5C,wBAAM,IAAI,aAAa,kBAAkB;AAAA,gBAC3C,OAAO;AACL,wBAAM,IAAI;AAAA,oBACR;AAAA,oBACA,IAAI,aAAa,WAAW,YAAY;AAAA,kBAC1C;AAAA,gBACF;AAAA,cACF;AACA,oBAAM,gBAAgB,IAAI,QAAQ,mBAAmB;AAAA,gBACnD,QAAQ;AAAA,cACV,CAAC;AACD,kBAAI;AACF,sBAAM,UAAU,KAAK,gBAAgB,gBAAgB;AACrD,sBAAM,WAAW,MAAM,QAAQ,aAAa;AAC5C,+BAAe;AACf,4BAAY,SAAS,MAAM,eAAe;AAC1C,uBAAO;AAAA,cACT,SAAS,KAAK;AACZ,4BAAY,SAAS,MAAM,YAAY;AACvC,oBAAI,eAAe,gBAAgB,IAAI,SAAS,cAAc;AAC5D,sBACE,eAAe,YACd,CAAC,cAAc,CAAC,WAAW,UAC5B;AACA,mCAAe,YAAY,iBAAiB;AAC5C,0BAAM,IAAI,aAAa,oBAAoB,GAAG;AAAA,kBAChD,WAAW,YAAY,SAAS;AAC9B,mCAAe,UAAU,iBAAiB;AAC1C,0BAAM,IAAI,WAAW,6BAA6B;AAAA,kBACpD,OAAO;AACL,0BAAM,IAAI;AAAA,sBACR;AAAA,sBACA,IAAI,aAAa,WAAW,YAAY;AAAA,oBAC1C;AAAA,kBACF;AAAA,gBACF,WACE,eAAe,aACf,6GAA6G;AAAA,kBAC3G,IAAI;AAAA,gBACN,GACA;AACA,wBAAM,IAAI,aAAa,IAAI,SAAS,GAAG;AAAA,gBACzC;AACA,sBAAM;AAAA,cACR;AAAA,YACF;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,cAAI,eAAe,mBAAmB;AACpC,kBAAM,MAAM,eAAe,kBAAkB,KAAK,iBAAiB;AAAA,UACrE;AACA,gBAAM,eAAe,QAAQ,mBAAmB,GAAG;AACnD,gBAAM,eAAe,aAAa,mBAAmB,KAAK,MAAS;AACnE,cACE,8BACE,IAAI,UAAU,OAAO,IAAI,SAAS,OAAO,IAAI,WAAW,OACxD,IAAI,UAAU,OACd,IAAI,WAAW,MACjB;AACA,kBAAM,EAAE,WAAAC,WAAU,IAAI,MAAM,OAAO,qBAAY;AAC/C,kBAAM,IAAIA;AAAA,cACR,eAAe,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,cAC3C;AAAA,YACF;AAAA,UACF;AACA,iBAAO;AAAA,QACT,SAAS,KAAc;AACrB,sBAAY,SAAS,MAAM,YAAY;AACvC,cAAI,cAAc;AAChB,kBAAM,OAAO;AACb,gBACE,8BACE,KAAK,UAAU,OACf,KAAK,SAAS,OACd,KAAK,WAAW,OAChB,KAAK,UAAU,OACf,KAAK,WAAW,MAClB;AACA,oBAAM,EAAE,WAAAA,WAAU,IAAI,MAAM,OAAO,qBAAY;AAC/C,oBAAM,IAAIA;AAAA,gBACR,eAAe,KAAK,MAAM,IAAI,KAAK,UAAU;AAAA,gBAC7C;AAAA,cACF;AAAA,YACF;AACA,mBAAO;AAAA,UACT;AACA,cAAI,eAAe,cAAc;AAC/B,kBAAM,eAAe,YAAY,iBAAiB;AAClD,kBAAM,eAAe,UAAU,mBAAmB,GAAG;AACrD,kBAAM,eAAe,aAAa,mBAAmB,QAAW,GAAG;AACnE,kBAAM;AAAA,UACR;AACA,cAAI,eAAe,YAAY;AAC7B,kBAAM,eAAe,UAAU,iBAAiB;AAChD,kBAAM,eAAe,UAAU,mBAAmB,GAAG;AACrD,kBAAM,eAAe,aAAa,mBAAmB,QAAW,GAAG;AACnE,kBAAM;AAAA,UACR;AACA,cAAI,eAAe,cAAc;AAC/B,kBAAM,eAAe,UAAU,mBAAmB,GAAG;AACrD,kBAAM,eAAe,aAAa,mBAAmB,QAAW,GAAG;AACnE,kBAAM;AAAA,UACR;AACA,gBAAM,WAAW,IAAI;AAAA,YACnB,OAAO,QAAQ,YACf,OACA,aAAa,OACb,OAAQ,IAA8B,YAAY,WAC7C,IAA4B,UAC7B;AAAA,YACJ;AAAA,UACF;AACA,gBAAM,eAAe,UAAU,mBAAmB,QAAQ;AAC1D,gBAAM,eAAe;AAAA,YACnB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAEA,YAAM,eAA+B,OAAO,QAAQ;AAClD,cAAM,iBACJ,QAAQ,gBAAgB,iBAAiB,IAAI,QAAQ;AACvD,eAAO,eAAe,KAAK,cAAc;AAAA,MAC3C;AAEA,UAAI,WAAW;AACf,eAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AAC5C,cAAM,SAAS,QAAQ,CAAC;AACxB,YAAI,OAAO,cAAc;AACvB,qBAAW,OAAO,aAAa,QAAQ;AAAA,QACzC;AAAA,MACF;AAEA,YAAM,gBAAgB,SAAS,aAAa,EACzC,KAAK,OAAO,aAAa;AACxB,mBAAW,UAAU,SAAS;AAC5B,gBAAM,OAAO,YAAY,eAAe,QAAQ;AAAA,QAClD;AACA,eAAO;AAAA,MACT,CAAC,EACA,MAAM,OAAO,QAAiB;AAC7B,mBAAW,UAAU,SAAS;AAC5B,gBAAM,OAAO,UAAU,eAAe,GAAG;AAAA,QAC3C;AACA,cAAM;AAAA,MACR,CAAC;AAEH,YAAM,eAA+B;AAAA,QACnC,SAAS;AAAA,QACT;AAAA,QACA;AAAA,MACF;AACA,sBAAgB,KAAK,YAAY;AAEjC,aAAO,cAAc,QAAQ,YAAY;AACvC,mBAAW,UAAU,SAAS;AAC5B,gBAAM,OAAO,YAAY,aAAa;AAAA,QACxC;AAEA,cAAM,QAAQ,gBAAgB,QAAQ,YAAY;AAClD,YAAI,QAAQ,IAAI;AACd,0BAAgB,OAAO,OAAO,CAAC;AAAA,QACjC;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,UAAU,QAAQ;AACtB,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,iBAAiB;AAC1B,kBAAU,OAAO,gBAAgB,OAAO;AAAA,MAC1C;AAAA,IACF;AACA,WAAO;AAAA,EAET;AAEA,SAAO,eAAe,QAAQ,mBAAmB;AAAA,IAC/C,MAAM;AACJ,aAAO;AAAA,IACT;AAAA,IACA,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAED,SAAO,eAAe,QAAQ,YAAY;AAAA,IACxC,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc;AAAA,EAChB,CAAC;AAED,SAAO,iBAAiB,QAAQ,oBAAoB;AAEpD,SAAO;AAIT;","names":["shouldRetry","controller","HttpError"]}
|
|
1
|
+
{"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type PluginRequestPromiseExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n type PluginRequestPromiseExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[] = readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = (input: RequestInfo | URL, init: FFetchRequestInit = {}) => {\n const execute = async () => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async (\n dispatchCtx: PluginRequestContext,\n dispatchSignal: AbortSignal | undefined\n ) => {\n const requestForAttempt = dispatchCtx.request\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n dispatchCtx.metadata.retry.attempt = attempt\n dispatchCtx.metadata.retry.lastError = ctx.error\n dispatchCtx.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n dispatchCtx.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n requestForAttempt,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n }\n if (typeof dispatchSignal?.throwIfAborted === 'function') {\n dispatchSignal.throwIfAborted()\n } else if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt, {\n signal: dispatchSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n dispatchCtx.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n dispatchCtx.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n requestForAttempt,\n dispatchSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, requestForAttempt)\n }\n await effectiveHooks.after?.(requestForAttempt, res)\n await effectiveHooks.onComplete?.(requestForAttempt, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n dispatchCtx.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 &&\n resp.status < 500 &&\n resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(requestForAttempt, retryErr)\n await effectiveHooks.onComplete?.(\n requestForAttempt,\n undefined,\n retryErr\n )\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async (ctx) => {\n const dispatchSignal =\n ctx === pluginContext ? combinedSignal : ctx.request.signal\n return retryWithHooks(ctx, dispatchSignal)\n }\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n let promise = execute() as Promise<Response>\n for (const plugin of plugins) {\n if (plugin.decoratePromise) {\n promise = plugin.decoratePromise(promise)\n }\n }\n return promise as Promise<Response> &\n PluginRequestPromiseExtensions<TPlugins>\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n >\n}\n"],"mappings":"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","names":["shouldRetry","controller","HttpError"]}
|
package/dist/index.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type PluginRequestPromiseExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n type PluginRequestPromiseExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[] = readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = (input: RequestInfo | URL, init: FFetchRequestInit = {}) => {\n const execute = async () => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async (\n dispatchCtx: PluginRequestContext,\n dispatchSignal: AbortSignal | undefined\n ) => {\n const requestForAttempt = dispatchCtx.request\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n dispatchCtx.metadata.retry.attempt = attempt\n dispatchCtx.metadata.retry.lastError = ctx.error\n dispatchCtx.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n dispatchCtx.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n requestForAttempt,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n }\n if (typeof dispatchSignal?.throwIfAborted === 'function') {\n dispatchSignal.throwIfAborted()\n } else if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt, {\n signal: dispatchSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n dispatchCtx.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n dispatchCtx.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n requestForAttempt,\n dispatchSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, requestForAttempt)\n }\n await effectiveHooks.after?.(requestForAttempt, res)\n await effectiveHooks.onComplete?.(requestForAttempt, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n dispatchCtx.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 &&\n resp.status < 500 &&\n resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(requestForAttempt, retryErr)\n await effectiveHooks.onComplete?.(\n requestForAttempt,\n undefined,\n retryErr\n )\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async (ctx) => {\n const dispatchSignal =\n ctx === pluginContext ? combinedSignal : ctx.request.signal\n return retryWithHooks(ctx, dispatchSignal)\n }\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n let promise = execute() as Promise<Response>\n for (const plugin of plugins) {\n if (plugin.decoratePromise) {\n promise = plugin.decoratePromise(promise)\n }\n }\n return promise as Promise<Response> &\n PluginRequestPromiseExtensions<TPlugins>\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n >\n}\n"],"mappings":"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","names":["defaultDelay","ctx","retryAfter","seconds","date","waitForRetryDelay","ms","signal","resolve","onAbort","timer","retry","fn","retries","delay","shouldRetry","request","lastErr","lastRes","i","wait","err","shouldRetry","ctx","error","response","AbortError","CircuitOpenError","TimeoutError","createClient","opts","clientDefaultTimeout","clientDefaultRetries","clientDefaultRetryDelay","defaultDelay","clientDefaultShouldRetry","shouldRetry","clientDefaultHooks","fetchHandler","inputPlugins","extensionDescriptors","plugins","plugin","index","a","b","aOrder","bOrder","entry","key","descriptor","propertyKey","pendingRequests","abortAll","client","input","init","promise","request","effectiveHooks","effectiveRetries","effectiveRetryDelay","effectiveShouldRetry","effectiveTimeout","userSignal","transformedSignal","pluginContext","effectiveThrowOnHttpError","createTimeoutSignal","timeout","controller","timeoutId","timeoutSignal","combinedSignal","signals","retryWithHooks","dispatchCtx","dispatchSignal","requestForAttempt","attempt","shouldRetryWithHook","ctx","retrying","lastResponse","res","retry","AbortError","TimeoutError","reqWithSignal","response","err","NetworkError","HttpError","resp","retryErr","RetryLimitError","dispatch","i","actualPromise","pendingEntry"]}
|
|
1
|
+
{"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type PluginRequestPromiseExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n type PluginRequestPromiseExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[] = readonly ClientPlugin<\n PluginExtensionBase,\n PluginRequestPromiseExtensionBase\n >[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = (input: RequestInfo | URL, init: FFetchRequestInit = {}) => {\n const execute = async () => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async (\n dispatchCtx: PluginRequestContext,\n dispatchSignal: AbortSignal | undefined\n ) => {\n const requestForAttempt = dispatchCtx.request\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n dispatchCtx.metadata.retry.attempt = attempt\n dispatchCtx.metadata.retry.lastError = ctx.error\n dispatchCtx.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n dispatchCtx.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n requestForAttempt,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n }\n if (typeof dispatchSignal?.throwIfAborted === 'function') {\n dispatchSignal.throwIfAborted()\n } else if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt, {\n signal: dispatchSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n dispatchCtx.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n dispatchCtx.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(requestForAttempt)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(requestForAttempt)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n requestForAttempt,\n dispatchSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, requestForAttempt)\n }\n await effectiveHooks.after?.(requestForAttempt, res)\n await effectiveHooks.onComplete?.(requestForAttempt, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n dispatchCtx.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 &&\n resp.status < 500 &&\n resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(requestForAttempt)\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(requestForAttempt, err)\n await effectiveHooks.onComplete?.(requestForAttempt, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(requestForAttempt, retryErr)\n await effectiveHooks.onComplete?.(\n requestForAttempt,\n undefined,\n retryErr\n )\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async (ctx) => {\n const dispatchSignal =\n ctx === pluginContext ? combinedSignal : ctx.request.signal\n return retryWithHooks(ctx, dispatchSignal)\n }\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n let promise = execute() as Promise<Response>\n for (const plugin of plugins) {\n if (plugin.decoratePromise) {\n promise = plugin.decoratePromise(promise)\n }\n }\n return promise as Promise<Response> &\n PluginRequestPromiseExtensions<TPlugins>\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<\n PluginExtensions<TPlugins>,\n PluginRequestPromiseExtensions<TPlugins>\n >\n}\n"],"mappings":"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","names":["defaultDelay","ctx","retryAfter","seconds","date","waitForRetryDelay","ms","signal","resolve","onAbort","timer","retry","fn","retries","delay","shouldRetry","request","lastErr","lastRes","i","wait","err","shouldRetry","ctx","error","response","AbortError","CircuitOpenError","TimeoutError","createClient","opts","clientDefaultTimeout","clientDefaultRetries","clientDefaultRetryDelay","defaultDelay","clientDefaultShouldRetry","shouldRetry","clientDefaultHooks","fetchHandler","inputPlugins","extensionDescriptors","plugins","plugin","index","a","b","aOrder","bOrder","entry","key","descriptor","propertyKey","pendingRequests","abortAll","client","input","init","promise","request","effectiveHooks","effectiveRetries","effectiveRetryDelay","effectiveShouldRetry","effectiveTimeout","userSignal","transformedSignal","pluginContext","effectiveThrowOnHttpError","createTimeoutSignal","timeout","controller","timeoutId","timeoutSignal","combinedSignal","signals","retryWithHooks","dispatchCtx","dispatchSignal","requestForAttempt","attempt","shouldRetryWithHook","ctx","retrying","lastResponse","res","retry","AbortError","TimeoutError","reqWithSignal","response","err","NetworkError","HttpError","resp","retryErr","RetryLimitError","dispatch","i","actualPromise","pendingEntry"]}
|
package/dist/plugins/dedupe.cjs
CHANGED
|
@@ -26,9 +26,23 @@ __export(dedupe_exports, {
|
|
|
26
26
|
module.exports = __toCommonJS(dedupe_exports);
|
|
27
27
|
|
|
28
28
|
// src/dedupeRequestHash.ts
|
|
29
|
+
function toBase64(bytes) {
|
|
30
|
+
const maybeBuffer = globalThis.Buffer;
|
|
31
|
+
if (maybeBuffer) {
|
|
32
|
+
return maybeBuffer.from(bytes).toString("base64");
|
|
33
|
+
}
|
|
34
|
+
let binary = "";
|
|
35
|
+
for (const byte of bytes) {
|
|
36
|
+
binary += String.fromCharCode(byte);
|
|
37
|
+
}
|
|
38
|
+
if (typeof btoa === "function") {
|
|
39
|
+
return btoa(binary);
|
|
40
|
+
}
|
|
41
|
+
throw new Error("Base64 encoding is not available in this runtime");
|
|
42
|
+
}
|
|
29
43
|
function dedupeRequestHash(params) {
|
|
30
44
|
const { method, url, body } = params;
|
|
31
|
-
let bodyString
|
|
45
|
+
let bodyString;
|
|
32
46
|
if (body instanceof FormData) {
|
|
33
47
|
return void 0;
|
|
34
48
|
}
|
|
@@ -40,9 +54,9 @@ function dedupeRequestHash(params) {
|
|
|
40
54
|
} else if (body instanceof URLSearchParams) {
|
|
41
55
|
bodyString = body.toString();
|
|
42
56
|
} else if (body instanceof ArrayBuffer) {
|
|
43
|
-
bodyString =
|
|
57
|
+
bodyString = toBase64(new Uint8Array(body));
|
|
44
58
|
} else if (body instanceof Uint8Array) {
|
|
45
|
-
bodyString =
|
|
59
|
+
bodyString = toBase64(body);
|
|
46
60
|
} else if (body instanceof Blob) {
|
|
47
61
|
bodyString = `[blob:${body.type}:${body.size}]`;
|
|
48
62
|
} else if (body == null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/dedupe.ts","../../src/dedupeRequestHash.ts"],"sourcesContent":["import type { ClientPlugin, PluginRequestContext } from '../plugins.js'\nimport {\n dedupeRequestHash,\n type DedupeHashParams,\n} from '../dedupeRequestHash.js'\n\ntype Waiter = {\n resolve: (value: Response) => void\n reject: (reason?: unknown) => void\n}\n\ntype DedupeEntry = {\n promise: Promise<Response>\n waiters: Waiter[]\n createdAt: number\n}\n\nexport type DedupePluginOptions = {\n hashFn?: (params: DedupeHashParams) => string | undefined\n ttl?: number\n sweepInterval?: number\n order?: number\n}\n\nfunction contextToHashParams(ctx: PluginRequestContext): DedupeHashParams {\n return {\n method: ctx.request.method,\n url: ctx.request.url,\n body: (ctx.init.body ?? null) as DedupeHashParams['body'],\n headers: ctx.request.headers,\n signal:\n ctx.init.signal === undefined || ctx.init.signal === null\n ? undefined\n : ctx.init.signal,\n requestInit: ctx.init,\n request: ctx.request,\n }\n}\n\nexport function dedupePlugin(options: DedupePluginOptions = {}): ClientPlugin {\n const {\n hashFn = dedupeRequestHash,\n ttl,\n sweepInterval = 5000,\n order = 10,\n } = options\n\n const inFlight = new Map<string, DedupeEntry>()\n let sweeper: ReturnType<typeof setInterval> | undefined\n\n function startSweeper() {\n if (sweeper || typeof ttl !== 'number' || ttl <= 0) return\n sweeper = setInterval(() => {\n const now = Date.now()\n for (const [key, entry] of inFlight.entries()) {\n if (now - entry.createdAt > ttl) {\n inFlight.delete(key)\n }\n }\n if (inFlight.size === 0 && sweeper) {\n clearInterval(sweeper)\n sweeper = undefined\n }\n }, sweepInterval)\n }\n\n function stopSweeperIfIdle() {\n if (inFlight.size === 0 && sweeper) {\n clearInterval(sweeper)\n sweeper = undefined\n }\n }\n\n return {\n name: 'dedupe',\n order,\n wrapDispatch: (next) => async (ctx) => {\n const key = hashFn(contextToHashParams(ctx))\n ctx.state.dedupeKey = key\n\n if (!key) {\n return next(ctx)\n }\n\n const existing = inFlight.get(key)\n if (existing) {\n return new Promise<Response>((resolve, reject) => {\n existing.waiters.push({ resolve, reject })\n })\n }\n\n const waiters: Waiter[] = []\n const actualPromise = next(ctx)\n\n inFlight.set(key, {\n promise: actualPromise,\n waiters,\n createdAt: Date.now(),\n })\n startSweeper()\n\n actualPromise.then(\n (result) => {\n for (const waiter of waiters) {\n waiter.resolve(result.clone())\n }\n },\n (error) => {\n for (const waiter of waiters) {\n waiter.reject(error)\n }\n }\n )\n\n return actualPromise.finally(() => {\n const current = inFlight.get(key)\n if (current?.promise === actualPromise) {\n inFlight.delete(key)\n stopSweeperIfIdle()\n }\n })\n },\n }\n}\n\nexport { dedupeRequestHash }\nexport type { DedupeHashParams }\n","export type DedupeHashParams = {\n method: string\n url: string\n body:\n | string\n | FormData\n | URLSearchParams\n | Blob\n | ArrayBuffer\n | BufferSource\n | null\n | ReadableStream<unknown>\n headers?: Headers | Record<string, string>\n signal?: AbortSignal\n requestInit?: RequestInit\n request?: Request\n}\n\nexport function dedupeRequestHash(\n params: DedupeHashParams\n): string | undefined {\n const { method, url, body } = params\n let bodyString
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/dedupe.ts","../../src/dedupeRequestHash.ts"],"sourcesContent":["import type { ClientPlugin, PluginRequestContext } from '../plugins.js'\nimport {\n dedupeRequestHash,\n type DedupeHashParams,\n} from '../dedupeRequestHash.js'\n\ntype Waiter = {\n resolve: (value: Response) => void\n reject: (reason?: unknown) => void\n}\n\ntype DedupeEntry = {\n promise: Promise<Response>\n waiters: Waiter[]\n createdAt: number\n}\n\nexport type DedupePluginOptions = {\n hashFn?: (params: DedupeHashParams) => string | undefined\n ttl?: number\n sweepInterval?: number\n order?: number\n}\n\nfunction contextToHashParams(ctx: PluginRequestContext): DedupeHashParams {\n return {\n method: ctx.request.method,\n url: ctx.request.url,\n body: (ctx.init.body ?? null) as DedupeHashParams['body'],\n headers: ctx.request.headers,\n signal:\n ctx.init.signal === undefined || ctx.init.signal === null\n ? undefined\n : ctx.init.signal,\n requestInit: ctx.init,\n request: ctx.request,\n }\n}\n\nexport function dedupePlugin(options: DedupePluginOptions = {}): ClientPlugin {\n const {\n hashFn = dedupeRequestHash,\n ttl,\n sweepInterval = 5000,\n order = 10,\n } = options\n\n const inFlight = new Map<string, DedupeEntry>()\n let sweeper: ReturnType<typeof setInterval> | undefined\n\n function startSweeper() {\n if (sweeper || typeof ttl !== 'number' || ttl <= 0) return\n sweeper = setInterval(() => {\n const now = Date.now()\n for (const [key, entry] of inFlight.entries()) {\n if (now - entry.createdAt > ttl) {\n inFlight.delete(key)\n }\n }\n if (inFlight.size === 0 && sweeper) {\n clearInterval(sweeper)\n sweeper = undefined\n }\n }, sweepInterval)\n }\n\n function stopSweeperIfIdle() {\n if (inFlight.size === 0 && sweeper) {\n clearInterval(sweeper)\n sweeper = undefined\n }\n }\n\n return {\n name: 'dedupe',\n order,\n wrapDispatch: (next) => async (ctx) => {\n const key = hashFn(contextToHashParams(ctx))\n ctx.state.dedupeKey = key\n\n if (!key) {\n return next(ctx)\n }\n\n const existing = inFlight.get(key)\n if (existing) {\n return new Promise<Response>((resolve, reject) => {\n existing.waiters.push({ resolve, reject })\n })\n }\n\n const waiters: Waiter[] = []\n const actualPromise = next(ctx)\n\n inFlight.set(key, {\n promise: actualPromise,\n waiters,\n createdAt: Date.now(),\n })\n startSweeper()\n\n actualPromise.then(\n (result) => {\n for (const waiter of waiters) {\n waiter.resolve(result.clone())\n }\n },\n (error) => {\n for (const waiter of waiters) {\n waiter.reject(error)\n }\n }\n )\n\n return actualPromise.finally(() => {\n const current = inFlight.get(key)\n if (current?.promise === actualPromise) {\n inFlight.delete(key)\n stopSweeperIfIdle()\n }\n })\n },\n }\n}\n\nexport { dedupeRequestHash }\nexport type { DedupeHashParams }\n","export type DedupeHashParams = {\n method: string\n url: string\n body:\n | string\n | FormData\n | URLSearchParams\n | Blob\n | ArrayBuffer\n | BufferSource\n | null\n | ReadableStream<unknown>\n headers?: Headers | Record<string, string>\n signal?: AbortSignal\n requestInit?: RequestInit\n request?: Request\n}\n\nfunction toBase64(bytes: Uint8Array): string {\n // Use Node Buffer when available, otherwise fall back to btoa for browser-like runtimes.\n const maybeBuffer = (\n globalThis as {\n Buffer?: {\n from(input: Uint8Array): { toString(encoding: string): string }\n }\n }\n ).Buffer\n if (maybeBuffer) {\n return maybeBuffer.from(bytes).toString('base64')\n }\n\n let binary = ''\n for (const byte of bytes) {\n binary += String.fromCharCode(byte)\n }\n if (typeof btoa === 'function') {\n return btoa(binary)\n }\n\n throw new Error('Base64 encoding is not available in this runtime')\n}\n\nexport function dedupeRequestHash(\n params: DedupeHashParams\n): string | undefined {\n const { method, url, body } = params\n let bodyString: string\n if (body instanceof FormData) {\n // Skip deduplication for FormData\n return undefined\n }\n // Skip deduplication for ReadableStream\n if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {\n return undefined\n }\n if (typeof body === 'string') {\n bodyString = body\n } else if (body instanceof URLSearchParams) {\n bodyString = body.toString()\n } else if (body instanceof ArrayBuffer) {\n bodyString = toBase64(new Uint8Array(body))\n } else if (body instanceof Uint8Array) {\n bodyString = toBase64(body)\n } else if (body instanceof Blob) {\n bodyString = `[blob:${body.type}:${body.size}]`\n } else if (body == null) {\n bodyString = ''\n } else {\n try {\n bodyString = JSON.stringify(body)\n } catch {\n bodyString = '[unserializable-body]'\n }\n }\n return `${method.toUpperCase()}|${url}|${bodyString}`\n}\n"],"mappings":"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","names":[]}
|
package/dist/plugins/dedupe.js
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
// src/dedupeRequestHash.ts
|
|
2
|
+
function toBase64(bytes) {
|
|
3
|
+
const maybeBuffer = globalThis.Buffer;
|
|
4
|
+
if (maybeBuffer) {
|
|
5
|
+
return maybeBuffer.from(bytes).toString("base64");
|
|
6
|
+
}
|
|
7
|
+
let binary = "";
|
|
8
|
+
for (const byte of bytes) {
|
|
9
|
+
binary += String.fromCharCode(byte);
|
|
10
|
+
}
|
|
11
|
+
if (typeof btoa === "function") {
|
|
12
|
+
return btoa(binary);
|
|
13
|
+
}
|
|
14
|
+
throw new Error("Base64 encoding is not available in this runtime");
|
|
15
|
+
}
|
|
2
16
|
function dedupeRequestHash(params) {
|
|
3
17
|
const { method, url, body } = params;
|
|
4
|
-
let bodyString
|
|
18
|
+
let bodyString;
|
|
5
19
|
if (body instanceof FormData) {
|
|
6
20
|
return void 0;
|
|
7
21
|
}
|
|
@@ -13,9 +27,9 @@ function dedupeRequestHash(params) {
|
|
|
13
27
|
} else if (body instanceof URLSearchParams) {
|
|
14
28
|
bodyString = body.toString();
|
|
15
29
|
} else if (body instanceof ArrayBuffer) {
|
|
16
|
-
bodyString =
|
|
30
|
+
bodyString = toBase64(new Uint8Array(body));
|
|
17
31
|
} else if (body instanceof Uint8Array) {
|
|
18
|
-
bodyString =
|
|
32
|
+
bodyString = toBase64(body);
|
|
19
33
|
} else if (body instanceof Blob) {
|
|
20
34
|
bodyString = `[blob:${body.type}:${body.size}]`;
|
|
21
35
|
} else if (body == null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/dedupeRequestHash.ts","../../src/plugins/dedupe.ts"],"sourcesContent":["export type DedupeHashParams = {\n method: string\n url: string\n body:\n | string\n | FormData\n | URLSearchParams\n | Blob\n | ArrayBuffer\n | BufferSource\n | null\n | ReadableStream<unknown>\n headers?: Headers | Record<string, string>\n signal?: AbortSignal\n requestInit?: RequestInit\n request?: Request\n}\n\nexport function dedupeRequestHash(\n params: DedupeHashParams\n): string | undefined {\n const { method, url, body } = params\n let bodyString
|
|
1
|
+
{"version":3,"sources":["../../src/dedupeRequestHash.ts","../../src/plugins/dedupe.ts"],"sourcesContent":["export type DedupeHashParams = {\n method: string\n url: string\n body:\n | string\n | FormData\n | URLSearchParams\n | Blob\n | ArrayBuffer\n | BufferSource\n | null\n | ReadableStream<unknown>\n headers?: Headers | Record<string, string>\n signal?: AbortSignal\n requestInit?: RequestInit\n request?: Request\n}\n\nfunction toBase64(bytes: Uint8Array): string {\n // Use Node Buffer when available, otherwise fall back to btoa for browser-like runtimes.\n const maybeBuffer = (\n globalThis as {\n Buffer?: {\n from(input: Uint8Array): { toString(encoding: string): string }\n }\n }\n ).Buffer\n if (maybeBuffer) {\n return maybeBuffer.from(bytes).toString('base64')\n }\n\n let binary = ''\n for (const byte of bytes) {\n binary += String.fromCharCode(byte)\n }\n if (typeof btoa === 'function') {\n return btoa(binary)\n }\n\n throw new Error('Base64 encoding is not available in this runtime')\n}\n\nexport function dedupeRequestHash(\n params: DedupeHashParams\n): string | undefined {\n const { method, url, body } = params\n let bodyString: string\n if (body instanceof FormData) {\n // Skip deduplication for FormData\n return undefined\n }\n // Skip deduplication for ReadableStream\n if (typeof ReadableStream !== 'undefined' && body instanceof ReadableStream) {\n return undefined\n }\n if (typeof body === 'string') {\n bodyString = body\n } else if (body instanceof URLSearchParams) {\n bodyString = body.toString()\n } else if (body instanceof ArrayBuffer) {\n bodyString = toBase64(new Uint8Array(body))\n } else if (body instanceof Uint8Array) {\n bodyString = toBase64(body)\n } else if (body instanceof Blob) {\n bodyString = `[blob:${body.type}:${body.size}]`\n } else if (body == null) {\n bodyString = ''\n } else {\n try {\n bodyString = JSON.stringify(body)\n } catch {\n bodyString = '[unserializable-body]'\n }\n }\n return `${method.toUpperCase()}|${url}|${bodyString}`\n}\n","import type { ClientPlugin, PluginRequestContext } from '../plugins.js'\nimport {\n dedupeRequestHash,\n type DedupeHashParams,\n} from '../dedupeRequestHash.js'\n\ntype Waiter = {\n resolve: (value: Response) => void\n reject: (reason?: unknown) => void\n}\n\ntype DedupeEntry = {\n promise: Promise<Response>\n waiters: Waiter[]\n createdAt: number\n}\n\nexport type DedupePluginOptions = {\n hashFn?: (params: DedupeHashParams) => string | undefined\n ttl?: number\n sweepInterval?: number\n order?: number\n}\n\nfunction contextToHashParams(ctx: PluginRequestContext): DedupeHashParams {\n return {\n method: ctx.request.method,\n url: ctx.request.url,\n body: (ctx.init.body ?? null) as DedupeHashParams['body'],\n headers: ctx.request.headers,\n signal:\n ctx.init.signal === undefined || ctx.init.signal === null\n ? undefined\n : ctx.init.signal,\n requestInit: ctx.init,\n request: ctx.request,\n }\n}\n\nexport function dedupePlugin(options: DedupePluginOptions = {}): ClientPlugin {\n const {\n hashFn = dedupeRequestHash,\n ttl,\n sweepInterval = 5000,\n order = 10,\n } = options\n\n const inFlight = new Map<string, DedupeEntry>()\n let sweeper: ReturnType<typeof setInterval> | undefined\n\n function startSweeper() {\n if (sweeper || typeof ttl !== 'number' || ttl <= 0) return\n sweeper = setInterval(() => {\n const now = Date.now()\n for (const [key, entry] of inFlight.entries()) {\n if (now - entry.createdAt > ttl) {\n inFlight.delete(key)\n }\n }\n if (inFlight.size === 0 && sweeper) {\n clearInterval(sweeper)\n sweeper = undefined\n }\n }, sweepInterval)\n }\n\n function stopSweeperIfIdle() {\n if (inFlight.size === 0 && sweeper) {\n clearInterval(sweeper)\n sweeper = undefined\n }\n }\n\n return {\n name: 'dedupe',\n order,\n wrapDispatch: (next) => async (ctx) => {\n const key = hashFn(contextToHashParams(ctx))\n ctx.state.dedupeKey = key\n\n if (!key) {\n return next(ctx)\n }\n\n const existing = inFlight.get(key)\n if (existing) {\n return new Promise<Response>((resolve, reject) => {\n existing.waiters.push({ resolve, reject })\n })\n }\n\n const waiters: Waiter[] = []\n const actualPromise = next(ctx)\n\n inFlight.set(key, {\n promise: actualPromise,\n waiters,\n createdAt: Date.now(),\n })\n startSweeper()\n\n actualPromise.then(\n (result) => {\n for (const waiter of waiters) {\n waiter.resolve(result.clone())\n }\n },\n (error) => {\n for (const waiter of waiters) {\n waiter.reject(error)\n }\n }\n )\n\n return actualPromise.finally(() => {\n const current = inFlight.get(key)\n if (current?.promise === actualPromise) {\n inFlight.delete(key)\n stopSweeperIfIdle()\n }\n })\n },\n }\n}\n\nexport { dedupeRequestHash }\nexport type { DedupeHashParams }\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fetchkit/ffetch",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.3",
|
|
4
4
|
"description": "Fetch wrapper with configurable timeouts, retries, and TypeScript-first DX",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fetch",
|
|
@@ -89,12 +89,12 @@
|
|
|
89
89
|
},
|
|
90
90
|
"devDependencies": {
|
|
91
91
|
"@changesets/cli": "^2.29.5",
|
|
92
|
-
"@eslint/js": "^
|
|
92
|
+
"@eslint/js": "^10.0.1",
|
|
93
93
|
"@types/node": "^25.9.1",
|
|
94
94
|
"@typescript-eslint/eslint-plugin": "^8.39.0",
|
|
95
95
|
"@typescript-eslint/parser": "^8.39.0",
|
|
96
|
-
"@vitest/coverage-v8": "^
|
|
97
|
-
"eslint": "^
|
|
96
|
+
"@vitest/coverage-v8": "^4.1.8",
|
|
97
|
+
"eslint": "^10.4.1",
|
|
98
98
|
"happy-dom": "^20.9.0",
|
|
99
99
|
"husky": "^9.1.7",
|
|
100
100
|
"jsr": "^0.14.3",
|
|
@@ -102,6 +102,6 @@
|
|
|
102
102
|
"prettier": "^3.6.2",
|
|
103
103
|
"tsup": "^8.5.0",
|
|
104
104
|
"typescript": "^6.0.3",
|
|
105
|
-
"vitest": "^
|
|
105
|
+
"vitest": "^4.1.8"
|
|
106
106
|
}
|
|
107
107
|
}
|