@routar/core 0.1.0 → 1.1.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 +1 -1
- package/dist/index.cjs +124 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +155 -101
- package/dist/index.d.ts +155 -101
- package/dist/index.js +123 -75
- package/dist/index.js.map +1 -1
- package/package.json +21 -4
package/dist/index.d.cts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
type HttpMethod =
|
|
1
|
+
type HttpMethod = "GET" | "POST" | "PATCH" | "PUT" | "DELETE";
|
|
2
2
|
/** Options passed to {@link Executor.execute} on every HTTP call. */
|
|
3
3
|
interface ExecuteOptions {
|
|
4
4
|
method: HttpMethod;
|
|
5
5
|
url: string;
|
|
6
6
|
params?: Record<string, unknown>;
|
|
7
7
|
body?: unknown;
|
|
8
|
+
/**
|
|
9
|
+
* Per-request headers injected by middleware (e.g. `defineMiddleware`).
|
|
10
|
+
* Headers cannot be set from `createApi` call sites directly — use middleware
|
|
11
|
+
* to add dynamic headers such as `Authorization` or `X-Request-Id`.
|
|
12
|
+
*/
|
|
8
13
|
headers?: Record<string, string>;
|
|
9
14
|
signal?: AbortSignal;
|
|
10
15
|
}
|
|
@@ -82,7 +87,7 @@ interface EndpointSpec<TRequest extends RequestShape = RequestShape, TResponse e
|
|
|
82
87
|
* - With `adapter`: returns the adapter's output type.
|
|
83
88
|
* - Without `adapter`: returns `ValidatorOutput<TResponse>`.
|
|
84
89
|
*/
|
|
85
|
-
type InferResponse<TSpec extends EndpointSpec<any, any, any>> = TSpec[
|
|
90
|
+
type InferResponse<TSpec extends EndpointSpec<any, any, any>> = TSpec["adapter"] extends (raw: any) => infer R ? R : ValidatorOutput<TSpec["response"]>;
|
|
86
91
|
/**
|
|
87
92
|
* A single entry inside a {@link RouterEndpoints} map.
|
|
88
93
|
* Either a leaf endpoint spec or a nested {@link RouterDef}.
|
|
@@ -97,53 +102,156 @@ interface RouterDef<TEndpoints extends RouterEndpoints = RouterEndpoints> {
|
|
|
97
102
|
}
|
|
98
103
|
/**
|
|
99
104
|
* Extracts request/response types from a typed API client for use in query
|
|
100
|
-
* hooks or mutation handlers.
|
|
105
|
+
* hooks or mutation handlers. Supports nested router clients recursively.
|
|
101
106
|
*
|
|
102
107
|
* @example
|
|
103
108
|
* ```ts
|
|
104
109
|
* export type TodoApiTypes = ApiTypes<typeof todoApi>;
|
|
105
110
|
* type CreateRequest = TodoApiTypes['create']['request'];
|
|
106
111
|
* type CreateResponse = TodoApiTypes['create']['response'];
|
|
112
|
+
*
|
|
113
|
+
* // Nested router: api.users.todos.getList
|
|
114
|
+
* type NestedTypes = ApiTypes<typeof api>;
|
|
115
|
+
* type ListReq = NestedTypes['users']['todos']['getList']['request'];
|
|
107
116
|
* ```
|
|
108
117
|
*/
|
|
109
118
|
type ApiTypes<TApi> = {
|
|
110
119
|
[K in keyof TApi]: TApi[K] extends (...args: any[]) => Promise<infer R> ? {
|
|
111
120
|
request: Parameters<TApi[K]>[0];
|
|
112
121
|
response: R;
|
|
113
|
-
} : never;
|
|
122
|
+
} : TApi[K] extends object ? ApiTypes<TApi[K]> : never;
|
|
114
123
|
};
|
|
124
|
+
/**
|
|
125
|
+
* Options for {@link createApi}.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* // Disable all validation in production
|
|
130
|
+
* createApi(executor, router, { validate: process.env.NODE_ENV !== 'production' });
|
|
131
|
+
*
|
|
132
|
+
* // Keep request validation (catch call-site bugs), skip response in prod
|
|
133
|
+
* createApi(executor, router, { validate: { request: true, response: false } });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
interface CreateApiOptions {
|
|
137
|
+
/**
|
|
138
|
+
* Controls whether request and response schemas are run at call time.
|
|
139
|
+
*
|
|
140
|
+
* - `true` (default) — validate both request and response.
|
|
141
|
+
* - `false` — skip both; raw params and raw response pass through.
|
|
142
|
+
* - `{ request?, response? }` — enable/disable each independently.
|
|
143
|
+
*/
|
|
144
|
+
validate?: boolean | {
|
|
145
|
+
request?: boolean;
|
|
146
|
+
response?: boolean;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
115
149
|
|
|
150
|
+
/** Callable type for a single endpoint on the generated API client. */
|
|
151
|
+
type EndpointFn<TSpec extends EndpointSpec<any, any, any>> = TSpec["request"] extends {
|
|
152
|
+
parse: (data: unknown) => infer R;
|
|
153
|
+
} ? (params: R, signal?: AbortSignal) => Promise<InferResponse<TSpec>> : (params?: RequestShape, signal?: AbortSignal) => Promise<InferResponse<TSpec>>;
|
|
116
154
|
/**
|
|
117
|
-
*
|
|
118
|
-
*
|
|
155
|
+
* Fully-typed API client produced by {@link createApi}.
|
|
156
|
+
* Nested {@link RouterDef} entries become nested sub-client objects.
|
|
157
|
+
*/
|
|
158
|
+
type ApiClient<TEndpoints extends RouterEndpoints> = {
|
|
159
|
+
[K in keyof TEndpoints]: TEndpoints[K] extends RouterDef<infer TNestedEndpoints> ? ApiClient<TNestedEndpoints> : TEndpoints[K] extends EndpointSpec<any, any, any> ? EndpointFn<TEndpoints[K]> : never;
|
|
160
|
+
};
|
|
161
|
+
/**
|
|
162
|
+
* Builds a fully-typed API client from an {@link Executor} and a router
|
|
163
|
+
* (or bare endpoint map).
|
|
119
164
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
165
|
+
* Three call signatures are supported:
|
|
166
|
+
* - `createApi(executor, router)` — preferred; pass the result of {@link defineRouter}.
|
|
167
|
+
* - `createApi(executor, prefix, endpoints)` — inline router without {@link defineRouter}.
|
|
168
|
+
* - `createApi(executor, endpoints)` — no prefix; useful for flat endpoint maps.
|
|
123
169
|
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
170
|
+
* Each key in `endpoints` becomes a typed async function on the returned client.
|
|
171
|
+
* The function validates the request with `spec.request.parse` (if present),
|
|
172
|
+
* resolves path parameters, calls the executor, validates the response with
|
|
173
|
+
* `spec.response.parse`, and applies `spec.adapter` (if present).
|
|
174
|
+
*
|
|
175
|
+
* @param executor - Transport to use for every HTTP call.
|
|
176
|
+
* @param router - A {@link RouterDef} produced by {@link defineRouter}.
|
|
177
|
+
* @param options - Optional settings (e.g. `validate` to skip schema parsing in production).
|
|
126
178
|
*
|
|
127
179
|
* @example
|
|
128
180
|
* ```ts
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema }),
|
|
133
|
-
* create: endpoint({ method: 'POST', path: '/', response: TodoSchema }),
|
|
134
|
-
* });
|
|
181
|
+
* const todoApi = createApi(executor, todoRouter);
|
|
182
|
+
* const todos = await todoApi.getList({});
|
|
183
|
+
* const todo = await todoApi.getDetail({ path: { id: 1 } });
|
|
135
184
|
*
|
|
136
|
-
* //
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* todos: defineRouter('/todos', {
|
|
140
|
-
* getList: endpoint({ method: 'GET', path: '/', response: TodoListSchema }),
|
|
141
|
-
* getDetail: endpoint({ method: 'GET', path: '/:id', response: TodoSchema }),
|
|
142
|
-
* }),
|
|
185
|
+
* // Skip response validation in production
|
|
186
|
+
* const prodApi = createApi(executor, todoRouter, {
|
|
187
|
+
* validate: { request: true, response: process.env.NODE_ENV !== 'production' },
|
|
143
188
|
* });
|
|
144
189
|
* ```
|
|
145
190
|
*/
|
|
146
|
-
declare function
|
|
191
|
+
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, router: RouterDef<TEndpoints>, options?: CreateApiOptions): ApiClient<TEndpoints>;
|
|
192
|
+
/**
|
|
193
|
+
* @param executor - Transport to use for every HTTP call.
|
|
194
|
+
* @param prefix - URL prefix prepended to every endpoint path.
|
|
195
|
+
* @param endpoints - Record of named endpoint specs.
|
|
196
|
+
* @param options - Optional settings.
|
|
197
|
+
*/
|
|
198
|
+
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, prefix: string, endpoints: TEndpoints, options?: CreateApiOptions): ApiClient<TEndpoints>;
|
|
199
|
+
/**
|
|
200
|
+
* @param executor - Transport to use for every HTTP call.
|
|
201
|
+
* @param endpoints - Record of named endpoint specs (no URL prefix).
|
|
202
|
+
* @param options - Optional settings.
|
|
203
|
+
*/
|
|
204
|
+
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, endpoints: TEndpoints, options?: CreateApiOptions): ApiClient<TEndpoints>;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Creates an {@link Executor} by wrapping a transport function with an
|
|
208
|
+
* optional middleware chain.
|
|
209
|
+
*
|
|
210
|
+
* Middlewares are applied in declaration order — the first middleware is the
|
|
211
|
+
* outermost wrapper and runs first on each request.
|
|
212
|
+
*
|
|
213
|
+
* @param execute - The underlying transport function (fetch, axios, etc.).
|
|
214
|
+
* @param middlewares - Ordered list of middlewares to apply.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const executor = createExecutor(
|
|
219
|
+
* async ({ method, url, body }) => {
|
|
220
|
+
* const res = await fetch(url, { method, body: JSON.stringify(body) });
|
|
221
|
+
* return res.json();
|
|
222
|
+
* },
|
|
223
|
+
* [withTimeout(5000), withRetry(3), withLogger()],
|
|
224
|
+
* );
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
declare function createExecutor(execute: (options: ExecuteOptions) => Promise<unknown>, middlewares?: ExecutorMiddleware[]): Executor;
|
|
228
|
+
/**
|
|
229
|
+
* Creates an {@link Executor} that selects the underlying transport at
|
|
230
|
+
* request time based on the result of `resolver`.
|
|
231
|
+
*
|
|
232
|
+
* Use this to unify SSR and CSR behind a single API client — the resolver
|
|
233
|
+
* picks the right executor per request, so `createApi` is called once and
|
|
234
|
+
* works in both environments without duplicate `*ServerApi` instances.
|
|
235
|
+
*
|
|
236
|
+
* The resolver receives the full {@link ExecuteOptions} so it can branch on
|
|
237
|
+
* environment, URL prefix, auth context, or any runtime condition.
|
|
238
|
+
*
|
|
239
|
+
* @param resolver - Called on every request; returns the executor to delegate to.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* // SSR vs CSR — pick transport based on environment
|
|
244
|
+
* const apiExecutor = dispatchExecutor(() =>
|
|
245
|
+
* typeof window === 'undefined' ? serverExecutor : clientExecutor,
|
|
246
|
+
* );
|
|
247
|
+
*
|
|
248
|
+
* // Route by URL prefix — internal routes use a different transport
|
|
249
|
+
* const apiExecutor = dispatchExecutor((opts) =>
|
|
250
|
+
* opts.url.startsWith('/internal') ? internalExecutor : publicExecutor,
|
|
251
|
+
* );
|
|
252
|
+
* ```
|
|
253
|
+
*/
|
|
254
|
+
declare function dispatchExecutor(resolver: (opts: ExecuteOptions) => Executor): Executor;
|
|
147
255
|
|
|
148
256
|
/**
|
|
149
257
|
* Extracts `:param` segment names from a path template string as a union of
|
|
@@ -159,9 +267,7 @@ type PathParams<TPath extends string> = TPath extends `${string}:${infer Param}/
|
|
|
159
267
|
* When `TPath` contains dynamic segments (`:param`), requires `request.path`
|
|
160
268
|
* to include all extracted param names. No constraint for static paths.
|
|
161
269
|
*/
|
|
162
|
-
type PathConstraint<TPath extends string> = [
|
|
163
|
-
PathParams<TPath>
|
|
164
|
-
] extends [never] ? {} : {
|
|
270
|
+
type PathConstraint<TPath extends string> = [PathParams<TPath>] extends [never] ? {} : {
|
|
165
271
|
path: Record<PathParams<TPath>, unknown>;
|
|
166
272
|
};
|
|
167
273
|
/**
|
|
@@ -242,77 +348,16 @@ declare function endpoint<TResponse extends Validator<unknown>>(spec: {
|
|
|
242
348
|
response: TResponse;
|
|
243
349
|
};
|
|
244
350
|
|
|
245
|
-
|
|
246
|
-
type EndpointFn<TSpec extends EndpointSpec<any, any, any>> = (params: TSpec['request'] extends {
|
|
247
|
-
parse: (data: unknown) => infer R;
|
|
248
|
-
} ? R : RequestShape, signal?: AbortSignal) => Promise<InferResponse<TSpec>>;
|
|
249
|
-
/**
|
|
250
|
-
* Fully-typed API client produced by {@link createApi}.
|
|
251
|
-
* Nested {@link RouterDef} entries become nested sub-client objects.
|
|
252
|
-
*/
|
|
253
|
-
type ApiClient<TEndpoints extends RouterEndpoints> = {
|
|
254
|
-
[K in keyof TEndpoints]: TEndpoints[K] extends RouterDef<infer TNestedEndpoints> ? ApiClient<TNestedEndpoints> : TEndpoints[K] extends EndpointSpec<any, any, any> ? EndpointFn<TEndpoints[K]> : never;
|
|
255
|
-
};
|
|
256
|
-
/**
|
|
257
|
-
* Builds a fully-typed API client from an {@link Executor} and a router
|
|
258
|
-
* (or bare endpoint map).
|
|
259
|
-
*
|
|
260
|
-
* Three call signatures are supported:
|
|
261
|
-
* - `createApi(executor, router)` — preferred; pass the result of {@link defineRouter}.
|
|
262
|
-
* - `createApi(executor, prefix, endpoints)` — inline router without {@link defineRouter}.
|
|
263
|
-
* - `createApi(executor, endpoints)` — no prefix; useful for flat endpoint maps.
|
|
264
|
-
*
|
|
265
|
-
* Each key in `endpoints` becomes a typed async function on the returned client.
|
|
266
|
-
* The function validates the request with `spec.request.parse` (if present),
|
|
267
|
-
* resolves path parameters, calls the executor, validates the response with
|
|
268
|
-
* `spec.response.parse`, and applies `spec.adapter` (if present).
|
|
269
|
-
*
|
|
270
|
-
* @param executor - Transport to use for every HTTP call.
|
|
271
|
-
* @param router - A {@link RouterDef} produced by {@link defineRouter}.
|
|
272
|
-
*
|
|
273
|
-
* @example
|
|
274
|
-
* ```ts
|
|
275
|
-
* const todoApi = createApi(executor, todoRouter);
|
|
276
|
-
* const todos = await todoApi.getList({});
|
|
277
|
-
* const todo = await todoApi.getDetail({ path: { id: 1 } });
|
|
278
|
-
* ```
|
|
279
|
-
*/
|
|
280
|
-
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, router: RouterDef<TEndpoints>): ApiClient<TEndpoints>;
|
|
281
|
-
/**
|
|
282
|
-
* @param executor - Transport to use for every HTTP call.
|
|
283
|
-
* @param prefix - URL prefix prepended to every endpoint path.
|
|
284
|
-
* @param endpoints - Record of named endpoint specs.
|
|
285
|
-
*/
|
|
286
|
-
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, prefix: string, endpoints: TEndpoints): ApiClient<TEndpoints>;
|
|
287
|
-
/**
|
|
288
|
-
* @param executor - Transport to use for every HTTP call.
|
|
289
|
-
* @param endpoints - Record of named endpoint specs (no URL prefix).
|
|
290
|
-
*/
|
|
291
|
-
declare function createApi<TEndpoints extends RouterEndpoints>(executor: Executor, endpoints: TEndpoints): ApiClient<TEndpoints>;
|
|
351
|
+
declare function defineRouter<TEndpoints extends RouterEndpoints>(prefix: string, endpoints: TEndpoints): RouterDef<TEndpoints>;
|
|
292
352
|
|
|
293
353
|
/**
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
* Middlewares are applied in declaration order — the first middleware is the
|
|
298
|
-
* outermost wrapper and runs first on each request.
|
|
299
|
-
*
|
|
300
|
-
* @param execute - The underlying transport function (fetch, axios, etc.).
|
|
301
|
-
* @param middlewares - Ordered list of middlewares to apply.
|
|
302
|
-
*
|
|
303
|
-
* @example
|
|
304
|
-
* ```ts
|
|
305
|
-
* const executor = createExecutor(
|
|
306
|
-
* async ({ method, url, body }) => {
|
|
307
|
-
* const res = await fetch(url, { method, body: JSON.stringify(body) });
|
|
308
|
-
* return res.json();
|
|
309
|
-
* },
|
|
310
|
-
* [withTimeout(5000), withRetry(3), withLogger()],
|
|
311
|
-
* );
|
|
312
|
-
* ```
|
|
354
|
+
* Thrown by {@link withTimeout} when a request exceeds the configured duration.
|
|
355
|
+
* Distinguishable from a user-initiated {@link AbortSignal} cancellation.
|
|
313
356
|
*/
|
|
314
|
-
declare
|
|
315
|
-
|
|
357
|
+
declare class TimeoutError extends Error {
|
|
358
|
+
readonly ms: number;
|
|
359
|
+
constructor(ms: number);
|
|
360
|
+
}
|
|
316
361
|
/**
|
|
317
362
|
* Identity helper that returns the middleware as-is.
|
|
318
363
|
*
|
|
@@ -335,6 +380,8 @@ declare function defineMiddleware(fn: ExecutorMiddleware): ExecutorMiddleware;
|
|
|
335
380
|
*
|
|
336
381
|
* @param count - Number of retries (not counting the initial attempt).
|
|
337
382
|
* @param options.shouldRetry - Return `false` to stop retrying early.
|
|
383
|
+
* Receives the error and a zero-based `attempt` index (0 = first failure,
|
|
384
|
+
* 1 = second failure, …) so you can limit retries by count or error type.
|
|
338
385
|
*
|
|
339
386
|
* @example
|
|
340
387
|
* ```ts
|
|
@@ -369,14 +416,21 @@ declare function withLogger(options?: {
|
|
|
369
416
|
log?: (message: string, data?: unknown) => void;
|
|
370
417
|
}): ExecutorMiddleware;
|
|
371
418
|
|
|
419
|
+
declare function serializeParams(params: Record<string, unknown>): URLSearchParams;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Joins URL path segments, normalising repeated slashes and trailing slashes.
|
|
423
|
+
*
|
|
424
|
+
* **Note:** Intended for relative API paths only. Absolute URLs containing
|
|
425
|
+
* `://` will be collapsed (`https://` → `https:/`). Pass absolute URLs
|
|
426
|
+
* directly to the executor instead of through this helper.
|
|
427
|
+
*/
|
|
372
428
|
declare function joinPaths(...segments: string[]): string;
|
|
373
429
|
declare function resolvePath(pathTemplate: string, params?: Record<string, unknown>): string;
|
|
374
430
|
|
|
375
|
-
declare function serializeParams(params: Record<string, unknown>): URLSearchParams;
|
|
376
|
-
|
|
377
431
|
declare class ValidationError extends Error {
|
|
378
|
-
readonly cause?: unknown
|
|
379
|
-
constructor(message: string, cause?: unknown
|
|
432
|
+
readonly cause?: unknown;
|
|
433
|
+
constructor(message: string, cause?: unknown);
|
|
380
434
|
}
|
|
381
435
|
|
|
382
|
-
export { type ApiTypes, type EndpointSpec, type ExecuteOptions, type Executor, type ExecutorMiddleware, type HttpMethod, type InferResponse, type PathParams, type RequestShape, type RouterDef, type RouterEndpoints, type RouterEntry, ValidationError, type Validator, type ValidatorOutput, createApi, createExecutor, defineMiddleware, defineRouter, endpoint, joinPaths, resolvePath, serializeParams, withLogger, withRetry, withTimeout };
|
|
436
|
+
export { type ApiTypes, type CreateApiOptions, type EndpointSpec, type ExecuteOptions, type Executor, type ExecutorMiddleware, type HttpMethod, type InferResponse, type PathParams, type RequestShape, type RouterDef, type RouterEndpoints, type RouterEntry, TimeoutError, ValidationError, type Validator, type ValidatorOutput, createApi, createExecutor, defineMiddleware, defineRouter, dispatchExecutor, endpoint, joinPaths, resolvePath, serializeParams, withLogger, withRetry, withTimeout };
|