feat(fetch): plugin system + eslint/tsconfig migration

- Add fetch plugin API (definePlugin, plugins) with type-level option flow.
- Migrate to eslint flat config and composite tsconfig.
This commit is contained in:
2026-06-07 16:29:18 +07:00
parent 96f4cba4a8
commit a7e668ced8
19 changed files with 1759 additions and 233 deletions
+54
View File
@@ -0,0 +1,54 @@
import { definePlugin } from '../plugin';
/**
* Caller's original `signal`, captured once per request so each retry attempt
* recombines a *fresh* timeout signal with it instead of reusing an already
* aborted one. Keyed on the FetchContext to keep its hidden class stable.
*/
const baseSignals = new WeakMap<object, AbortSignal | undefined>();
/**
* @name timeoutPlugin
* @category Fetch
* @description Composes an `AbortSignal.timeout(ms)` with any caller-supplied signal
* when `options.timeout` is set.
*
* Implemented as an `execute` middleware (inner to `retry`) so every retry attempt
* gets a brand-new timeout signal — a single timeout no longer poisons all
* subsequent attempts. The timeout therefore applies per attempt, not to the whole
* retry sequence.
*
* Auto-registered by `createFetch`; no-op when `timeout` is unset.
*
* @since 0.1.0
*/
export function timeoutPlugin() {
return definePlugin({
name: 'timeout',
execute: async (context, next) => {
const options = context.options;
const timeout = options.timeout;
if (timeout === undefined) {
await next();
return;
}
// Fix the caller's signal once; reuse it across retry attempts.
let base: AbortSignal | undefined;
if (baseSignals.has(context)) {
base = baseSignals.get(context);
}
else {
base = options.signal as AbortSignal | undefined;
baseSignals.set(context, base);
}
const timeoutSignal = AbortSignal.timeout(timeout);
options.signal = base === undefined
? timeoutSignal
: AbortSignal.any([timeoutSignal, base]);
await next();
},
});
}