@routar/core 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +147 -103
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -118
- package/dist/index.d.ts +86 -118
- package/dist/index.js +146 -100
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
CHANGED
|
@@ -6,7 +6,7 @@ interface ExecuteOptions {
|
|
|
6
6
|
params?: Record<string, unknown>;
|
|
7
7
|
body?: unknown;
|
|
8
8
|
/**
|
|
9
|
-
* Per-request headers injected by
|
|
9
|
+
* Per-request headers injected by a plugin's `onRequest` hook.
|
|
10
10
|
* Headers cannot be set from `createApi` call sites directly — use middleware
|
|
11
11
|
* to add dynamic headers such as `Authorization` or `X-Request-Id`.
|
|
12
12
|
*/
|
|
@@ -22,21 +22,51 @@ interface Executor {
|
|
|
22
22
|
execute(options: ExecuteOptions): Promise<unknown>;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* A named, composable unit of executor behavior.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Each lifecycle hook is optional — implement only what you need.
|
|
28
|
+
* Related concerns (e.g. auth header injection + 401 refresh) can be
|
|
29
|
+
* bundled into a single plugin.
|
|
30
30
|
*
|
|
31
|
-
* @example
|
|
31
|
+
* @example Auth plugin
|
|
32
32
|
* ```ts
|
|
33
|
-
* const
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* const authPlugin: ExecutorPlugin = {
|
|
34
|
+
* name: 'auth',
|
|
35
|
+
* onRequest: async (opts) => ({
|
|
36
|
+
* ...opts,
|
|
37
|
+
* headers: { ...opts.headers, Authorization: `Bearer ${await getToken()}` },
|
|
38
|
+
* }),
|
|
39
|
+
* onError: async (err) => {
|
|
40
|
+
* if (isUnauthorized(err)) await refreshToken();
|
|
41
|
+
* throw err;
|
|
42
|
+
* },
|
|
36
43
|
* };
|
|
37
44
|
* ```
|
|
38
45
|
*/
|
|
39
|
-
|
|
46
|
+
interface ExecutorPlugin {
|
|
47
|
+
/** Optional name — used for introspection and `eject`. */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Runs before the request is sent. Return modified opts to transform the request. */
|
|
50
|
+
onRequest?: (opts: ExecuteOptions) => ExecuteOptions | Promise<ExecuteOptions>;
|
|
51
|
+
/** Runs after a successful response. Return a modified value to transform the response. */
|
|
52
|
+
onResponse?: (response: unknown, opts: ExecuteOptions) => unknown | Promise<unknown>;
|
|
53
|
+
/** Runs when the request throws. Must re-throw (or throw a different error). */
|
|
54
|
+
onError?: (error: unknown, opts: ExecuteOptions) => never | Promise<never>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Options for {@link createExecutor}.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const executor = createExecutor(transport, {
|
|
62
|
+
* plugins: [authPlugin, logger()],
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
interface CreateExecutorOptions {
|
|
67
|
+
/** Plugins applied in declaration order (first plugin is outermost). */
|
|
68
|
+
plugins?: ExecutorPlugin[];
|
|
69
|
+
}
|
|
40
70
|
/**
|
|
41
71
|
* Any object with a `parse` method — compatible with Zod, Valibot, Yup, etc.
|
|
42
72
|
*
|
|
@@ -229,55 +259,43 @@ declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executo
|
|
|
229
259
|
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, endpoints: TEndpoints, options?: CreateApiOptions): ApiClient<TEndpoints>;
|
|
230
260
|
|
|
231
261
|
/**
|
|
232
|
-
* Creates an {@link Executor} by wrapping a transport function with
|
|
233
|
-
* optional middleware chain.
|
|
262
|
+
* Creates an {@link Executor} by wrapping a transport function with plugins.
|
|
234
263
|
*
|
|
235
|
-
*
|
|
236
|
-
* outermost wrapper and runs first on each request.
|
|
264
|
+
* Plugins run in declaration order (first plugin is outermost).
|
|
237
265
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
266
|
+
* For `retry` and `timeout`, use the options on {@link createFetchExecutor}
|
|
267
|
+
* or configure them via the underlying HTTP client (axios, ky).
|
|
240
268
|
*
|
|
241
269
|
* @example
|
|
242
270
|
* ```ts
|
|
243
|
-
* const executor = createExecutor(
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* return res.json();
|
|
247
|
-
* },
|
|
248
|
-
* [withTimeout(5000), withRetry(3), withLogger()],
|
|
249
|
-
* );
|
|
271
|
+
* const executor = createExecutor(transport, {
|
|
272
|
+
* plugins: [authPlugin, logger()],
|
|
273
|
+
* });
|
|
250
274
|
* ```
|
|
251
275
|
*/
|
|
252
|
-
declare function createExecutor(execute: (options: ExecuteOptions) => Promise<unknown>,
|
|
276
|
+
declare function createExecutor(execute: (options: ExecuteOptions) => Promise<unknown>, options?: CreateExecutorOptions): Executor;
|
|
253
277
|
/**
|
|
254
278
|
* Creates an {@link Executor} that selects the underlying transport at
|
|
255
279
|
* request time based on the result of `resolver`.
|
|
256
280
|
*
|
|
257
|
-
* Use this to unify SSR and CSR behind a single API client — the resolver
|
|
258
|
-
* picks the right executor per request, so `createApi` is called once and
|
|
259
|
-
* works in both environments without duplicate `*ServerApi` instances.
|
|
260
|
-
*
|
|
261
|
-
* The resolver receives the full {@link ExecuteOptions} so it can branch on
|
|
262
|
-
* environment, URL prefix, auth context, or any runtime condition.
|
|
263
|
-
*
|
|
264
|
-
* @param resolver - Called on every request; returns the executor to delegate to.
|
|
265
|
-
*
|
|
266
281
|
* @example
|
|
267
282
|
* ```ts
|
|
268
|
-
* // SSR vs CSR — pick transport based on environment
|
|
269
283
|
* const apiExecutor = dispatchExecutor(() =>
|
|
270
284
|
* typeof window === 'undefined' ? serverExecutor : clientExecutor,
|
|
271
285
|
* );
|
|
272
|
-
*
|
|
273
|
-
* // Route by URL prefix — internal routes use a different transport
|
|
274
|
-
* const apiExecutor = dispatchExecutor((opts) =>
|
|
275
|
-
* opts.url.startsWith('/internal') ? internalExecutor : publicExecutor,
|
|
276
|
-
* );
|
|
277
286
|
* ```
|
|
278
287
|
*/
|
|
279
288
|
declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor): Executor;
|
|
280
289
|
|
|
290
|
+
type FetchRetryOption = number | {
|
|
291
|
+
count: number;
|
|
292
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
293
|
+
};
|
|
294
|
+
interface FetchExecutorOptions extends CreateExecutorOptions {
|
|
295
|
+
defaultHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
296
|
+
retry?: FetchRetryOption;
|
|
297
|
+
timeout?: number;
|
|
298
|
+
}
|
|
281
299
|
/**
|
|
282
300
|
* Creates an {@link Executor} backed by the browser / Node.js `fetch` API.
|
|
283
301
|
*
|
|
@@ -293,7 +311,13 @@ declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor):
|
|
|
293
311
|
* @param baseURL - Absolute base URL prepended to every endpoint path.
|
|
294
312
|
* @param options.defaultHeaders - Async factory called on every request to
|
|
295
313
|
* produce headers (e.g. reading cookies in a Next.js server component).
|
|
296
|
-
* @param options.
|
|
314
|
+
* @param options.plugins - Plugins applied around the fetch call. Each retry
|
|
315
|
+
* attempt re-runs `onRequest` hooks (so headers are refreshed per attempt)
|
|
316
|
+
* and `onError` hooks. Token-refresh-on-401 patterns work by updating an
|
|
317
|
+
* external token store in `onError` and letting `onRequest` pick it up on
|
|
318
|
+
* the next attempt.
|
|
319
|
+
* @param options.retry - Number of retries, or `{ count, shouldRetry? }`.
|
|
320
|
+
* @param options.timeout - Per-attempt timeout in milliseconds.
|
|
297
321
|
*
|
|
298
322
|
* @example Minimal — no options needed
|
|
299
323
|
* ```ts
|
|
@@ -310,22 +334,15 @@ declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor):
|
|
|
310
334
|
* });
|
|
311
335
|
* ```
|
|
312
336
|
*
|
|
313
|
-
* @example
|
|
337
|
+
* @example With retry and timeout
|
|
314
338
|
* ```ts
|
|
315
339
|
* const executor = createFetchExecutor('https://api.example.com', {
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* const token = (await cookies()).get('access_token')?.value;
|
|
319
|
-
* return token ? { Authorization: `Bearer ${token}` } : {};
|
|
320
|
-
* },
|
|
321
|
-
* middlewares: [withTimeout(8_000), withRetry(2)],
|
|
340
|
+
* retry: 2,
|
|
341
|
+
* timeout: 8_000,
|
|
322
342
|
* });
|
|
323
343
|
* ```
|
|
324
344
|
*/
|
|
325
|
-
declare function createFetchExecutor(baseURL: string, options?:
|
|
326
|
-
defaultHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
327
|
-
middlewares?: ExecutorMiddleware[];
|
|
328
|
-
}): Executor;
|
|
345
|
+
declare function createFetchExecutor(baseURL: string, options?: FetchExecutorOptions): Executor;
|
|
329
346
|
/**
|
|
330
347
|
* Thrown by {@link createFetchExecutor} when the server returns a non-2xx
|
|
331
348
|
* status code.
|
|
@@ -342,19 +359,10 @@ declare function createFetchExecutor(baseURL: string, options?: {
|
|
|
342
359
|
* ```
|
|
343
360
|
*/
|
|
344
361
|
declare class HttpError extends Error {
|
|
345
|
-
/** HTTP status code (e.g. 404, 500). */
|
|
346
362
|
readonly status: number;
|
|
347
|
-
/** HTTP status text (e.g. "Not Found"). */
|
|
348
363
|
readonly statusText: string;
|
|
349
|
-
/** Parsed response body, or `null` if the body was empty or not JSON. */
|
|
350
364
|
readonly body: unknown;
|
|
351
|
-
constructor(
|
|
352
|
-
/** HTTP status code (e.g. 404, 500). */
|
|
353
|
-
status: number,
|
|
354
|
-
/** HTTP status text (e.g. "Not Found"). */
|
|
355
|
-
statusText: string,
|
|
356
|
-
/** Parsed response body, or `null` if the body was empty or not JSON. */
|
|
357
|
-
body?: unknown);
|
|
365
|
+
constructor(status: number, statusText: string, body?: unknown);
|
|
358
366
|
}
|
|
359
367
|
|
|
360
368
|
/**
|
|
@@ -522,71 +530,29 @@ declare function isRouterDef(entry: object): entry is RouterDef<RouterEndpoints>
|
|
|
522
530
|
declare function defineRouter<TEndpoints extends RouterEndpoints>(prefix: string, endpoints: TEndpoints): RouterDef<TEndpoints>;
|
|
523
531
|
|
|
524
532
|
/**
|
|
525
|
-
* Thrown by
|
|
526
|
-
* Distinguishable from a user-initiated
|
|
533
|
+
* Thrown by the built-in `timeout` option when a request exceeds the
|
|
534
|
+
* configured duration. Distinguishable from a user-initiated
|
|
535
|
+
* {@link AbortSignal} cancellation.
|
|
527
536
|
*/
|
|
528
537
|
declare class TimeoutError extends Error {
|
|
529
538
|
readonly ms: number;
|
|
530
539
|
constructor(ms: number);
|
|
531
540
|
}
|
|
532
541
|
/**
|
|
533
|
-
* Identity helper that returns the
|
|
534
|
-
*
|
|
535
|
-
* Wrap your middleware function with this to get full type inference on `opts`
|
|
536
|
-
* and `next` without having to annotate the type manually.
|
|
537
|
-
*
|
|
538
|
-
* @example
|
|
539
|
-
* ```ts
|
|
540
|
-
* const withCorrelationId = defineMiddleware((opts, next) =>
|
|
541
|
-
* next({ ...opts, headers: { ...opts.headers, 'X-Request-Id': crypto.randomUUID() } })
|
|
542
|
-
* );
|
|
543
|
-
* ```
|
|
544
|
-
*/
|
|
545
|
-
declare function defineMiddleware(fn: ExecutorMiddleware): ExecutorMiddleware;
|
|
546
|
-
/**
|
|
547
|
-
* Retries a failed request up to `count` additional times.
|
|
548
|
-
*
|
|
549
|
-
* By default all errors trigger a retry. Pass `shouldRetry` to skip retries
|
|
550
|
-
* for non-transient errors (e.g. 4xx responses).
|
|
551
|
-
*
|
|
552
|
-
* @param count - Number of retries (not counting the initial attempt).
|
|
553
|
-
* @param options.shouldRetry - Return `false` to stop retrying early.
|
|
554
|
-
* Receives the error and a zero-based `attempt` index (0 = first failure,
|
|
555
|
-
* 1 = second failure, …) so you can limit retries by count or error type.
|
|
556
|
-
*
|
|
557
|
-
* @example
|
|
558
|
-
* ```ts
|
|
559
|
-
* withRetry(3, {
|
|
560
|
-
* shouldRetry: (err) => err instanceof HttpError && err.status >= 500,
|
|
561
|
-
* })
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
|
-
declare function withRetry(count: number, options?: {
|
|
565
|
-
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
566
|
-
}): ExecutorMiddleware;
|
|
567
|
-
/**
|
|
568
|
-
* Aborts a request if it does not complete within `ms` milliseconds.
|
|
569
|
-
*
|
|
570
|
-
* Merges the timeout signal with any existing `AbortSignal` on the request,
|
|
571
|
-
* so whichever fires first wins.
|
|
572
|
-
*
|
|
573
|
-
* @param ms - Timeout in milliseconds.
|
|
542
|
+
* Identity helper that returns the plugin as-is, providing full type inference.
|
|
574
543
|
*
|
|
575
544
|
* @example
|
|
576
545
|
* ```ts
|
|
577
|
-
* const
|
|
578
|
-
*
|
|
546
|
+
* const authPlugin = definePlugin({
|
|
547
|
+
* name: 'auth',
|
|
548
|
+
* onRequest: async (opts) => ({
|
|
549
|
+
* ...opts,
|
|
550
|
+
* headers: { ...opts.headers, Authorization: `Bearer ${await getToken()}` },
|
|
551
|
+
* }),
|
|
579
552
|
* });
|
|
580
|
-
*
|
|
581
|
-
* // Combine with retry — timeout applies per attempt
|
|
582
|
-
* const executor = createExecutor(transport, [
|
|
583
|
-
* withTimeout(5_000),
|
|
584
|
-
* withRetry(3, { shouldRetry: (err) => !(err instanceof HttpError && err.status < 500) }),
|
|
585
|
-
* withLogger(),
|
|
586
|
-
* ]);
|
|
587
553
|
* ```
|
|
588
554
|
*/
|
|
589
|
-
declare function
|
|
555
|
+
declare function definePlugin(plugin: ExecutorPlugin): ExecutorPlugin;
|
|
590
556
|
/**
|
|
591
557
|
* Logs each request and its outcome (success duration or error).
|
|
592
558
|
*
|
|
@@ -594,12 +560,14 @@ declare function withTimeout(ms: number): ExecutorMiddleware;
|
|
|
594
560
|
*
|
|
595
561
|
* @example
|
|
596
562
|
* ```ts
|
|
597
|
-
*
|
|
563
|
+
* createExecutor(transport, {
|
|
564
|
+
* plugins: [logger({ log: (msg, data) => myLogger.debug(msg, data) })],
|
|
565
|
+
* })
|
|
598
566
|
* ```
|
|
599
567
|
*/
|
|
600
|
-
declare function
|
|
568
|
+
declare function logger(options?: {
|
|
601
569
|
log?: (message: string, data?: unknown) => void;
|
|
602
|
-
}):
|
|
570
|
+
}): ExecutorPlugin;
|
|
603
571
|
|
|
604
572
|
declare function serializeParams(params: Record<string, unknown>): URLSearchParams;
|
|
605
573
|
|
|
@@ -618,4 +586,4 @@ declare class ValidationError extends Error {
|
|
|
618
586
|
constructor(message: string, cause?: unknown);
|
|
619
587
|
}
|
|
620
588
|
|
|
621
|
-
export { type ApiTypes, type CreateApiOptions, type EndpointSpec, type ExecuteOptions, type Executor, type
|
|
589
|
+
export { type ApiTypes, type CreateApiOptions, type CreateExecutorOptions, type EndpointSpec, type ExecuteOptions, type Executor, type ExecutorPlugin, type FetchExecutorOptions, type FetchRetryOption, HttpError, type HttpMethod, type InferResponse, type PathParams, type RequestShape, type RouterDef, type RouterEndpoints, type RouterEntry, TimeoutError, ValidationError, type Validator, type ValidatorOutput, createApi, createExecutor, createFetchExecutor, definePlugin, defineRouter, dispatchExecutor, endpoint, isRouterDef, joinPaths, logger, resolvePath, serializeParams };
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ interface ExecuteOptions {
|
|
|
6
6
|
params?: Record<string, unknown>;
|
|
7
7
|
body?: unknown;
|
|
8
8
|
/**
|
|
9
|
-
* Per-request headers injected by
|
|
9
|
+
* Per-request headers injected by a plugin's `onRequest` hook.
|
|
10
10
|
* Headers cannot be set from `createApi` call sites directly — use middleware
|
|
11
11
|
* to add dynamic headers such as `Authorization` or `X-Request-Id`.
|
|
12
12
|
*/
|
|
@@ -22,21 +22,51 @@ interface Executor {
|
|
|
22
22
|
execute(options: ExecuteOptions): Promise<unknown>;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* A named, composable unit of executor behavior.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Each lifecycle hook is optional — implement only what you need.
|
|
28
|
+
* Related concerns (e.g. auth header injection + 401 refresh) can be
|
|
29
|
+
* bundled into a single plugin.
|
|
30
30
|
*
|
|
31
|
-
* @example
|
|
31
|
+
* @example Auth plugin
|
|
32
32
|
* ```ts
|
|
33
|
-
* const
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* const authPlugin: ExecutorPlugin = {
|
|
34
|
+
* name: 'auth',
|
|
35
|
+
* onRequest: async (opts) => ({
|
|
36
|
+
* ...opts,
|
|
37
|
+
* headers: { ...opts.headers, Authorization: `Bearer ${await getToken()}` },
|
|
38
|
+
* }),
|
|
39
|
+
* onError: async (err) => {
|
|
40
|
+
* if (isUnauthorized(err)) await refreshToken();
|
|
41
|
+
* throw err;
|
|
42
|
+
* },
|
|
36
43
|
* };
|
|
37
44
|
* ```
|
|
38
45
|
*/
|
|
39
|
-
|
|
46
|
+
interface ExecutorPlugin {
|
|
47
|
+
/** Optional name — used for introspection and `eject`. */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Runs before the request is sent. Return modified opts to transform the request. */
|
|
50
|
+
onRequest?: (opts: ExecuteOptions) => ExecuteOptions | Promise<ExecuteOptions>;
|
|
51
|
+
/** Runs after a successful response. Return a modified value to transform the response. */
|
|
52
|
+
onResponse?: (response: unknown, opts: ExecuteOptions) => unknown | Promise<unknown>;
|
|
53
|
+
/** Runs when the request throws. Must re-throw (or throw a different error). */
|
|
54
|
+
onError?: (error: unknown, opts: ExecuteOptions) => never | Promise<never>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Options for {@link createExecutor}.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* const executor = createExecutor(transport, {
|
|
62
|
+
* plugins: [authPlugin, logger()],
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
interface CreateExecutorOptions {
|
|
67
|
+
/** Plugins applied in declaration order (first plugin is outermost). */
|
|
68
|
+
plugins?: ExecutorPlugin[];
|
|
69
|
+
}
|
|
40
70
|
/**
|
|
41
71
|
* Any object with a `parse` method — compatible with Zod, Valibot, Yup, etc.
|
|
42
72
|
*
|
|
@@ -229,55 +259,43 @@ declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executo
|
|
|
229
259
|
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, endpoints: TEndpoints, options?: CreateApiOptions): ApiClient<TEndpoints>;
|
|
230
260
|
|
|
231
261
|
/**
|
|
232
|
-
* Creates an {@link Executor} by wrapping a transport function with
|
|
233
|
-
* optional middleware chain.
|
|
262
|
+
* Creates an {@link Executor} by wrapping a transport function with plugins.
|
|
234
263
|
*
|
|
235
|
-
*
|
|
236
|
-
* outermost wrapper and runs first on each request.
|
|
264
|
+
* Plugins run in declaration order (first plugin is outermost).
|
|
237
265
|
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
266
|
+
* For `retry` and `timeout`, use the options on {@link createFetchExecutor}
|
|
267
|
+
* or configure them via the underlying HTTP client (axios, ky).
|
|
240
268
|
*
|
|
241
269
|
* @example
|
|
242
270
|
* ```ts
|
|
243
|
-
* const executor = createExecutor(
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
* return res.json();
|
|
247
|
-
* },
|
|
248
|
-
* [withTimeout(5000), withRetry(3), withLogger()],
|
|
249
|
-
* );
|
|
271
|
+
* const executor = createExecutor(transport, {
|
|
272
|
+
* plugins: [authPlugin, logger()],
|
|
273
|
+
* });
|
|
250
274
|
* ```
|
|
251
275
|
*/
|
|
252
|
-
declare function createExecutor(execute: (options: ExecuteOptions) => Promise<unknown>,
|
|
276
|
+
declare function createExecutor(execute: (options: ExecuteOptions) => Promise<unknown>, options?: CreateExecutorOptions): Executor;
|
|
253
277
|
/**
|
|
254
278
|
* Creates an {@link Executor} that selects the underlying transport at
|
|
255
279
|
* request time based on the result of `resolver`.
|
|
256
280
|
*
|
|
257
|
-
* Use this to unify SSR and CSR behind a single API client — the resolver
|
|
258
|
-
* picks the right executor per request, so `createApi` is called once and
|
|
259
|
-
* works in both environments without duplicate `*ServerApi` instances.
|
|
260
|
-
*
|
|
261
|
-
* The resolver receives the full {@link ExecuteOptions} so it can branch on
|
|
262
|
-
* environment, URL prefix, auth context, or any runtime condition.
|
|
263
|
-
*
|
|
264
|
-
* @param resolver - Called on every request; returns the executor to delegate to.
|
|
265
|
-
*
|
|
266
281
|
* @example
|
|
267
282
|
* ```ts
|
|
268
|
-
* // SSR vs CSR — pick transport based on environment
|
|
269
283
|
* const apiExecutor = dispatchExecutor(() =>
|
|
270
284
|
* typeof window === 'undefined' ? serverExecutor : clientExecutor,
|
|
271
285
|
* );
|
|
272
|
-
*
|
|
273
|
-
* // Route by URL prefix — internal routes use a different transport
|
|
274
|
-
* const apiExecutor = dispatchExecutor((opts) =>
|
|
275
|
-
* opts.url.startsWith('/internal') ? internalExecutor : publicExecutor,
|
|
276
|
-
* );
|
|
277
286
|
* ```
|
|
278
287
|
*/
|
|
279
288
|
declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor): Executor;
|
|
280
289
|
|
|
290
|
+
type FetchRetryOption = number | {
|
|
291
|
+
count: number;
|
|
292
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
293
|
+
};
|
|
294
|
+
interface FetchExecutorOptions extends CreateExecutorOptions {
|
|
295
|
+
defaultHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
296
|
+
retry?: FetchRetryOption;
|
|
297
|
+
timeout?: number;
|
|
298
|
+
}
|
|
281
299
|
/**
|
|
282
300
|
* Creates an {@link Executor} backed by the browser / Node.js `fetch` API.
|
|
283
301
|
*
|
|
@@ -293,7 +311,13 @@ declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor):
|
|
|
293
311
|
* @param baseURL - Absolute base URL prepended to every endpoint path.
|
|
294
312
|
* @param options.defaultHeaders - Async factory called on every request to
|
|
295
313
|
* produce headers (e.g. reading cookies in a Next.js server component).
|
|
296
|
-
* @param options.
|
|
314
|
+
* @param options.plugins - Plugins applied around the fetch call. Each retry
|
|
315
|
+
* attempt re-runs `onRequest` hooks (so headers are refreshed per attempt)
|
|
316
|
+
* and `onError` hooks. Token-refresh-on-401 patterns work by updating an
|
|
317
|
+
* external token store in `onError` and letting `onRequest` pick it up on
|
|
318
|
+
* the next attempt.
|
|
319
|
+
* @param options.retry - Number of retries, or `{ count, shouldRetry? }`.
|
|
320
|
+
* @param options.timeout - Per-attempt timeout in milliseconds.
|
|
297
321
|
*
|
|
298
322
|
* @example Minimal — no options needed
|
|
299
323
|
* ```ts
|
|
@@ -310,22 +334,15 @@ declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor):
|
|
|
310
334
|
* });
|
|
311
335
|
* ```
|
|
312
336
|
*
|
|
313
|
-
* @example
|
|
337
|
+
* @example With retry and timeout
|
|
314
338
|
* ```ts
|
|
315
339
|
* const executor = createFetchExecutor('https://api.example.com', {
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* const token = (await cookies()).get('access_token')?.value;
|
|
319
|
-
* return token ? { Authorization: `Bearer ${token}` } : {};
|
|
320
|
-
* },
|
|
321
|
-
* middlewares: [withTimeout(8_000), withRetry(2)],
|
|
340
|
+
* retry: 2,
|
|
341
|
+
* timeout: 8_000,
|
|
322
342
|
* });
|
|
323
343
|
* ```
|
|
324
344
|
*/
|
|
325
|
-
declare function createFetchExecutor(baseURL: string, options?:
|
|
326
|
-
defaultHeaders?: () => Record<string, string> | Promise<Record<string, string>>;
|
|
327
|
-
middlewares?: ExecutorMiddleware[];
|
|
328
|
-
}): Executor;
|
|
345
|
+
declare function createFetchExecutor(baseURL: string, options?: FetchExecutorOptions): Executor;
|
|
329
346
|
/**
|
|
330
347
|
* Thrown by {@link createFetchExecutor} when the server returns a non-2xx
|
|
331
348
|
* status code.
|
|
@@ -342,19 +359,10 @@ declare function createFetchExecutor(baseURL: string, options?: {
|
|
|
342
359
|
* ```
|
|
343
360
|
*/
|
|
344
361
|
declare class HttpError extends Error {
|
|
345
|
-
/** HTTP status code (e.g. 404, 500). */
|
|
346
362
|
readonly status: number;
|
|
347
|
-
/** HTTP status text (e.g. "Not Found"). */
|
|
348
363
|
readonly statusText: string;
|
|
349
|
-
/** Parsed response body, or `null` if the body was empty or not JSON. */
|
|
350
364
|
readonly body: unknown;
|
|
351
|
-
constructor(
|
|
352
|
-
/** HTTP status code (e.g. 404, 500). */
|
|
353
|
-
status: number,
|
|
354
|
-
/** HTTP status text (e.g. "Not Found"). */
|
|
355
|
-
statusText: string,
|
|
356
|
-
/** Parsed response body, or `null` if the body was empty or not JSON. */
|
|
357
|
-
body?: unknown);
|
|
365
|
+
constructor(status: number, statusText: string, body?: unknown);
|
|
358
366
|
}
|
|
359
367
|
|
|
360
368
|
/**
|
|
@@ -522,71 +530,29 @@ declare function isRouterDef(entry: object): entry is RouterDef<RouterEndpoints>
|
|
|
522
530
|
declare function defineRouter<TEndpoints extends RouterEndpoints>(prefix: string, endpoints: TEndpoints): RouterDef<TEndpoints>;
|
|
523
531
|
|
|
524
532
|
/**
|
|
525
|
-
* Thrown by
|
|
526
|
-
* Distinguishable from a user-initiated
|
|
533
|
+
* Thrown by the built-in `timeout` option when a request exceeds the
|
|
534
|
+
* configured duration. Distinguishable from a user-initiated
|
|
535
|
+
* {@link AbortSignal} cancellation.
|
|
527
536
|
*/
|
|
528
537
|
declare class TimeoutError extends Error {
|
|
529
538
|
readonly ms: number;
|
|
530
539
|
constructor(ms: number);
|
|
531
540
|
}
|
|
532
541
|
/**
|
|
533
|
-
* Identity helper that returns the
|
|
534
|
-
*
|
|
535
|
-
* Wrap your middleware function with this to get full type inference on `opts`
|
|
536
|
-
* and `next` without having to annotate the type manually.
|
|
537
|
-
*
|
|
538
|
-
* @example
|
|
539
|
-
* ```ts
|
|
540
|
-
* const withCorrelationId = defineMiddleware((opts, next) =>
|
|
541
|
-
* next({ ...opts, headers: { ...opts.headers, 'X-Request-Id': crypto.randomUUID() } })
|
|
542
|
-
* );
|
|
543
|
-
* ```
|
|
544
|
-
*/
|
|
545
|
-
declare function defineMiddleware(fn: ExecutorMiddleware): ExecutorMiddleware;
|
|
546
|
-
/**
|
|
547
|
-
* Retries a failed request up to `count` additional times.
|
|
548
|
-
*
|
|
549
|
-
* By default all errors trigger a retry. Pass `shouldRetry` to skip retries
|
|
550
|
-
* for non-transient errors (e.g. 4xx responses).
|
|
551
|
-
*
|
|
552
|
-
* @param count - Number of retries (not counting the initial attempt).
|
|
553
|
-
* @param options.shouldRetry - Return `false` to stop retrying early.
|
|
554
|
-
* Receives the error and a zero-based `attempt` index (0 = first failure,
|
|
555
|
-
* 1 = second failure, …) so you can limit retries by count or error type.
|
|
556
|
-
*
|
|
557
|
-
* @example
|
|
558
|
-
* ```ts
|
|
559
|
-
* withRetry(3, {
|
|
560
|
-
* shouldRetry: (err) => err instanceof HttpError && err.status >= 500,
|
|
561
|
-
* })
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
|
-
declare function withRetry(count: number, options?: {
|
|
565
|
-
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
566
|
-
}): ExecutorMiddleware;
|
|
567
|
-
/**
|
|
568
|
-
* Aborts a request if it does not complete within `ms` milliseconds.
|
|
569
|
-
*
|
|
570
|
-
* Merges the timeout signal with any existing `AbortSignal` on the request,
|
|
571
|
-
* so whichever fires first wins.
|
|
572
|
-
*
|
|
573
|
-
* @param ms - Timeout in milliseconds.
|
|
542
|
+
* Identity helper that returns the plugin as-is, providing full type inference.
|
|
574
543
|
*
|
|
575
544
|
* @example
|
|
576
545
|
* ```ts
|
|
577
|
-
* const
|
|
578
|
-
*
|
|
546
|
+
* const authPlugin = definePlugin({
|
|
547
|
+
* name: 'auth',
|
|
548
|
+
* onRequest: async (opts) => ({
|
|
549
|
+
* ...opts,
|
|
550
|
+
* headers: { ...opts.headers, Authorization: `Bearer ${await getToken()}` },
|
|
551
|
+
* }),
|
|
579
552
|
* });
|
|
580
|
-
*
|
|
581
|
-
* // Combine with retry — timeout applies per attempt
|
|
582
|
-
* const executor = createExecutor(transport, [
|
|
583
|
-
* withTimeout(5_000),
|
|
584
|
-
* withRetry(3, { shouldRetry: (err) => !(err instanceof HttpError && err.status < 500) }),
|
|
585
|
-
* withLogger(),
|
|
586
|
-
* ]);
|
|
587
553
|
* ```
|
|
588
554
|
*/
|
|
589
|
-
declare function
|
|
555
|
+
declare function definePlugin(plugin: ExecutorPlugin): ExecutorPlugin;
|
|
590
556
|
/**
|
|
591
557
|
* Logs each request and its outcome (success duration or error).
|
|
592
558
|
*
|
|
@@ -594,12 +560,14 @@ declare function withTimeout(ms: number): ExecutorMiddleware;
|
|
|
594
560
|
*
|
|
595
561
|
* @example
|
|
596
562
|
* ```ts
|
|
597
|
-
*
|
|
563
|
+
* createExecutor(transport, {
|
|
564
|
+
* plugins: [logger({ log: (msg, data) => myLogger.debug(msg, data) })],
|
|
565
|
+
* })
|
|
598
566
|
* ```
|
|
599
567
|
*/
|
|
600
|
-
declare function
|
|
568
|
+
declare function logger(options?: {
|
|
601
569
|
log?: (message: string, data?: unknown) => void;
|
|
602
|
-
}):
|
|
570
|
+
}): ExecutorPlugin;
|
|
603
571
|
|
|
604
572
|
declare function serializeParams(params: Record<string, unknown>): URLSearchParams;
|
|
605
573
|
|
|
@@ -618,4 +586,4 @@ declare class ValidationError extends Error {
|
|
|
618
586
|
constructor(message: string, cause?: unknown);
|
|
619
587
|
}
|
|
620
588
|
|
|
621
|
-
export { type ApiTypes, type CreateApiOptions, type EndpointSpec, type ExecuteOptions, type Executor, type
|
|
589
|
+
export { type ApiTypes, type CreateApiOptions, type CreateExecutorOptions, type EndpointSpec, type ExecuteOptions, type Executor, type ExecutorPlugin, type FetchExecutorOptions, type FetchRetryOption, HttpError, type HttpMethod, type InferResponse, type PathParams, type RequestShape, type RouterDef, type RouterEndpoints, type RouterEntry, TimeoutError, ValidationError, type Validator, type ValidatorOutput, createApi, createExecutor, createFetchExecutor, definePlugin, defineRouter, dispatchExecutor, endpoint, isRouterDef, joinPaths, logger, resolvePath, serializeParams };
|