rouzer 3.2.0 → 4.0.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 +5 -4
- package/dist/client/index.d.ts +15 -21
- package/dist/client/index.js +5 -19
- package/dist/http.d.ts +0 -3
- package/dist/http.js +1 -8
- package/dist/response.d.ts +11 -2
- package/dist/types/args.d.ts +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/infer.d.ts +3 -8
- package/docs/context.md +19 -44
- package/package.json +1 -1
- package/dist/types/request.d.ts +0 -35
- package/dist/types/request.js +0 -1
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Rouzer lets you declare an HTTP route tree once and share its TypeScript types
|
|
4
4
|
and Zod validation between a Hattip-compatible server and a typed fetch client.
|
|
5
|
+
The client is always created from that route tree.
|
|
5
6
|
|
|
6
7
|
## What it does
|
|
7
8
|
|
|
@@ -45,7 +46,7 @@ Consider something else if:
|
|
|
45
46
|
- Zod v4 or newer
|
|
46
47
|
- a Hattip adapter when using `createRouter(...)`
|
|
47
48
|
- a Fetch API implementation when using `createClient(...)`
|
|
48
|
-
- an absolute `baseURL` for generated client URLs
|
|
49
|
+
- an absolute `baseURL` and shared `routes` tree for generated client URLs
|
|
49
50
|
|
|
50
51
|
## Installation
|
|
51
52
|
|
|
@@ -101,9 +102,9 @@ const { message } = await client.hello({
|
|
|
101
102
|
})
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
`handler` can be mounted with any Hattip adapter.
|
|
105
|
-
route arguments before `fetch`; server handlers validate matched path,
|
|
106
|
-
headers, and JSON bodies before your handler runs.
|
|
105
|
+
`handler` can be mounted with any Hattip adapter. Generated client action calls
|
|
106
|
+
validate route arguments before `fetch`; server handlers validate matched path,
|
|
107
|
+
query, headers, and JSON bodies before your handler runs.
|
|
107
108
|
|
|
108
109
|
### Typed status responses
|
|
109
110
|
|
package/dist/client/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { Promisable } from '../common.js';
|
|
|
2
2
|
import type { HttpAction, HttpResource, HttpRouteTree } from '../http.js';
|
|
3
3
|
import { type ClientResponsePlugin } from '../response.js';
|
|
4
4
|
import type { RouteArgs } from '../types/args.js';
|
|
5
|
-
import type { RouteRequest } from '../types/request.js';
|
|
6
5
|
import type { InferRouteResponse } from '../types/response.js';
|
|
7
6
|
import type { RouteSchema } from '../types/schema.js';
|
|
8
7
|
/** Client type inferred from an HTTP route tree passed to `createClient`. */
|
|
@@ -10,9 +9,8 @@ export type RouzerClient<TRoutes extends HttpRouteTree = Record<string, never>>
|
|
|
10
9
|
/**
|
|
11
10
|
* Create a typed fetch client for an HTTP route tree.
|
|
12
11
|
*
|
|
13
|
-
* @remarks The returned client
|
|
14
|
-
*
|
|
15
|
-
* tree and attaches direct action functions such as `client.users.list(...)`.
|
|
12
|
+
* @remarks The returned client mirrors the resource tree and attaches direct
|
|
13
|
+
* action functions such as `client.users.list(...)`.
|
|
16
14
|
*/
|
|
17
15
|
export declare function createClient<TRoutes extends HttpRouteTree = Record<string, never>>(config: {
|
|
18
16
|
/**
|
|
@@ -38,22 +36,22 @@ export declare function createClient<TRoutes extends HttpRouteTree = Record<stri
|
|
|
38
36
|
* await client.users.list({ query: { page: 1 } })
|
|
39
37
|
* ```
|
|
40
38
|
*/
|
|
41
|
-
routes
|
|
39
|
+
routes: TRoutes;
|
|
42
40
|
/** Response codec plugins used by generated action functions. */
|
|
43
41
|
plugins?: readonly ClientResponsePlugin[];
|
|
44
42
|
/**
|
|
45
|
-
* Custom handler for non-2xx responses from
|
|
46
|
-
*
|
|
43
|
+
* Custom handler for non-2xx responses from generated client action
|
|
44
|
+
* functions.
|
|
47
45
|
*
|
|
48
|
-
* @remarks When provided, the return value is returned from the
|
|
49
|
-
*
|
|
50
|
-
*
|
|
46
|
+
* @remarks When provided, the return value is returned from the client action
|
|
47
|
+
* as-is; Rouzer does not automatically parse a `Response` returned by this
|
|
48
|
+
* hook.
|
|
51
49
|
*/
|
|
52
50
|
onJsonError?: (response: Response) => Promisable<unknown>;
|
|
53
51
|
/** Custom `fetch` implementation to use for requests. */
|
|
54
52
|
fetch?: typeof globalThis.fetch;
|
|
55
53
|
}): ClientTree<TRoutes, ""> & {
|
|
56
|
-
|
|
54
|
+
clientConfig: {
|
|
57
55
|
/**
|
|
58
56
|
* Absolute base URL used for generated request URLs.
|
|
59
57
|
*
|
|
@@ -77,25 +75,21 @@ export declare function createClient<TRoutes extends HttpRouteTree = Record<stri
|
|
|
77
75
|
* await client.users.list({ query: { page: 1 } })
|
|
78
76
|
* ```
|
|
79
77
|
*/
|
|
80
|
-
routes
|
|
78
|
+
routes: TRoutes;
|
|
81
79
|
/** Response codec plugins used by generated action functions. */
|
|
82
80
|
plugins?: readonly ClientResponsePlugin[];
|
|
83
81
|
/**
|
|
84
|
-
* Custom handler for non-2xx responses from
|
|
85
|
-
*
|
|
82
|
+
* Custom handler for non-2xx responses from generated client action
|
|
83
|
+
* functions.
|
|
86
84
|
*
|
|
87
|
-
* @remarks When provided, the return value is returned from the
|
|
88
|
-
*
|
|
89
|
-
*
|
|
85
|
+
* @remarks When provided, the return value is returned from the client action
|
|
86
|
+
* as-is; Rouzer does not automatically parse a `Response` returned by this
|
|
87
|
+
* hook.
|
|
90
88
|
*/
|
|
91
89
|
onJsonError?: (response: Response) => Promisable<unknown>;
|
|
92
90
|
/** Custom `fetch` implementation to use for requests. */
|
|
93
91
|
fetch?: typeof globalThis.fetch;
|
|
94
92
|
};
|
|
95
|
-
request: <T extends RouteRequest>({ path: pathBuilder, method, args, schema, }: T) => Promise<Response & {
|
|
96
|
-
json(): Promise<T['$result']>;
|
|
97
|
-
}>;
|
|
98
|
-
json: <T extends RouteRequest>(props: T) => Promise<T['$result']>;
|
|
99
93
|
};
|
|
100
94
|
type Join<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
|
|
101
95
|
/** Client object shape produced from an HTTP route tree. */
|
package/dist/client/index.js
CHANGED
|
@@ -6,18 +6,15 @@ import { getResponseMapPluginIds, isErrorMarker, isResponseMap, } from '../respo
|
|
|
6
6
|
/**
|
|
7
7
|
* Create a typed fetch client for an HTTP route tree.
|
|
8
8
|
*
|
|
9
|
-
* @remarks The returned client
|
|
10
|
-
*
|
|
11
|
-
* tree and attaches direct action functions such as `client.users.list(...)`.
|
|
9
|
+
* @remarks The returned client mirrors the resource tree and attaches direct
|
|
10
|
+
* action functions such as `client.users.list(...)`.
|
|
12
11
|
*/
|
|
13
12
|
export function createClient(config) {
|
|
14
13
|
const baseURL = config.baseURL.replace(/\/?$/, '/');
|
|
15
14
|
const defaultHeaders = config.headers && shake(config.headers);
|
|
16
15
|
const fetch = config.fetch ?? globalThis.fetch;
|
|
17
16
|
const responsePlugins = createResponsePluginMap(config.plugins, 'client response');
|
|
18
|
-
|
|
19
|
-
validateClientResponsePlugins(config.routes, responsePlugins);
|
|
20
|
-
}
|
|
17
|
+
validateClientResponsePlugins(config.routes, responsePlugins);
|
|
21
18
|
async function request({ path: pathBuilder, method, args, schema, }) {
|
|
22
19
|
let { path, query, body, headers, ...init } = args;
|
|
23
20
|
if (schema.path) {
|
|
@@ -61,13 +58,6 @@ export function createClient(config) {
|
|
|
61
58
|
headers: (headers ?? defaultHeaders),
|
|
62
59
|
});
|
|
63
60
|
}
|
|
64
|
-
async function json(props) {
|
|
65
|
-
const response = await request(props);
|
|
66
|
-
if (!response.ok) {
|
|
67
|
-
return handleResponseError(response, props);
|
|
68
|
-
}
|
|
69
|
-
return response.json();
|
|
70
|
-
}
|
|
71
61
|
async function response(props) {
|
|
72
62
|
const httpResponse = await request(props);
|
|
73
63
|
const responseSchema = props.schema.response;
|
|
@@ -127,12 +117,8 @@ export function createClient(config) {
|
|
|
127
117
|
throw error;
|
|
128
118
|
}
|
|
129
119
|
return {
|
|
130
|
-
...(config.routes
|
|
131
|
-
|
|
132
|
-
: null),
|
|
133
|
-
config,
|
|
134
|
-
request,
|
|
135
|
-
json,
|
|
120
|
+
...connectTree(config.routes, '', request, response),
|
|
121
|
+
clientConfig: config,
|
|
136
122
|
};
|
|
137
123
|
}
|
|
138
124
|
function connectTree(tree, prefix, request, response) {
|
package/dist/http.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
-
import type { RouteRequestFactory } from './types/request.js';
|
|
3
2
|
import type { RouteSchema } from './types/schema.js';
|
|
4
3
|
/** HTTP methods supported by Rouzer action declarations. */
|
|
5
4
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
@@ -18,8 +17,6 @@ export type HttpAction<P extends string = string, T extends RouteSchema = RouteS
|
|
|
18
17
|
method: M;
|
|
19
18
|
/** Request validation and optional response type schema. */
|
|
20
19
|
schema: T;
|
|
21
|
-
/** Low-level request descriptor factory for this action. */
|
|
22
|
-
request: RouteRequestFactory<T, P>;
|
|
23
20
|
};
|
|
24
21
|
/**
|
|
25
22
|
* Path-scoped namespace in an HTTP route tree.
|
package/dist/http.js
CHANGED
|
@@ -34,12 +34,5 @@ function action(method, pathOrSchema, schema) {
|
|
|
34
34
|
? RoutePattern.parse(pathOrSchema)
|
|
35
35
|
: undefined;
|
|
36
36
|
schema ??= typeof pathOrSchema === 'string' ? {} : pathOrSchema;
|
|
37
|
-
|
|
38
|
-
schema,
|
|
39
|
-
path: path ?? RoutePattern.parse(''),
|
|
40
|
-
method,
|
|
41
|
-
args,
|
|
42
|
-
$result: undefined,
|
|
43
|
-
}));
|
|
44
|
-
return { kind: 'action', path, method, schema, request };
|
|
37
|
+
return { kind: 'action', path, method, schema };
|
|
45
38
|
}
|
package/dist/response.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { Promisable } from './common.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { RoutePattern } from '@remix-run/route-pattern';
|
|
3
|
+
import type { RouteArgs } from './types/args.js';
|
|
4
|
+
import type { RouteSchema } from './types/schema.js';
|
|
3
5
|
/** Runtime key carried by response plugin markers. */
|
|
4
6
|
export declare const responsePluginMarker: unique symbol;
|
|
5
7
|
/**
|
|
@@ -24,9 +26,16 @@ export type ClientResponsePlugin = {
|
|
|
24
26
|
/** Decode a successful `Response` into the client action result. */
|
|
25
27
|
decode(response: Response, context: {
|
|
26
28
|
marker: ResponsePluginMarker<any, any>;
|
|
27
|
-
request:
|
|
29
|
+
request: ClientResponsePluginRequest;
|
|
28
30
|
}): Promisable<unknown>;
|
|
29
31
|
};
|
|
32
|
+
/** Request metadata passed to client response plugins. */
|
|
33
|
+
export type ClientResponsePluginRequest = {
|
|
34
|
+
schema: RouteSchema;
|
|
35
|
+
path: RoutePattern;
|
|
36
|
+
method: string;
|
|
37
|
+
args: RouteArgs;
|
|
38
|
+
};
|
|
30
39
|
/** Router-side response plugin used by `createRouter({ plugins })`. */
|
|
31
40
|
export type RouterResponsePlugin = {
|
|
32
41
|
/** Stable response codec id matched against route response markers. */
|
package/dist/types/args.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ type MutationArgs<T> = T extends MutationRouteSchema ? T extends {
|
|
|
32
32
|
body?: unknown;
|
|
33
33
|
} : unknown;
|
|
34
34
|
/**
|
|
35
|
-
* Arguments accepted by a client action function
|
|
35
|
+
* Arguments accepted by a generated client action function.
|
|
36
36
|
*
|
|
37
37
|
* @remarks The type is derived from an action schema and route pattern. `path`,
|
|
38
38
|
* `query`, `body`, and `headers` are validated by the client before `fetch` when
|
package/dist/types/index.d.ts
CHANGED
package/dist/types/infer.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import type * as z from 'zod';
|
|
2
2
|
import type { MutationRouteSchema, RouteSchema } from './schema.js';
|
|
3
|
-
import type { RouteRequestFactory } from './request.js';
|
|
4
3
|
type InferRouteSchemaBody<TSchema> = TSchema extends MutationRouteSchema ? TSchema extends {
|
|
5
4
|
body: infer TBody;
|
|
6
5
|
} ? z.infer<TBody> : unknown : never;
|
|
7
|
-
type InferRouteArgsBody<TArgs> = TArgs extends {
|
|
8
|
-
body?: infer TBody;
|
|
9
|
-
} ? TBody : never;
|
|
10
6
|
/**
|
|
11
|
-
* Infer the request body type from an action schema
|
|
7
|
+
* Infer the request body type from an action schema.
|
|
12
8
|
*
|
|
13
9
|
* @remarks HTTP action schemas can be inspected with
|
|
14
|
-
* `InferRouteBody<typeof action.schema>`.
|
|
15
|
-
* infer their `body` argument type. Schemas without a body schema infer
|
|
10
|
+
* `InferRouteBody<typeof action.schema>`. Schemas without a body schema infer
|
|
16
11
|
* `unknown`.
|
|
17
12
|
*/
|
|
18
|
-
export type InferRouteBody<T> = T extends
|
|
13
|
+
export type InferRouteBody<T> = T extends RouteSchema ? InferRouteSchemaBody<T> : never;
|
|
19
14
|
export {};
|
package/docs/context.md
CHANGED
|
@@ -211,18 +211,16 @@ requests with an `Origin` header.
|
|
|
211
211
|
|
|
212
212
|
### Client
|
|
213
213
|
|
|
214
|
-
`createClient({ baseURL, routes })` creates
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
- `
|
|
219
|
-
|
|
220
|
-
- response-map support
|
|
221
|
-
|
|
222
|
-
- response plugin support
|
|
223
|
-
|
|
224
|
-
- a client tree that mirrors `routes`, with action functions such as
|
|
225
|
-
`client.profiles.get(args)` when `routes` is supplied
|
|
214
|
+
`createClient({ baseURL, routes })` creates a client tree that mirrors
|
|
215
|
+
`routes`, with action functions such as `client.profiles.get(args)`.
|
|
216
|
+
Generated action functions include:
|
|
217
|
+
|
|
218
|
+
- raw `Response` results for actions without a response schema
|
|
219
|
+
- parsed JSON and default non-2xx throwing for `$type<T>()` responses
|
|
220
|
+
- response-map support, returning `[error, value, status]` tuples for declared
|
|
221
|
+
statuses
|
|
222
|
+
- response plugin support, such as `ndjson.clientPlugin` for NDJSON response
|
|
223
|
+
streams
|
|
226
224
|
|
|
227
225
|
Prefer an absolute `baseURL` for generated client URLs:
|
|
228
226
|
|
|
@@ -235,7 +233,8 @@ const client = createClient({
|
|
|
235
233
|
|
|
236
234
|
Default headers can be supplied with `headers`, per-request headers are merged on
|
|
237
235
|
top, and a custom `fetch` implementation can be supplied for tests or non-browser
|
|
238
|
-
runtimes.
|
|
236
|
+
runtimes. The returned client exposes the original options as `clientConfig`, so
|
|
237
|
+
route actions named `config` remain available as `client.config(...)`.
|
|
239
238
|
|
|
240
239
|
## Lifecycle
|
|
241
240
|
|
|
@@ -260,9 +259,9 @@ string-coercion step.
|
|
|
260
259
|
|
|
261
260
|
## Common tasks
|
|
262
261
|
|
|
263
|
-
###
|
|
262
|
+
### Call client actions
|
|
264
263
|
|
|
265
|
-
Use client action functions for
|
|
264
|
+
Use generated client action functions for application calls:
|
|
266
265
|
|
|
267
266
|
```ts
|
|
268
267
|
await client.profiles.get({ path: { id: '42' } })
|
|
@@ -272,29 +271,6 @@ await client.profiles.update({
|
|
|
272
271
|
})
|
|
273
272
|
```
|
|
274
273
|
|
|
275
|
-
Use longhand calls when you need to choose response handling explicitly. The
|
|
276
|
-
action request factory must include the full path you want to call, so this style
|
|
277
|
-
is most convenient for top-level actions:
|
|
278
|
-
|
|
279
|
-
```ts
|
|
280
|
-
export const getProfile = http.get('profiles/:id', {
|
|
281
|
-
response: $type<Profile>(),
|
|
282
|
-
})
|
|
283
|
-
export const routes = { getProfile }
|
|
284
|
-
|
|
285
|
-
const response = await client.request(
|
|
286
|
-
routes.getProfile.request({ path: { id: '42' } })
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
const json = await client.json(
|
|
290
|
-
routes.getProfile.request({ path: { id: '42' } })
|
|
291
|
-
)
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Response maps and response plugins are applied by generated client action
|
|
295
|
-
functions. For longhand calls to mapped or plugin-backed routes, use
|
|
296
|
-
`client.request(...)` for the raw `Response` and decode the response yourself.
|
|
297
|
-
|
|
298
274
|
### Handle declared error responses
|
|
299
275
|
|
|
300
276
|
Use `$error<T>()` inside a response map when an error status is part of the route
|
|
@@ -420,7 +396,7 @@ custom headers. Return a plain value for the default `Response.json(value)` path
|
|
|
420
396
|
|
|
421
397
|
### Customize JSON errors
|
|
422
398
|
|
|
423
|
-
By default,
|
|
399
|
+
By default, generated client action functions throw for
|
|
424
400
|
non-2xx responses that are not declared in a response map. If the response body
|
|
425
401
|
is JSON, its properties are copied onto the thrown `Error`.
|
|
426
402
|
|
|
@@ -484,7 +460,7 @@ await client.profiles.update({
|
|
|
484
460
|
- Export route trees from a small shared module and import that module on both
|
|
485
461
|
server and client.
|
|
486
462
|
- Use `rouzer/http` actions for routes that are registered with
|
|
487
|
-
`createRouter().use(...)` or `createClient({ routes })
|
|
463
|
+
`createRouter().use(...)` or the required `createClient({ routes })` option.
|
|
488
464
|
- Add Zod schemas when you need runtime guarantees; rely on inferred path params
|
|
489
465
|
only when string params are sufficient.
|
|
490
466
|
- Use `response: $type<T>()` for JSON endpoints that should have typed client
|
|
@@ -513,11 +489,10 @@ await client.profiles.update({
|
|
|
513
489
|
- Pathname route patterns expect an absolute client `baseURL`.
|
|
514
490
|
- Resource and action keys are API names only; paths come from the pattern
|
|
515
491
|
strings passed to `http.resource(...)` and action helpers.
|
|
516
|
-
- Nested action `.request(...)` factories do not include parent resource paths;
|
|
517
|
-
prefer client action functions for nested resources.
|
|
518
492
|
- Extra `RequestInit` fields in route args, such as `signal` or `credentials`,
|
|
519
|
-
are forwarded by `createClient`; `method
|
|
520
|
-
|
|
493
|
+
are forwarded by `createClient`; `method` and `body` are reserved for Rouzer's
|
|
494
|
+
action metadata and validated call arguments. Use route args or client defaults
|
|
495
|
+
for request headers.
|
|
521
496
|
- The HTTP action API has no `ALL` fallback route. Declare explicit actions for
|
|
522
497
|
supported methods.
|
|
523
498
|
- Rouzer does not automatically set `Access-Control-Allow-Credentials`; set it in
|
package/package.json
CHANGED
package/dist/types/request.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
-
import type { RouteArgs } from './args.js';
|
|
3
|
-
import type { InferRouteResponse } from './response.js';
|
|
4
|
-
import type { RouteSchema } from './schema.js';
|
|
5
|
-
/**
|
|
6
|
-
* Request descriptor produced by an HTTP action request factory.
|
|
7
|
-
*
|
|
8
|
-
* @remarks Pass this object to `client.request(...)` for a raw `Response` or
|
|
9
|
-
* `client.json(...)` for parsed JSON handling.
|
|
10
|
-
*/
|
|
11
|
-
export type RouteRequest<TResult = any> = {
|
|
12
|
-
/** Method schema used for client-side validation. */
|
|
13
|
-
schema: RouteSchema;
|
|
14
|
-
/** Parsed route pattern used to generate the request URL. */
|
|
15
|
-
path: RoutePattern;
|
|
16
|
-
/** HTTP method to send. */
|
|
17
|
-
method: string;
|
|
18
|
-
/** Validated route arguments and request options. */
|
|
19
|
-
args: RouteArgs;
|
|
20
|
-
/** Phantom result type consumed by `client.json(...)`. */
|
|
21
|
-
$result: TResult;
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Callable factory attached to an HTTP action.
|
|
25
|
-
*
|
|
26
|
-
* @remarks Calling a factory validates no data by itself; it creates a typed
|
|
27
|
-
* `RouteRequest` descriptor for `createClient` to validate and send.
|
|
28
|
-
*/
|
|
29
|
-
export type RouteRequestFactory<T extends RouteSchema, P extends string> = {
|
|
30
|
-
(...p: RouteArgs<T, P> extends infer TArgs ? {} extends TArgs ? [args?: TArgs] : [args: TArgs] : never): RouteRequest<InferRouteResponse<T>>;
|
|
31
|
-
/** Inferred argument type for this request factory. */
|
|
32
|
-
$args: RouteArgs<T, P>;
|
|
33
|
-
/** Inferred response type for this request factory. */
|
|
34
|
-
$response: InferRouteResponse<T>;
|
|
35
|
-
};
|
package/dist/types/request.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|