@mpen/routekit 0.1.0 → 0.1.2
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/bin.d.mts +4 -0
- package/dist/client/react.d.mts +178 -0
- package/dist/client/react.mjs +142 -0
- package/dist/client.d.mts +433 -0
- package/dist/client.mjs +264 -0
- package/dist/content-BuDOmhH_.mjs +102 -0
- package/dist/core-CzUCxvGk.d.mts +140 -0
- package/dist/core-DbmQauwS.mjs +81 -0
- package/dist/handlers.d.mts +72 -0
- package/dist/handlers.mjs +153 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.mjs +1152 -0
- package/dist/middleware.d.mts +388 -0
- package/dist/middleware.mjs +1222 -0
- package/dist/request-Dn0zc-xm.mjs +1025 -0
- package/dist/response/content.d.mts +79 -0
- package/dist/response/content.mjs +2 -0
- package/dist/response/json-rpc.d.mts +1 -0
- package/dist/response/json-rpc.mjs +1 -0
- package/dist/response/problem/valibot.d.mts +230 -0
- package/dist/response/problem/valibot.mjs +258 -0
- package/dist/response/problem.d.mts +415 -0
- package/dist/response/problem.mjs +183 -0
- package/dist/response/status.d.mts +45 -0
- package/dist/response/status.mjs +2 -0
- package/dist/responses-B379Ep9Y.d.mts +296 -0
- package/dist/responses-BpVrgeYi.mjs +101 -0
- package/dist/router-Cwb7ak0J.d.mts +1819 -0
- package/dist/routes.d.mts +282 -0
- package/dist/routes.mjs +311 -0
- package/dist/status-C-8mw-FB.mjs +59 -0
- package/dist/valibot-D7liFYyB.d.mts +290 -0
- package/dist/valibot-Du97X-TS.mjs +326 -0
- package/package.json +8 -2
- package/src/bin/gen-api-client.test.ts +0 -70
- package/src/bin/gen-api-client.ts +0 -986
- package/src/client/headers.ts +0 -31
- package/src/client/index.ts +0 -8
- package/src/client/promise.ts +0 -11
- package/src/client/react/index.test.tsx +0 -266
- package/src/client/react/index.ts +0 -431
- package/src/client/responses.test.ts +0 -151
- package/src/client/responses.ts +0 -278
- package/src/client/transport.ts +0 -74
- package/src/client/transports/body-codec.ts +0 -61
- package/src/client/transports/fetch.ts +0 -113
- package/src/client/tsconfig.json +0 -9
- package/src/client/types.ts +0 -15
- package/src/client/url.ts +0 -31
- package/src/index.ts +0 -63
- package/src/router/fetch-types.ts +0 -13
- package/src/router/handlers/index.ts +0 -2
- package/src/router/handlers/openapi/index.ts +0 -2
- package/src/router/handlers/openapi/openapi.ts +0 -293
- package/src/router/integration/zod-openapi.test.ts +0 -74
- package/src/router/lib/charset.test.ts +0 -22
- package/src/router/lib/charset.ts +0 -133
- package/src/router/lib/collections.ts +0 -3
- package/src/router/lib/format.test.ts +0 -67
- package/src/router/lib/format.ts +0 -35
- package/src/router/lib/host.ts +0 -4
- package/src/router/lib/json-schema.ts +0 -6
- package/src/router/lib/media-type.test.ts +0 -122
- package/src/router/lib/media-type.ts +0 -289
- package/src/router/lib/pathname.test.ts +0 -18
- package/src/router/lib/pathname.ts +0 -19
- package/src/router/lib/route-names.ts +0 -70
- package/src/router/lib/route-normalize.test.ts +0 -36
- package/src/router/lib/route-normalize.ts +0 -67
- package/src/router/lib/schema-merge.ts +0 -56
- package/src/router/middleware/accept-ctx.test.ts +0 -33
- package/src/router/middleware/accept-ctx.ts +0 -12
- package/src/router/middleware/body-limit.test.ts +0 -112
- package/src/router/middleware/body-limit.ts +0 -121
- package/src/router/middleware/content-type-context.ts +0 -0
- package/src/router/middleware/cors.test.ts +0 -269
- package/src/router/middleware/cors.ts +0 -490
- package/src/router/middleware/csrf.test.ts +0 -106
- package/src/router/middleware/csrf.ts +0 -192
- package/src/router/middleware/define.ts +0 -249
- package/src/router/middleware/index.ts +0 -34
- package/src/router/middleware/jsxhtml-response.ts +0 -0
- package/src/router/middleware/oas-swagger.ts +0 -0
- package/src/router/middleware/rate-limit.test.ts +0 -886
- package/src/router/middleware/rate-limit.ts +0 -920
- package/src/router/middleware/request-id-ctx.test.ts +0 -183
- package/src/router/middleware/request-id-ctx.ts +0 -135
- package/src/router/middleware/request-logger-format.test.ts +0 -16
- package/src/router/middleware/request-logger-format.ts +0 -269
- package/src/router/middleware/request-logger.test.ts +0 -267
- package/src/router/middleware/request-logger.ts +0 -131
- package/src/router/middleware/start-time-ctx.ts +0 -5
- package/src/router/request.ts +0 -611
- package/src/router/response/core.ts +0 -181
- package/src/router/response/directives.ts +0 -233
- package/src/router/response/formats/content/bodyless.ts +0 -54
- package/src/router/response/formats/content/content.ts +0 -79
- package/src/router/response/formats/content/index.ts +0 -2
- package/src/router/response/formats/json-rpc/index.ts +0 -2
- package/src/router/response/formats/problem/badRequest.ts +0 -90
- package/src/router/response/formats/problem/conflict.ts +0 -90
- package/src/router/response/formats/problem/created.ts +0 -40
- package/src/router/response/formats/problem/index.ts +0 -27
- package/src/router/response/formats/problem/notFound.ts +0 -90
- package/src/router/response/formats/problem/permissionDenied.ts +0 -90
- package/src/router/response/formats/problem/problem.test.ts +0 -888
- package/src/router/response/formats/problem/rateLimited.ts +0 -90
- package/src/router/response/formats/problem/responses.ts +0 -219
- package/src/router/response/formats/problem/root-errors.ts +0 -48
- package/src/router/response/formats/problem/sessionExpired.ts +0 -90
- package/src/router/response/formats/problem/types.ts +0 -170
- package/src/router/response/formats/problem/unauthenticated.ts +0 -90
- package/src/router/response/formats/problem/valibot.ts +0 -410
- package/src/router/response/formats/status/index.ts +0 -1
- package/src/router/response/formats/status/responses.ts +0 -59
- package/src/router/response/formats/status/status.test.ts +0 -21
- package/src/router/response/framers.ts +0 -85
- package/src/router/response/index.ts +0 -28
- package/src/router/response/openapi.test.ts +0 -96
- package/src/router/response/openapi.ts +0 -1
- package/src/router/response/serializers.ts +0 -66
- package/src/router/response/stream.ts +0 -35
- package/src/router/router.test.ts +0 -1571
- package/src/router/router.ts +0 -1965
- package/src/router/routes/index.ts +0 -46
- package/src/router/routes/valibot/index.ts +0 -18
- package/src/router/routes/valibot/valibot.ts +0 -1393
- package/src/router/routes/valibot.test.ts +0 -286
- package/src/router/routes/zod/index.ts +0 -18
- package/src/router/routes/zod/zod.ts +0 -1318
- package/src/router/routes/zod.test.ts +0 -280
- package/src/router/server-interface.ts +0 -31
- package/src/router/types.ts +0 -657
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
//#region src/client/promise.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A value that may be returned immediately or through a promise.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* const headers: MaybePromise<HeadersInit> = { authorization: 'Bearer token' }
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* @typeParam T - The resolved value type.
|
|
11
|
+
*/
|
|
12
|
+
type MaybePromise<T> = T | Promise<T>;
|
|
13
|
+
//#endregion
|
|
14
|
+
//#region src/client/headers.d.ts
|
|
15
|
+
type ClientHeadersInit$1 = NonNullable<ConstructorParameters<typeof Headers>[0]>;
|
|
16
|
+
/**
|
|
17
|
+
* Context passed to global header providers.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* const headers = (context: ClientHeaderContext) =>
|
|
22
|
+
* context.url.startsWith('/admin') ? { authorization: `Bearer ${token}` } : undefined
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
interface ClientHeaderContext {
|
|
26
|
+
/** The request URL before base URL resolution. */
|
|
27
|
+
url: string;
|
|
28
|
+
/** The request init object before execution. */
|
|
29
|
+
init: RequestInit;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Headers or a function that provides headers for each request.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* const headers: ClientHeaders = () => ({ authorization: `Bearer ${token}` })
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
type ClientHeaders = ClientHeadersInit$1 | ((context: ClientHeaderContext) => MaybePromise<ClientHeadersInit$1 | undefined>);
|
|
40
|
+
//#endregion
|
|
41
|
+
//#region src/client/transport.d.ts
|
|
42
|
+
type ClientHeadersInit = NonNullable<ConstructorParameters<typeof Headers>[0]>;
|
|
43
|
+
/**
|
|
44
|
+
* A generated client request before it is executed by a transport.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const request: ClientRequest = {
|
|
49
|
+
* url: '/widgets',
|
|
50
|
+
* init: { method: 'POST' },
|
|
51
|
+
* body: { name: 'demo' },
|
|
52
|
+
* }
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
interface ClientRequest {
|
|
56
|
+
/** The URL path generated for the route. */
|
|
57
|
+
url: string;
|
|
58
|
+
/** Fetch-compatible request initialization. */
|
|
59
|
+
init: RequestInit;
|
|
60
|
+
/** The unencoded request body value. */
|
|
61
|
+
body?: unknown;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A minimal response returned by client transports.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const response: ApiTransportResponse = {
|
|
69
|
+
* status: 200,
|
|
70
|
+
* body: { ok: true },
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
interface ApiTransportResponse {
|
|
75
|
+
/** The response status code. */
|
|
76
|
+
status: number;
|
|
77
|
+
/** The response headers, when available. */
|
|
78
|
+
headers?: ClientHeadersInit;
|
|
79
|
+
/** The parsed response body. */
|
|
80
|
+
body: unknown;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The async response wrapper returned by client transports.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const response: ApiTransportResponsePromise = transport.request(request)
|
|
88
|
+
* ```
|
|
89
|
+
*/
|
|
90
|
+
type ApiTransportResponsePromise = Promise<ApiTransportResponse>;
|
|
91
|
+
/**
|
|
92
|
+
* Transport used by generated API clients to execute typed requests.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* class AxiosTransport implements ClientTransport {
|
|
97
|
+
* async request(request: ClientRequest): ApiTransportResponsePromise {
|
|
98
|
+
* throw new Error('Adapt Axios into a Response-shaped object here')
|
|
99
|
+
* }
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
interface ClientTransport {
|
|
104
|
+
/**
|
|
105
|
+
* Execute a generated client request.
|
|
106
|
+
*
|
|
107
|
+
* @param request - The generated request metadata and init.
|
|
108
|
+
* @returns A transport response promise.
|
|
109
|
+
*/
|
|
110
|
+
request(request: ClientRequest): ApiTransportResponsePromise;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/client/responses.d.ts
|
|
114
|
+
/**
|
|
115
|
+
* A typed response returned by generated API clients.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const response: ApiResponse<{ ok: true }> = await client.health.get()
|
|
120
|
+
* if (response.status === 200) {
|
|
121
|
+
* response.body.ok
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*
|
|
125
|
+
* @typeParam T - The parsed response body type.
|
|
126
|
+
*/
|
|
127
|
+
interface ApiResponse<T> {
|
|
128
|
+
/** Whether the response status is in the successful 200-299 range. */
|
|
129
|
+
ok: boolean;
|
|
130
|
+
/** The response status code. */
|
|
131
|
+
status: number;
|
|
132
|
+
/** The response headers. */
|
|
133
|
+
headers: Headers;
|
|
134
|
+
/** The parsed response body. */
|
|
135
|
+
body: T;
|
|
136
|
+
}
|
|
137
|
+
type ApiResponseStatus<TStatus> = TStatus extends number ? TStatus : TStatus extends `${infer TNumber extends number}` ? TNumber : number;
|
|
138
|
+
/**
|
|
139
|
+
* A typed response union keyed by HTTP status code.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* type WidgetResponse = ApiResponseByStatus<{
|
|
144
|
+
* 200: { id: number }
|
|
145
|
+
* 400: { message: string }
|
|
146
|
+
* }>
|
|
147
|
+
*
|
|
148
|
+
* if (response.status === 400) {
|
|
149
|
+
* response.body.message
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* @typeParam T - A map from response status codes to parsed response body types.
|
|
154
|
+
*/
|
|
155
|
+
type ApiResponseByStatus<T extends object> = { [TStatus in keyof T]: Omit<ApiResponse<T[TStatus]>, 'status'> & {
|
|
156
|
+
/** The narrowed response status code. */status: ApiResponseStatus<TStatus>;
|
|
157
|
+
} }[keyof T];
|
|
158
|
+
/**
|
|
159
|
+
* The default async response wrapper used by generated API clients.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```ts
|
|
163
|
+
* const response: ApiResponsePromise<{ id: number }> = client.items.byId.get(123)
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* @typeParam T - The parsed response body type.
|
|
167
|
+
*/
|
|
168
|
+
type ApiResponsePromise<T> = Promise<ApiResponse<T>>;
|
|
169
|
+
/**
|
|
170
|
+
* The async response wrapper used by generated API clients for routes with response body types
|
|
171
|
+
* keyed by HTTP status code.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* const response: ApiResponseByStatusPromise<{ 200: { ok: true } }> = client.health.get()
|
|
176
|
+
* ```
|
|
177
|
+
*
|
|
178
|
+
* @typeParam T - A map from response status codes to parsed response body types.
|
|
179
|
+
*/
|
|
180
|
+
type ApiResponseByStatusPromise<T extends object> = Promise<ApiResponseByStatus<T>>;
|
|
181
|
+
/**
|
|
182
|
+
* Extracts the successful `data` payload from a Routekit Problem-style success envelope.
|
|
183
|
+
*
|
|
184
|
+
* @example
|
|
185
|
+
* ```ts
|
|
186
|
+
* type Body = { success: true; data: { id: string } }
|
|
187
|
+
* type Data = RoutekitProblemSuccessData<Body> // { id: string }
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @typeParam TSuccessBody - Routekit Problem-style successful response body.
|
|
191
|
+
*/
|
|
192
|
+
type RoutekitProblemSuccessData<TSuccessBody> = TSuccessBody extends {
|
|
193
|
+
success: true;
|
|
194
|
+
data: infer Data;
|
|
195
|
+
} ? Data : unknown;
|
|
196
|
+
/**
|
|
197
|
+
* Error thrown for Routekit Problem-style response bodies.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* try {
|
|
202
|
+
* await resolveRoutekitProblemData(response)
|
|
203
|
+
* } catch (error) {
|
|
204
|
+
* if (isRoutekitProblemError(error)) {
|
|
205
|
+
* console.error(error.status, error.body.error.code)
|
|
206
|
+
* }
|
|
207
|
+
* }
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* @typeParam TProblem - Routekit Problem-style error response body.
|
|
211
|
+
*/
|
|
212
|
+
declare class RoutekitProblemError<TProblem> extends Error {
|
|
213
|
+
/**
|
|
214
|
+
* The response status code.
|
|
215
|
+
*/
|
|
216
|
+
readonly status: number;
|
|
217
|
+
/**
|
|
218
|
+
* The response headers.
|
|
219
|
+
*/
|
|
220
|
+
readonly headers: Headers;
|
|
221
|
+
/**
|
|
222
|
+
* The Routekit Problem-style response body.
|
|
223
|
+
*/
|
|
224
|
+
readonly body: TProblem;
|
|
225
|
+
/**
|
|
226
|
+
* The full normalized API response.
|
|
227
|
+
*/
|
|
228
|
+
readonly response: ApiResponse<TProblem>;
|
|
229
|
+
/**
|
|
230
|
+
* Create an error for a Routekit Problem-style response.
|
|
231
|
+
*
|
|
232
|
+
* @param response - Normalized API response containing a problem body.
|
|
233
|
+
*/
|
|
234
|
+
constructor(response: ApiResponse<TProblem>);
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Checks whether a value is a [`RoutekitProblemError`]{@link RoutekitProblemError}.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* if (isRoutekitProblemError(error)) {
|
|
242
|
+
* error.body
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* @param value - Value to inspect.
|
|
247
|
+
* @returns Whether the value is a Routekit Problem error.
|
|
248
|
+
* @typeParam TProblem - Routekit Problem-style error response body.
|
|
249
|
+
*/
|
|
250
|
+
declare function isRoutekitProblemError<TProblem = unknown>(value: unknown): value is RoutekitProblemError<TProblem>;
|
|
251
|
+
/**
|
|
252
|
+
* Normalize a transport response into the public generated API response shape.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* const response = await resolveApiResponse(transport.request(request))
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* @param response - A transport response or transport response promise.
|
|
260
|
+
* @returns The normalized API response.
|
|
261
|
+
* @typeParam T - The parsed response body type.
|
|
262
|
+
*/
|
|
263
|
+
declare function resolveApiResponse<T>(response: ApiTransportResponse | ApiTransportResponsePromise): ApiResponsePromise<T>;
|
|
264
|
+
/**
|
|
265
|
+
* Normalize a transport response into a generated API response union keyed by status code.
|
|
266
|
+
*
|
|
267
|
+
* @example
|
|
268
|
+
* ```ts
|
|
269
|
+
* const response = await resolveApiResponseByStatus<{ 200: { ok: true } }>(
|
|
270
|
+
* transport.request(request),
|
|
271
|
+
* )
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @param response - A transport response or transport response promise.
|
|
275
|
+
* @returns The normalized API response.
|
|
276
|
+
* @typeParam T - A map from response status codes to parsed response body types.
|
|
277
|
+
*/
|
|
278
|
+
declare function resolveApiResponseByStatus<T extends object>(response: ApiTransportResponse | ApiTransportResponsePromise): ApiResponseByStatusPromise<T>;
|
|
279
|
+
/**
|
|
280
|
+
* Resolve a Routekit Problem-style API response into its successful data payload.
|
|
281
|
+
*
|
|
282
|
+
* This helper unwraps `{ success: true, data }` bodies and throws
|
|
283
|
+
* [`RoutekitProblemError`]{@link RoutekitProblemError} for non-success problem bodies, which lets
|
|
284
|
+
* TanStack Query use its built-in success and error states while preserving rich problem details.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```ts
|
|
288
|
+
* const data = await resolveRoutekitProblemData<
|
|
289
|
+
* { success: true; data: { id: string } },
|
|
290
|
+
* { success: false; error: { code: 'not_found'; message: string } }
|
|
291
|
+
* >(transport.request(request))
|
|
292
|
+
* ```
|
|
293
|
+
*
|
|
294
|
+
* @param response - A transport response, normalized API response, or promise for either.
|
|
295
|
+
* @returns The unwrapped successful response `data` payload.
|
|
296
|
+
* @typeParam TSuccessBody - Routekit Problem-style successful response body.
|
|
297
|
+
* @typeParam TProblemBody - Routekit Problem-style problem response body.
|
|
298
|
+
*/
|
|
299
|
+
declare function resolveRoutekitProblemData<TSuccessBody, TProblemBody>(response: ApiResponse<TSuccessBody | TProblemBody> | ApiTransportResponse | ApiTransportResponsePromise | Promise<ApiResponse<TSuccessBody | TProblemBody>>): Promise<RoutekitProblemSuccessData<TSuccessBody>>;
|
|
300
|
+
//#endregion
|
|
301
|
+
//#region src/client/types.d.ts
|
|
302
|
+
/**
|
|
303
|
+
* Extracts the type of a single path parameter from a path parameter object type.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* type Params = { id: string }
|
|
308
|
+
* type IdParam = SinglePathParam<Params, 'id'> // string
|
|
309
|
+
* ```
|
|
310
|
+
*
|
|
311
|
+
* @typeParam TParams - The path parameter object type.
|
|
312
|
+
* @typeParam TKey - The parameter key to extract.
|
|
313
|
+
*/
|
|
314
|
+
type SinglePathParam<TParams, TKey extends string> = TParams extends { [K in TKey]: infer V } ? V : unknown;
|
|
315
|
+
//#endregion
|
|
316
|
+
//#region src/client/transports/body-codec.d.ts
|
|
317
|
+
type ClientBodyInit = NonNullable<RequestInit['body']>;
|
|
318
|
+
/**
|
|
319
|
+
* The response body stream exposed to response body codecs.
|
|
320
|
+
*
|
|
321
|
+
* @example
|
|
322
|
+
* ```ts
|
|
323
|
+
* const text = await new Response(body).text()
|
|
324
|
+
* ```
|
|
325
|
+
*/
|
|
326
|
+
type ResponseBodyReader = ReadableStream<Uint8Array<ArrayBufferLike>> | null;
|
|
327
|
+
/**
|
|
328
|
+
* Serializes request bodies and deserializes response bodies for generated clients.
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```ts
|
|
332
|
+
* const textJsonCodec: BodyCodec = {
|
|
333
|
+
* serialize: (value) => JSON.stringify(value),
|
|
334
|
+
* deserialize: async (body) => JSON.parse(await new Response(body).text()),
|
|
335
|
+
* }
|
|
336
|
+
* ```
|
|
337
|
+
*/
|
|
338
|
+
interface BodyCodec {
|
|
339
|
+
/**
|
|
340
|
+
* Serialize a generated client body value into a Fetch-compatible body.
|
|
341
|
+
*
|
|
342
|
+
* @param value - The generated client body value.
|
|
343
|
+
* @returns A Fetch-compatible body, or nullish to omit the request body.
|
|
344
|
+
*/
|
|
345
|
+
serialize(value: unknown): ClientBodyInit | null | undefined;
|
|
346
|
+
/**
|
|
347
|
+
* Deserialize a response body for generated `response.body` values.
|
|
348
|
+
*
|
|
349
|
+
* @param body - The response body stream.
|
|
350
|
+
* @param contentType - The response content type, when present.
|
|
351
|
+
* @returns The deserialized response body.
|
|
352
|
+
* @typeParam T - The expected response body type.
|
|
353
|
+
*/
|
|
354
|
+
deserialize<T>(body: ResponseBodyReader, contentType: string | null): Promise<T>;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The default JSON codec used by generated API clients.
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* ```ts
|
|
361
|
+
* const client = new ApiClient(new FetchTransport({ bodyCodec: jsonBodyCodec }))
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
declare const jsonBodyCodec: BodyCodec;
|
|
365
|
+
//#endregion
|
|
366
|
+
//#region src/client/transports/fetch.d.ts
|
|
367
|
+
/**
|
|
368
|
+
* Options for [`FetchTransport`]{@link FetchTransport}.
|
|
369
|
+
*
|
|
370
|
+
* @example
|
|
371
|
+
* ```ts
|
|
372
|
+
* const transport = new FetchTransport({
|
|
373
|
+
* baseUrl: 'https://api.example.com',
|
|
374
|
+
* headers: () => ({ authorization: `Bearer ${token}` }),
|
|
375
|
+
* })
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
interface FetchTransportOptions {
|
|
379
|
+
/** Base URL used to resolve generated relative route URLs. */
|
|
380
|
+
baseUrl?: string | URL;
|
|
381
|
+
/** Fetch implementation to call. Defaults to `globalThis.fetch`. */
|
|
382
|
+
fetch?: (url: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
383
|
+
/** Global headers, or a function that returns headers for each request. */
|
|
384
|
+
headers?: ClientHeaders;
|
|
385
|
+
/** Default body codec. Defaults to [`jsonBodyCodec`]{@link jsonBodyCodec}. */
|
|
386
|
+
bodyCodec?: BodyCodec;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Fetch-based transport for generated API clients.
|
|
390
|
+
*
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* const client = new ApiClient(
|
|
394
|
+
* new FetchTransport({
|
|
395
|
+
* baseUrl: 'https://api.example.com',
|
|
396
|
+
* headers: { authorization: 'Bearer token' },
|
|
397
|
+
* }),
|
|
398
|
+
* )
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
401
|
+
declare class FetchTransport implements ClientTransport {
|
|
402
|
+
#private;
|
|
403
|
+
/**
|
|
404
|
+
* Create a Fetch-backed generated client transport.
|
|
405
|
+
*
|
|
406
|
+
* @param options - Transport configuration.
|
|
407
|
+
*/
|
|
408
|
+
constructor(options?: FetchTransportOptions);
|
|
409
|
+
/**
|
|
410
|
+
* Execute a generated client request with Fetch.
|
|
411
|
+
*
|
|
412
|
+
* @param request - The generated client request.
|
|
413
|
+
* @returns A transport response promise.
|
|
414
|
+
*/
|
|
415
|
+
request(request: ClientRequest): ApiTransportResponsePromise;
|
|
416
|
+
}
|
|
417
|
+
//#endregion
|
|
418
|
+
//#region src/client/url.d.ts
|
|
419
|
+
/**
|
|
420
|
+
* Append an object of query values to a URL.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* withQuery('/widgets', { view: 'full', tag: ['a', 'b'] })
|
|
425
|
+
* ```
|
|
426
|
+
*
|
|
427
|
+
* @param url - URL without generated query parameters.
|
|
428
|
+
* @param query - Query parameter object.
|
|
429
|
+
* @returns The URL with serialized query parameters.
|
|
430
|
+
*/
|
|
431
|
+
declare function withQuery(url: string, query: object): string;
|
|
432
|
+
//#endregion
|
|
433
|
+
export { ApiResponse, ApiResponseByStatus, ApiResponseByStatusPromise, ApiResponsePromise, ApiTransportResponse, ApiTransportResponsePromise, BodyCodec, ClientHeaderContext, ClientHeaders, ClientRequest, ClientTransport, FetchTransport, FetchTransportOptions, MaybePromise, ResponseBodyReader, RoutekitProblemError, RoutekitProblemSuccessData, SinglePathParam, isRoutekitProblemError, jsonBodyCodec, resolveApiResponse, resolveApiResponseByStatus, resolveRoutekitProblemData, withQuery };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
//#region src/client/responses.ts
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown for Routekit Problem-style response bodies.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* try {
|
|
8
|
+
* await resolveRoutekitProblemData(response)
|
|
9
|
+
* } catch (error) {
|
|
10
|
+
* if (isRoutekitProblemError(error)) {
|
|
11
|
+
* console.error(error.status, error.body.error.code)
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @typeParam TProblem - Routekit Problem-style error response body.
|
|
17
|
+
*/
|
|
18
|
+
var RoutekitProblemError = class extends Error {
|
|
19
|
+
/**
|
|
20
|
+
* The response status code.
|
|
21
|
+
*/
|
|
22
|
+
status;
|
|
23
|
+
/**
|
|
24
|
+
* The response headers.
|
|
25
|
+
*/
|
|
26
|
+
headers;
|
|
27
|
+
/**
|
|
28
|
+
* The Routekit Problem-style response body.
|
|
29
|
+
*/
|
|
30
|
+
body;
|
|
31
|
+
/**
|
|
32
|
+
* The full normalized API response.
|
|
33
|
+
*/
|
|
34
|
+
response;
|
|
35
|
+
/**
|
|
36
|
+
* Create an error for a Routekit Problem-style response.
|
|
37
|
+
*
|
|
38
|
+
* @param response - Normalized API response containing a problem body.
|
|
39
|
+
*/
|
|
40
|
+
constructor(response) {
|
|
41
|
+
super(getRoutekitProblemMessage(response.body));
|
|
42
|
+
this.name = "RoutekitProblemError";
|
|
43
|
+
this.status = response.status;
|
|
44
|
+
this.headers = response.headers;
|
|
45
|
+
this.body = response.body;
|
|
46
|
+
this.response = response;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Checks whether a value is a [`RoutekitProblemError`]{@link RoutekitProblemError}.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```ts
|
|
54
|
+
* if (isRoutekitProblemError(error)) {
|
|
55
|
+
* error.body
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @param value - Value to inspect.
|
|
60
|
+
* @returns Whether the value is a Routekit Problem error.
|
|
61
|
+
* @typeParam TProblem - Routekit Problem-style error response body.
|
|
62
|
+
*/
|
|
63
|
+
function isRoutekitProblemError(value) {
|
|
64
|
+
return value instanceof RoutekitProblemError;
|
|
65
|
+
}
|
|
66
|
+
function getRoutekitProblemMessage(body) {
|
|
67
|
+
if (!body || typeof body !== "object") return "Routekit problem response";
|
|
68
|
+
const error = body.error;
|
|
69
|
+
return typeof error?.message === "string" ? error.message : "Routekit problem response";
|
|
70
|
+
}
|
|
71
|
+
function isRoutekitProblemSuccessBody(body) {
|
|
72
|
+
return !!body && typeof body === "object" && body.success === true && "data" in body;
|
|
73
|
+
}
|
|
74
|
+
function isApiResponse(response) {
|
|
75
|
+
return "ok" in response && typeof response.ok === "boolean" && response.headers instanceof Headers;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Normalize a transport response into the public generated API response shape.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const response = await resolveApiResponse(transport.request(request))
|
|
83
|
+
* ```
|
|
84
|
+
*
|
|
85
|
+
* @param response - A transport response or transport response promise.
|
|
86
|
+
* @returns The normalized API response.
|
|
87
|
+
* @typeParam T - The parsed response body type.
|
|
88
|
+
*/
|
|
89
|
+
async function resolveApiResponse(response) {
|
|
90
|
+
const resolved = await response;
|
|
91
|
+
return {
|
|
92
|
+
ok: resolved.status >= 200 && resolved.status < 300,
|
|
93
|
+
status: resolved.status,
|
|
94
|
+
headers: new Headers(resolved.headers),
|
|
95
|
+
body: resolved.body
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Normalize a transport response into a generated API response union keyed by status code.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* const response = await resolveApiResponseByStatus<{ 200: { ok: true } }>(
|
|
104
|
+
* transport.request(request),
|
|
105
|
+
* )
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @param response - A transport response or transport response promise.
|
|
109
|
+
* @returns The normalized API response.
|
|
110
|
+
* @typeParam T - A map from response status codes to parsed response body types.
|
|
111
|
+
*/
|
|
112
|
+
function resolveApiResponseByStatus(response) {
|
|
113
|
+
return resolveApiResponse(response);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a Routekit Problem-style API response into its successful data payload.
|
|
117
|
+
*
|
|
118
|
+
* This helper unwraps `{ success: true, data }` bodies and throws
|
|
119
|
+
* [`RoutekitProblemError`]{@link RoutekitProblemError} for non-success problem bodies, which lets
|
|
120
|
+
* TanStack Query use its built-in success and error states while preserving rich problem details.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* const data = await resolveRoutekitProblemData<
|
|
125
|
+
* { success: true; data: { id: string } },
|
|
126
|
+
* { success: false; error: { code: 'not_found'; message: string } }
|
|
127
|
+
* >(transport.request(request))
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @param response - A transport response, normalized API response, or promise for either.
|
|
131
|
+
* @returns The unwrapped successful response `data` payload.
|
|
132
|
+
* @typeParam TSuccessBody - Routekit Problem-style successful response body.
|
|
133
|
+
* @typeParam TProblemBody - Routekit Problem-style problem response body.
|
|
134
|
+
*/
|
|
135
|
+
async function resolveRoutekitProblemData(response) {
|
|
136
|
+
const resolvedResponse = await response;
|
|
137
|
+
const apiResponse = isApiResponse(resolvedResponse) ? resolvedResponse : await resolveApiResponse(resolvedResponse);
|
|
138
|
+
if (apiResponse.ok && isRoutekitProblemSuccessBody(apiResponse.body)) return apiResponse.body.data;
|
|
139
|
+
throw new RoutekitProblemError(apiResponse);
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region src/client/transports/body-codec.ts
|
|
143
|
+
/**
|
|
144
|
+
* The default JSON codec used by generated API clients.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```ts
|
|
148
|
+
* const client = new ApiClient(new FetchTransport({ bodyCodec: jsonBodyCodec }))
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
const jsonBodyCodec = {
|
|
152
|
+
serialize: (value) => JSON.stringify(value),
|
|
153
|
+
deserialize: async (body, contentType) => {
|
|
154
|
+
if (!body) return void 0;
|
|
155
|
+
const text = await new Response(body).text();
|
|
156
|
+
if (text.length === 0) return void 0;
|
|
157
|
+
if (contentType?.includes("json")) return JSON.parse(text);
|
|
158
|
+
return text;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
//#region src/client/transports/fetch.ts
|
|
163
|
+
function mergeHeaders(...sources) {
|
|
164
|
+
const headers = new Headers();
|
|
165
|
+
for (const source of sources) {
|
|
166
|
+
if (!source) continue;
|
|
167
|
+
new Headers(source).forEach((value, key) => {
|
|
168
|
+
headers.set(key, value);
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return headers;
|
|
172
|
+
}
|
|
173
|
+
function resolveUrl(url, baseUrl) {
|
|
174
|
+
return baseUrl ? new URL(url, baseUrl).toString() : url;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Fetch-based transport for generated API clients.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const client = new ApiClient(
|
|
182
|
+
* new FetchTransport({
|
|
183
|
+
* baseUrl: 'https://api.example.com',
|
|
184
|
+
* headers: { authorization: 'Bearer token' },
|
|
185
|
+
* }),
|
|
186
|
+
* )
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
var FetchTransport = class {
|
|
190
|
+
#baseUrl;
|
|
191
|
+
#bodyCodec;
|
|
192
|
+
#fetch;
|
|
193
|
+
#headers;
|
|
194
|
+
/**
|
|
195
|
+
* Create a Fetch-backed generated client transport.
|
|
196
|
+
*
|
|
197
|
+
* @param options - Transport configuration.
|
|
198
|
+
*/
|
|
199
|
+
constructor(options = {}) {
|
|
200
|
+
this.#baseUrl = options.baseUrl;
|
|
201
|
+
this.#bodyCodec = options.bodyCodec ?? jsonBodyCodec;
|
|
202
|
+
this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
203
|
+
this.#headers = options.headers;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Execute a generated client request with Fetch.
|
|
207
|
+
*
|
|
208
|
+
* @param request - The generated client request.
|
|
209
|
+
* @returns A transport response promise.
|
|
210
|
+
*/
|
|
211
|
+
async request(request) {
|
|
212
|
+
const codec = this.#bodyCodec;
|
|
213
|
+
const init = { ...request.init };
|
|
214
|
+
const headerContext = {
|
|
215
|
+
url: request.url,
|
|
216
|
+
init
|
|
217
|
+
};
|
|
218
|
+
const headers = mergeHeaders(typeof this.#headers === "function" ? await this.#headers(headerContext) : this.#headers, init.headers);
|
|
219
|
+
if (Object.hasOwn(request, "body")) {
|
|
220
|
+
const body = codec.serialize(request.body);
|
|
221
|
+
if (body != null) {
|
|
222
|
+
init.body = body;
|
|
223
|
+
if (!headers.has("content-type")) headers.set("content-type", "application/json");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
if ([...headers].length > 0) init.headers = headers;
|
|
227
|
+
else delete init.headers;
|
|
228
|
+
const response = await this.#fetch(resolveUrl(request.url, this.#baseUrl), init);
|
|
229
|
+
return {
|
|
230
|
+
status: response.status,
|
|
231
|
+
headers: response.headers,
|
|
232
|
+
body: await codec.deserialize(response.body, response.headers.get("content-type"))
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/client/url.ts
|
|
238
|
+
/**
|
|
239
|
+
* Append an object of query values to a URL.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```ts
|
|
243
|
+
* withQuery('/widgets', { view: 'full', tag: ['a', 'b'] })
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* @param url - URL without generated query parameters.
|
|
247
|
+
* @param query - Query parameter object.
|
|
248
|
+
* @returns The URL with serialized query parameters.
|
|
249
|
+
*/
|
|
250
|
+
function withQuery(url, query) {
|
|
251
|
+
const searchParams = new URLSearchParams();
|
|
252
|
+
for (const [key, value] of Object.entries(query)) {
|
|
253
|
+
if (value == null) continue;
|
|
254
|
+
if (Array.isArray(value)) {
|
|
255
|
+
for (const item of value) if (item != null) searchParams.append(key, typeof item === "object" ? JSON.stringify(item) : String(item));
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
searchParams.append(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
259
|
+
}
|
|
260
|
+
const search = searchParams.toString();
|
|
261
|
+
return search.length > 0 ? `${url}?${search}` : url;
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
export { FetchTransport, RoutekitProblemError, isRoutekitProblemError, jsonBodyCodec, resolveApiResponse, resolveApiResponseByStatus, resolveRoutekitProblemData, withQuery };
|