@fetchkit/ffetch 5.5.3 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/index.cjs +63 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +63 -46
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/plugins/bulkhead.cjs +25 -13
- package/dist/plugins/bulkhead.cjs.map +1 -1
- package/dist/plugins/bulkhead.js +22 -14
- package/dist/plugins/bulkhead.js.map +1 -1
- package/dist/plugins/dedupe.cjs +53 -4
- package/dist/plugins/dedupe.cjs.map +1 -1
- package/dist/plugins/dedupe.js +39 -4
- package/dist/plugins/dedupe.js.map +1 -1
- package/dist/plugins/download-progress.cjs +3 -2
- package/dist/plugins/download-progress.cjs.map +1 -1
- package/dist/plugins/download-progress.js +3 -2
- package/dist/plugins/download-progress.js.map +1 -1
- package/dist/plugins/hedge.cjs +22 -22
- package/dist/plugins/hedge.cjs.map +1 -1
- package/dist/plugins/hedge.js +22 -22
- package/dist/plugins/hedge.js.map +1 -1
- package/package.json +2 -2
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\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/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 | Promise<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 } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !(await shouldRetry(ctx))) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && (await shouldRetry(ctx))) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\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 const controller = new AbortController()\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 signals.push(controller.signal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\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 }\n pluginContext.metadata.signals.combined = combinedSignal\n\n let coreErrorReported = false\n const reportCoreError = async (error: unknown, hookRequest: Request) => {\n if (coreErrorReported) return\n if (\n !(error instanceof TimeoutError) &&\n !(error instanceof AbortError) &&\n !(error instanceof NetworkError) &&\n !(error instanceof RetryLimitError)\n ) {\n return\n }\n\n coreErrorReported = true\n if (error instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(hookRequest)\n } else if (error instanceof AbortError) {\n await effectiveHooks.onAbort?.(hookRequest)\n }\n await effectiveHooks.onError?.(hookRequest, error)\n }\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 = async (\n ctx: import('./types').RetryContext\n ) => {\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 await 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 (controller.signal.aborted) {\n throw new AbortError('Request was aborted')\n }\n if (userSignal?.aborted) {\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n throw new TimeoutError('signal timed out')\n }\n if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n dispatchSignal.reason\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt.clone(), {\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 throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n throw new AbortError('Request was aborted by user')\n } else if (controller.signal.aborted) {\n throw new AbortError('Request was aborted', err)\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 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 (err instanceof TimeoutError) {\n if (dispatchCtx === pluginContext) {\n await reportCoreError(err, requestForAttempt)\n }\n throw err\n }\n if (err instanceof AbortError) {\n if (dispatchCtx === pluginContext) {\n await reportCoreError(err, requestForAttempt)\n }\n throw err\n }\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 NetworkError) {\n if (dispatchCtx === pluginContext) {\n await reportCoreError(err, requestForAttempt)\n }\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 if (dispatchCtx === pluginContext) {\n await reportCoreError(retryErr, requestForAttempt)\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 let completeCalled = false\n const callComplete = async (\n response: Response | undefined,\n error: unknown\n ) => {\n if (completeCalled) return\n completeCalled = true\n await effectiveHooks.onComplete?.(request, response, error)\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n await callComplete(response, undefined)\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n await reportCoreError(err, request)\n await callComplete(undefined, err)\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
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as
|
|
1
|
+
import{a as P,b as K,c as se,d as u,e as B,f as O,g as ie}from"./chunk-X64PXWPN.min.js";var Y=l=>{let i=l.response?.headers.get("Retry-After");if(i){let o=parseInt(i,10);if(!isNaN(o))return o*1e3;let b=Date.parse(i);if(!isNaN(b))return Math.max(0,b-Date.now())}return 2**l.attempt*200+Math.random()*100};function X(l,i){return l<=0?Promise.resolve():new Promise(o=>{if(!i){setTimeout(o,l);return}if(i.aborted){o();return}let b=()=>{clearTimeout(T),o()},T=setTimeout(()=>{i.removeEventListener("abort",b),o()},l);i.addEventListener("abort",b,{once:!0})})}async function Z(l,i,o,b=()=>!0,T,k){let A,D;for(let R=0;R<=i;R++){let a={attempt:R+1,request:T,response:D,error:A};try{D=await l()}catch(p){if(A=p,a.error=p,R===i||!await b(a))throw p;let L=typeof o=="function"?o(a):o;await X(L,k);continue}if(a.response=D,a.error=void 0,R<i&&await b(a)){let p=typeof o=="function"?o(a):o;await X(p,k);continue}return D}throw A}function ee(l){let{error:i,response:o}=l;return i instanceof u||i instanceof K||i instanceof P?!1:o?o.status>=500||o.status===429:!0}function ae(l={}){let{timeout:i=5e3,retries:o=0,retryDelay:b=Y,shouldRetry:T=ee,hooks:k={},fetchHandler:A,plugins:D=[]}=l,R=Object.create(null),a=D.map((f,s)=>({plugin:f,index:s})).sort((f,s)=>{let E=f.plugin.order??0,m=s.plugin.order??0;return E!==m?E-m:f.index-s.index}).map(f=>f.plugin);for(let f of a)f.setup?.({defineExtension:(s,E)=>{let m=s;if(m in R)throw new Error(`Plugin extension collision for property "${String(m)}"`);if("get"in E){R[m]={get:E.get,enumerable:E.enumerable??!0,configurable:!1};return}R[m]={value:E.value,writable:!1,enumerable:E.enumerable??!0,configurable:!1}}});let p=[];function L(){for(let f of p)f.controller?.abort()}let C=(f,s={})=>{let m=(async()=>{let y=new Request(f,s),g={...k,...s.hooks||{}};g.transformRequest&&(y=await g.transformRequest(y)),await g.before?.(y);let M=s.retries??o,U=typeof s.retryDelay<"u"?s.retryDelay:b,te=s.shouldRetry??T,I=s.timeout??i,w=s.signal,H=y.signal,c={request:y,init:s,state:Object.create(null),metadata:{startedAt:Date.now(),timeoutMs:I,signals:{user:w??void 0,transformed:H},retry:{configuredRetries:M,configuredDelay:U,attempt:0}}};for(let e of a)await e.preRequest?.(c);let _=typeof s.throwOnHttpError<"u"?s.throwOnHttpError:l.throwOnHttpError??!1;function re(e){if(typeof AbortSignal?.timeout=="function")return AbortSignal.timeout(e);let r=new AbortController,d=setTimeout(()=>r.abort(),e);return r.signal.addEventListener("abort",()=>clearTimeout(d),{once:!0}),r.signal}let q,v,j=new AbortController;I>0&&(q=re(I),c.metadata.signals.timeout=q);let x=[];if(w&&x.push(w),H&&H!==w&&x.push(H),q&&x.push(q),x.push(j.signal),x.length===1)v=x[0];else{if(typeof AbortSignal.any!="function")throw new Error("AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.");v=AbortSignal.any(x)}c.metadata.signals.combined=v;let z=!1,F=async(e,r)=>{z||!(e instanceof P)&&!(e instanceof u)&&!(e instanceof O)&&!(e instanceof B)||(z=!0,e instanceof P?await g.onTimeout?.(r):e instanceof u&&await g.onAbort?.(r),await g.onError?.(r,e))},ne=async(e,r)=>{let d=e.request,N=0,oe=async t=>{N=t.attempt,e.metadata.retry.attempt=N,e.metadata.retry.lastError=t.error,e.metadata.retry.lastResponse=t.response;let h=te(t);return e.metadata.retry.shouldRetryResult=h,h&&N<=M&&await g.onRetry?.(d,N-1,t.error,t.response),h},W;try{let t=await Z(async()=>{if(j.signal.aborted)throw new u("Request was aborted");if(w?.aborted)throw new u("Request was aborted by user");if(q?.aborted)throw new P("signal timed out");if(r?.aborted)throw w?.aborted?new u("Request was aborted by user"):q?.aborted?new P("signal timed out"):new u("Request was aborted",r.reason);let h=new Request(d.clone(),{signal:r});try{let S=await(s.fetchHandler??A??fetch)(h);return W=S,e.metadata.retry.lastResponse=S,S}catch(n){throw e.metadata.retry.lastError=n,n instanceof DOMException&&n.name==="AbortError"?q?.aborted&&(!w||!w.aborted)?new P("signal timed out",n):w?.aborted?new u("Request was aborted by user"):j.signal.aborted?new u("Request was aborted",n):new u("Request was aborted",new DOMException("Aborted","AbortError")):n instanceof TypeError&&/NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(n.message)?new O(n.message,n):n}},M,U,oe,d,r);if(g.transformResponse&&(t=await g.transformResponse(t,d)),await g.after?.(d,t),_&&(t.status>=400&&t.status<500&&t.status!==429||t.status>=500||t.status===429)){let{HttpError:h}=await import("./error-HT5UPPWM.min.js");throw new h(`HTTP error: ${t.status} ${t.statusText}`,t)}return t}catch(t){if(e.metadata.retry.lastError=t,t instanceof P)throw e===c&&await F(t,d),t;if(t instanceof u)throw e===c&&await F(t,d),t;if(W){let n=W;if(_&&(n.status>=400&&n.status<500&&n.status!==429||n.status>=500||n.status===429)){let{HttpError:S}=await import("./error-HT5UPPWM.min.js");throw new S(`HTTP error: ${n.status} ${n.statusText}`,n)}return n}if(t instanceof O)throw e===c&&await F(t,d),t;let h=new B(typeof t=="object"&&t&&"message"in t&&typeof t.message=="string"?t.message:"Retry limit reached",t);throw e===c&&await F(h,d),h}},$=async e=>{let r=e===c?v:e.request.signal;return ne(e,r)};for(let e=a.length-1;e>=0;e--){let r=a[e];r.wrapDispatch&&($=r.wrapDispatch($))}let G=!1,J=async(e,r)=>{G||(G=!0,await g.onComplete?.(y,e,r))},Q=$(c).then(async e=>{await J(e,void 0);for(let r of a)await r.onSuccess?.(c,e);return e}).catch(async e=>{await F(e,y),await J(void 0,e);for(let r of a)await r.onError?.(c,e);throw e}),V={promise:Q,request:y,controller:j};return p.push(V),Q.finally(async()=>{for(let r of a)await r.onFinally?.(c);let e=p.indexOf(V);e>-1&&p.splice(e,1)})})();for(let y of a)y.decoratePromise&&(m=y.decoratePromise(m));return m};return Object.defineProperty(C,"pendingRequests",{get(){return p},enumerable:!1,configurable:!1}),Object.defineProperty(C,"abortAll",{value:L,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperties(C,R),C}export{u as AbortError,se as BulkheadFullError,K as CircuitOpenError,ie as HttpError,O as NetworkError,B as RetryLimitError,P as TimeoutError,ae as createClient};
|
|
2
2
|
//# sourceMappingURL=index.min.js.map
|
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\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 | Promise<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 } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !(await shouldRetry(ctx))) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && (await shouldRetry(ctx))) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\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 const controller = new AbortController()\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 signals.push(controller.signal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\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 }\n pluginContext.metadata.signals.combined = combinedSignal\n\n let coreErrorReported = false\n const reportCoreError = async (error: unknown, hookRequest: Request) => {\n if (coreErrorReported) return\n if (\n !(error instanceof TimeoutError) &&\n !(error instanceof AbortError) &&\n !(error instanceof NetworkError) &&\n !(error instanceof RetryLimitError)\n ) {\n return\n }\n\n coreErrorReported = true\n if (error instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(hookRequest)\n } else if (error instanceof AbortError) {\n await effectiveHooks.onAbort?.(hookRequest)\n }\n await effectiveHooks.onError?.(hookRequest, error)\n }\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 = async (\n ctx: import('./types').RetryContext\n ) => {\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 await 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 (controller.signal.aborted) {\n throw new AbortError('Request was aborted')\n }\n if (userSignal?.aborted) {\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n throw new TimeoutError('signal timed out')\n }\n if (dispatchSignal?.aborted) {\n if (userSignal?.aborted) {\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n dispatchSignal.reason\n )\n }\n }\n const reqWithSignal = new Request(requestForAttempt.clone(), {\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 throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n throw new AbortError('Request was aborted by user')\n } else if (controller.signal.aborted) {\n throw new AbortError('Request was aborted', err)\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 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 (err instanceof TimeoutError) {\n if (dispatchCtx === pluginContext) {\n await reportCoreError(err, requestForAttempt)\n }\n throw err\n }\n if (err instanceof AbortError) {\n if (dispatchCtx === pluginContext) {\n await reportCoreError(err, requestForAttempt)\n }\n throw err\n }\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 NetworkError) {\n if (dispatchCtx === pluginContext) {\n await reportCoreError(err, requestForAttempt)\n }\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 if (dispatchCtx === pluginContext) {\n await reportCoreError(retryErr, requestForAttempt)\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 let completeCalled = false\n const callComplete = async (\n response: Response | undefined,\n error: unknown\n ) => {\n if (completeCalled) return\n completeCalled = true\n await effectiveHooks.onComplete?.(request, response, error)\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n await callComplete(response, undefined)\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n await reportCoreError(err, request)\n await callComplete(undefined, err)\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","err","wait","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","coreErrorReported","reportCoreError","error","hookRequest","TimeoutError","AbortError","NetworkError","RetryLimitError","retryWithHooks","dispatchCtx","dispatchSignal","requestForAttempt","attempt","shouldRetryWithHook","ctx","retrying","lastResponse","res","retry","reqWithSignal","response","err","HttpError","resp","retryErr","dispatch","i","completeCalled","callComplete","actualPromise","pendingEntry"]}
|
|
@@ -32,6 +32,11 @@ var BaseError = class extends Error {
|
|
|
32
32
|
if (cause !== void 0) this.cause = cause;
|
|
33
33
|
}
|
|
34
34
|
};
|
|
35
|
+
var TimeoutError = class extends BaseError {
|
|
36
|
+
constructor(message = "Request timed out", cause) {
|
|
37
|
+
super("TimeoutError", message, cause);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
35
40
|
var BulkheadFullError = class extends BaseError {
|
|
36
41
|
constructor(message = "Bulkhead queue is full", cause) {
|
|
37
42
|
super("BulkheadFullError", message, cause);
|
|
@@ -48,17 +53,21 @@ function bulkheadPlugin(options) {
|
|
|
48
53
|
const { maxConcurrent, maxQueue, onReject, order = 5 } = options;
|
|
49
54
|
let activeCount = 0;
|
|
50
55
|
const queue = [];
|
|
51
|
-
function
|
|
56
|
+
function makeCancellationError(ctx) {
|
|
57
|
+
const { signals } = ctx.metadata;
|
|
58
|
+
if (signals.user?.aborted) {
|
|
59
|
+
return new AbortError("Request was aborted by user", signals.user.reason);
|
|
60
|
+
}
|
|
61
|
+
if (signals.timeout?.aborted) {
|
|
62
|
+
return new TimeoutError("signal timed out", signals.timeout.reason);
|
|
63
|
+
}
|
|
64
|
+
const signal = signals.combined ?? ctx.request.signal;
|
|
52
65
|
return new AbortError("Request was aborted", signal.reason);
|
|
53
66
|
}
|
|
54
67
|
function drainQueue() {
|
|
55
68
|
while (activeCount < maxConcurrent && queue.length > 0) {
|
|
56
69
|
const next = queue.shift();
|
|
57
70
|
next.cleanup();
|
|
58
|
-
if (next.request.signal.aborted) {
|
|
59
|
-
next.reject(makeAbortError(next.request.signal));
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
71
|
activeCount++;
|
|
63
72
|
next.resolve();
|
|
64
73
|
}
|
|
@@ -67,7 +76,9 @@ function bulkheadPlugin(options) {
|
|
|
67
76
|
activeCount = Math.max(0, activeCount - 1);
|
|
68
77
|
drainQueue();
|
|
69
78
|
}
|
|
70
|
-
async function acquire(
|
|
79
|
+
async function acquire(ctx) {
|
|
80
|
+
const { request } = ctx;
|
|
81
|
+
const signal = ctx.metadata.signals.combined ?? request.signal;
|
|
71
82
|
if (activeCount < maxConcurrent) {
|
|
72
83
|
activeCount++;
|
|
73
84
|
return;
|
|
@@ -78,7 +89,8 @@ function bulkheadPlugin(options) {
|
|
|
78
89
|
}
|
|
79
90
|
await new Promise((resolve, reject) => {
|
|
80
91
|
const entry = {
|
|
81
|
-
|
|
92
|
+
ctx,
|
|
93
|
+
signal,
|
|
82
94
|
resolve,
|
|
83
95
|
reject,
|
|
84
96
|
cleanup: () => {
|
|
@@ -90,16 +102,16 @@ function bulkheadPlugin(options) {
|
|
|
90
102
|
queue.splice(idx, 1);
|
|
91
103
|
}
|
|
92
104
|
entry.cleanup();
|
|
93
|
-
reject(
|
|
105
|
+
reject(makeCancellationError(ctx));
|
|
94
106
|
};
|
|
95
107
|
entry.cleanup = () => {
|
|
96
|
-
|
|
108
|
+
signal.removeEventListener("abort", onAbort);
|
|
97
109
|
};
|
|
98
|
-
if (
|
|
99
|
-
reject(
|
|
110
|
+
if (signal.aborted) {
|
|
111
|
+
reject(makeCancellationError(ctx));
|
|
100
112
|
return;
|
|
101
113
|
}
|
|
102
|
-
|
|
114
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
103
115
|
queue.push(entry);
|
|
104
116
|
});
|
|
105
117
|
}
|
|
@@ -119,7 +131,7 @@ function bulkheadPlugin(options) {
|
|
|
119
131
|
wrapDispatch: (next) => async (ctx) => {
|
|
120
132
|
let acquired = false;
|
|
121
133
|
try {
|
|
122
|
-
await acquire(ctx
|
|
134
|
+
await acquire(ctx);
|
|
123
135
|
acquired = true;
|
|
124
136
|
return await next(ctx);
|
|
125
137
|
} finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/bulkhead.ts","../../src/error.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\nimport { AbortError, BulkheadFullError } from '../error.js'\n\nexport type BulkheadPluginExtension = {\n activeCount: number\n queueDepth: number\n}\n\nexport type BulkheadPluginOptions = {\n maxConcurrent: number\n maxQueue?: number\n onReject?: (req: Request) => void | Promise<void>\n order?: number\n}\n\ntype QueueEntry = {\n
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/bulkhead.ts","../../src/error.ts"],"sourcesContent":["import type { ClientPlugin, PluginRequestContext } from '../plugins.js'\nimport { AbortError, BulkheadFullError, TimeoutError } from '../error.js'\n\nexport type BulkheadPluginExtension = {\n activeCount: number\n queueDepth: number\n}\n\nexport type BulkheadPluginOptions = {\n maxConcurrent: number\n maxQueue?: number\n onReject?: (req: Request) => void | Promise<void>\n order?: number\n}\n\ntype QueueEntry = {\n ctx: PluginRequestContext\n signal: AbortSignal\n resolve: () => void\n reject: (err: unknown) => void\n cleanup: () => void\n}\n\nexport function bulkheadPlugin(\n options: BulkheadPluginOptions\n): ClientPlugin<BulkheadPluginExtension> {\n const { maxConcurrent, maxQueue, onReject, order = 5 } = options\n\n let activeCount = 0\n const queue: QueueEntry[] = []\n\n function makeCancellationError(ctx: PluginRequestContext): Error {\n const { signals } = ctx.metadata\n if (signals.user?.aborted) {\n return new AbortError('Request was aborted by user', signals.user.reason)\n }\n if (signals.timeout?.aborted) {\n return new TimeoutError('signal timed out', signals.timeout.reason)\n }\n const signal = signals.combined ?? ctx.request.signal\n return new AbortError('Request was aborted', signal.reason)\n }\n\n function drainQueue(): void {\n while (activeCount < maxConcurrent && queue.length > 0) {\n const next = queue.shift()!\n next.cleanup()\n\n activeCount++\n next.resolve()\n }\n }\n\n function release(): void {\n activeCount = Math.max(0, activeCount - 1)\n drainQueue()\n }\n\n async function acquire(ctx: PluginRequestContext): Promise<void> {\n const { request } = ctx\n const signal = ctx.metadata.signals.combined ?? request.signal\n if (activeCount < maxConcurrent) {\n activeCount++\n return\n }\n\n if (\n typeof maxQueue === 'number' &&\n maxQueue >= 0 &&\n queue.length >= maxQueue\n ) {\n await onReject?.(request)\n throw new BulkheadFullError('Bulkhead queue is full')\n }\n\n await new Promise<void>((resolve, reject) => {\n const entry: QueueEntry = {\n ctx,\n signal,\n resolve,\n reject,\n cleanup: () => {},\n }\n\n const onAbort = () => {\n const idx = queue.indexOf(entry)\n if (idx >= 0) {\n queue.splice(idx, 1)\n }\n entry.cleanup()\n reject(makeCancellationError(ctx))\n }\n\n entry.cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n }\n\n if (signal.aborted) {\n reject(makeCancellationError(ctx))\n return\n }\n\n signal.addEventListener('abort', onAbort, { once: true })\n queue.push(entry)\n })\n }\n\n return {\n name: 'bulkhead',\n order,\n setup: ({ defineExtension }) => {\n defineExtension('activeCount', {\n get: () => activeCount,\n enumerable: true,\n })\n defineExtension('queueDepth', {\n get: () => queue.length,\n enumerable: true,\n })\n },\n wrapDispatch: (next) => async (ctx) => {\n let acquired = false\n try {\n await acquire(ctx)\n acquired = true\n return await next(ctx)\n } finally {\n if (acquired) {\n release()\n }\n }\n },\n }\n}\n","// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class BulkheadFullError extends BaseError {\n constructor(message = 'Bulkhead queue is full', cause?: unknown) {\n super('BulkheadFullError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/plugins/bulkhead.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
AbortError,
|
|
3
|
-
BulkheadFullError
|
|
3
|
+
BulkheadFullError,
|
|
4
|
+
TimeoutError
|
|
4
5
|
} from "../chunk-ZIJ2DZYZ.js";
|
|
5
6
|
|
|
6
7
|
// src/plugins/bulkhead.ts
|
|
@@ -8,17 +9,21 @@ function bulkheadPlugin(options) {
|
|
|
8
9
|
const { maxConcurrent, maxQueue, onReject, order = 5 } = options;
|
|
9
10
|
let activeCount = 0;
|
|
10
11
|
const queue = [];
|
|
11
|
-
function
|
|
12
|
+
function makeCancellationError(ctx) {
|
|
13
|
+
const { signals } = ctx.metadata;
|
|
14
|
+
if (signals.user?.aborted) {
|
|
15
|
+
return new AbortError("Request was aborted by user", signals.user.reason);
|
|
16
|
+
}
|
|
17
|
+
if (signals.timeout?.aborted) {
|
|
18
|
+
return new TimeoutError("signal timed out", signals.timeout.reason);
|
|
19
|
+
}
|
|
20
|
+
const signal = signals.combined ?? ctx.request.signal;
|
|
12
21
|
return new AbortError("Request was aborted", signal.reason);
|
|
13
22
|
}
|
|
14
23
|
function drainQueue() {
|
|
15
24
|
while (activeCount < maxConcurrent && queue.length > 0) {
|
|
16
25
|
const next = queue.shift();
|
|
17
26
|
next.cleanup();
|
|
18
|
-
if (next.request.signal.aborted) {
|
|
19
|
-
next.reject(makeAbortError(next.request.signal));
|
|
20
|
-
continue;
|
|
21
|
-
}
|
|
22
27
|
activeCount++;
|
|
23
28
|
next.resolve();
|
|
24
29
|
}
|
|
@@ -27,7 +32,9 @@ function bulkheadPlugin(options) {
|
|
|
27
32
|
activeCount = Math.max(0, activeCount - 1);
|
|
28
33
|
drainQueue();
|
|
29
34
|
}
|
|
30
|
-
async function acquire(
|
|
35
|
+
async function acquire(ctx) {
|
|
36
|
+
const { request } = ctx;
|
|
37
|
+
const signal = ctx.metadata.signals.combined ?? request.signal;
|
|
31
38
|
if (activeCount < maxConcurrent) {
|
|
32
39
|
activeCount++;
|
|
33
40
|
return;
|
|
@@ -38,7 +45,8 @@ function bulkheadPlugin(options) {
|
|
|
38
45
|
}
|
|
39
46
|
await new Promise((resolve, reject) => {
|
|
40
47
|
const entry = {
|
|
41
|
-
|
|
48
|
+
ctx,
|
|
49
|
+
signal,
|
|
42
50
|
resolve,
|
|
43
51
|
reject,
|
|
44
52
|
cleanup: () => {
|
|
@@ -50,16 +58,16 @@ function bulkheadPlugin(options) {
|
|
|
50
58
|
queue.splice(idx, 1);
|
|
51
59
|
}
|
|
52
60
|
entry.cleanup();
|
|
53
|
-
reject(
|
|
61
|
+
reject(makeCancellationError(ctx));
|
|
54
62
|
};
|
|
55
63
|
entry.cleanup = () => {
|
|
56
|
-
|
|
64
|
+
signal.removeEventListener("abort", onAbort);
|
|
57
65
|
};
|
|
58
|
-
if (
|
|
59
|
-
reject(
|
|
66
|
+
if (signal.aborted) {
|
|
67
|
+
reject(makeCancellationError(ctx));
|
|
60
68
|
return;
|
|
61
69
|
}
|
|
62
|
-
|
|
70
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
63
71
|
queue.push(entry);
|
|
64
72
|
});
|
|
65
73
|
}
|
|
@@ -79,7 +87,7 @@ function bulkheadPlugin(options) {
|
|
|
79
87
|
wrapDispatch: (next) => async (ctx) => {
|
|
80
88
|
let acquired = false;
|
|
81
89
|
try {
|
|
82
|
-
await acquire(ctx
|
|
90
|
+
await acquire(ctx);
|
|
83
91
|
acquired = true;
|
|
84
92
|
return await next(ctx);
|
|
85
93
|
} finally {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/bulkhead.ts"],"sourcesContent":["import type { ClientPlugin } from '../plugins.js'\nimport { AbortError, BulkheadFullError } from '../error.js'\n\nexport type BulkheadPluginExtension = {\n activeCount: number\n queueDepth: number\n}\n\nexport type BulkheadPluginOptions = {\n maxConcurrent: number\n maxQueue?: number\n onReject?: (req: Request) => void | Promise<void>\n order?: number\n}\n\ntype QueueEntry = {\n
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/bulkhead.ts"],"sourcesContent":["import type { ClientPlugin, PluginRequestContext } from '../plugins.js'\nimport { AbortError, BulkheadFullError, TimeoutError } from '../error.js'\n\nexport type BulkheadPluginExtension = {\n activeCount: number\n queueDepth: number\n}\n\nexport type BulkheadPluginOptions = {\n maxConcurrent: number\n maxQueue?: number\n onReject?: (req: Request) => void | Promise<void>\n order?: number\n}\n\ntype QueueEntry = {\n ctx: PluginRequestContext\n signal: AbortSignal\n resolve: () => void\n reject: (err: unknown) => void\n cleanup: () => void\n}\n\nexport function bulkheadPlugin(\n options: BulkheadPluginOptions\n): ClientPlugin<BulkheadPluginExtension> {\n const { maxConcurrent, maxQueue, onReject, order = 5 } = options\n\n let activeCount = 0\n const queue: QueueEntry[] = []\n\n function makeCancellationError(ctx: PluginRequestContext): Error {\n const { signals } = ctx.metadata\n if (signals.user?.aborted) {\n return new AbortError('Request was aborted by user', signals.user.reason)\n }\n if (signals.timeout?.aborted) {\n return new TimeoutError('signal timed out', signals.timeout.reason)\n }\n const signal = signals.combined ?? ctx.request.signal\n return new AbortError('Request was aborted', signal.reason)\n }\n\n function drainQueue(): void {\n while (activeCount < maxConcurrent && queue.length > 0) {\n const next = queue.shift()!\n next.cleanup()\n\n activeCount++\n next.resolve()\n }\n }\n\n function release(): void {\n activeCount = Math.max(0, activeCount - 1)\n drainQueue()\n }\n\n async function acquire(ctx: PluginRequestContext): Promise<void> {\n const { request } = ctx\n const signal = ctx.metadata.signals.combined ?? request.signal\n if (activeCount < maxConcurrent) {\n activeCount++\n return\n }\n\n if (\n typeof maxQueue === 'number' &&\n maxQueue >= 0 &&\n queue.length >= maxQueue\n ) {\n await onReject?.(request)\n throw new BulkheadFullError('Bulkhead queue is full')\n }\n\n await new Promise<void>((resolve, reject) => {\n const entry: QueueEntry = {\n ctx,\n signal,\n resolve,\n reject,\n cleanup: () => {},\n }\n\n const onAbort = () => {\n const idx = queue.indexOf(entry)\n if (idx >= 0) {\n queue.splice(idx, 1)\n }\n entry.cleanup()\n reject(makeCancellationError(ctx))\n }\n\n entry.cleanup = () => {\n signal.removeEventListener('abort', onAbort)\n }\n\n if (signal.aborted) {\n reject(makeCancellationError(ctx))\n return\n }\n\n signal.addEventListener('abort', onAbort, { once: true })\n queue.push(entry)\n })\n }\n\n return {\n name: 'bulkhead',\n order,\n setup: ({ defineExtension }) => {\n defineExtension('activeCount', {\n get: () => activeCount,\n enumerable: true,\n })\n defineExtension('queueDepth', {\n get: () => queue.length,\n enumerable: true,\n })\n },\n wrapDispatch: (next) => async (ctx) => {\n let acquired = false\n try {\n await acquire(ctx)\n acquired = true\n return await next(ctx)\n } finally {\n if (acquired) {\n release()\n }\n }\n },\n }\n}\n"],"mappings":";;;;;;;AAuBO,SAAS,eACd,SACuC;AACvC,QAAM,EAAE,eAAe,UAAU,UAAU,QAAQ,EAAE,IAAI;AAEzD,MAAI,cAAc;AAClB,QAAM,QAAsB,CAAC;AAE7B,WAAS,sBAAsB,KAAkC;AAC/D,UAAM,EAAE,QAAQ,IAAI,IAAI;AACxB,QAAI,QAAQ,MAAM,SAAS;AACzB,aAAO,IAAI,WAAW,+BAA+B,QAAQ,KAAK,MAAM;AAAA,IAC1E;AACA,QAAI,QAAQ,SAAS,SAAS;AAC5B,aAAO,IAAI,aAAa,oBAAoB,QAAQ,QAAQ,MAAM;AAAA,IACpE;AACA,UAAM,SAAS,QAAQ,YAAY,IAAI,QAAQ;AAC/C,WAAO,IAAI,WAAW,uBAAuB,OAAO,MAAM;AAAA,EAC5D;AAEA,WAAS,aAAmB;AAC1B,WAAO,cAAc,iBAAiB,MAAM,SAAS,GAAG;AACtD,YAAM,OAAO,MAAM,MAAM;AACzB,WAAK,QAAQ;AAEb;AACA,WAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,kBAAc,KAAK,IAAI,GAAG,cAAc,CAAC;AACzC,eAAW;AAAA,EACb;AAEA,iBAAe,QAAQ,KAA0C;AAC/D,UAAM,EAAE,QAAQ,IAAI;AACpB,UAAM,SAAS,IAAI,SAAS,QAAQ,YAAY,QAAQ;AACxD,QAAI,cAAc,eAAe;AAC/B;AACA;AAAA,IACF;AAEA,QACE,OAAO,aAAa,YACpB,YAAY,KACZ,MAAM,UAAU,UAChB;AACA,YAAM,WAAW,OAAO;AACxB,YAAM,IAAI,kBAAkB,wBAAwB;AAAA,IACtD;AAEA,UAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,YAAM,QAAoB;AAAA,QACxB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,SAAS,MAAM;AAAA,QAAC;AAAA,MAClB;AAEA,YAAM,UAAU,MAAM;AACpB,cAAM,MAAM,MAAM,QAAQ,KAAK;AAC/B,YAAI,OAAO,GAAG;AACZ,gBAAM,OAAO,KAAK,CAAC;AAAA,QACrB;AACA,cAAM,QAAQ;AACd,eAAO,sBAAsB,GAAG,CAAC;AAAA,MACnC;AAEA,YAAM,UAAU,MAAM;AACpB,eAAO,oBAAoB,SAAS,OAAO;AAAA,MAC7C;AAEA,UAAI,OAAO,SAAS;AAClB,eAAO,sBAAsB,GAAG,CAAC;AACjC;AAAA,MACF;AAEA,aAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACxD,YAAM,KAAK,KAAK;AAAA,IAClB,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO,CAAC,EAAE,gBAAgB,MAAM;AAC9B,sBAAgB,eAAe;AAAA,QAC7B,KAAK,MAAM;AAAA,QACX,YAAY;AAAA,MACd,CAAC;AACD,sBAAgB,cAAc;AAAA,QAC5B,KAAK,MAAM,MAAM;AAAA,QACjB,YAAY;AAAA,MACd,CAAC;AAAA,IACH;AAAA,IACA,cAAc,CAAC,SAAS,OAAO,QAAQ;AACrC,UAAI,WAAW;AACf,UAAI;AACF,cAAM,QAAQ,GAAG;AACjB,mBAAW;AACX,eAAO,MAAM,KAAK,GAAG;AAAA,MACvB,UAAE;AACA,YAAI,UAAU;AACZ,kBAAQ;AAAA,QACV;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|