rouzer 3.0.1 → 3.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 +43 -5
- package/dist/client/index.d.ts +20 -9
- package/dist/client/index.js +55 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ndjson.d.ts +54 -0
- package/dist/ndjson.js +161 -0
- package/dist/response.d.ts +47 -0
- package/dist/response.js +35 -0
- package/dist/server/router.d.ts +5 -2
- package/dist/server/router.js +31 -5
- package/dist/type.d.ts +1 -1
- package/dist/types/handler.d.ts +3 -3
- package/dist/types/request.d.ts +1 -1
- package/dist/types/response.d.ts +10 -2
- package/dist/types/schema.d.ts +11 -6
- package/docs/context.md +116 -25
- package/examples/ndjson-stream.ts +68 -0
- package/package.json +6 -2
- package/dist/internal.d.ts +0 -17
- package/dist/internal.js +0 -1
- package/dist/server/types.d.ts +0 -42
- package/dist/server/types.js +0 -1
- package/dist/types.d.ts +0 -140
- package/dist/types.js +0 -1
package/dist/type.d.ts
CHANGED
package/dist/types/handler.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { AnyMiddlewareChain, MiddlewareContext } from 'alien-middleware';
|
|
|
3
3
|
import type * as z from 'zod';
|
|
4
4
|
import { Promisable } from '../common.js';
|
|
5
5
|
import type { HttpAction } from '../http.js';
|
|
6
|
-
import type {
|
|
6
|
+
import type { InferRouteHandlerResult } from './response.js';
|
|
7
7
|
import type { RouteSchema } from './schema.js';
|
|
8
8
|
type RequestContext<TMiddleware extends AnyMiddlewareChain> = MiddlewareContext<TMiddleware>;
|
|
9
9
|
export type RouteRequestHandler<TMiddleware extends AnyMiddlewareChain, TArgs extends object, TResult> = (context: RequestContext<TMiddleware> & TArgs) => Promisable<TResult | Response>;
|
|
@@ -17,7 +17,7 @@ export type InferActionHandler<TMiddleware extends AnyMiddlewareChain, TAction e
|
|
|
17
17
|
headers: TAction['schema'] extends {
|
|
18
18
|
headers: any;
|
|
19
19
|
} ? z.infer<TAction['schema']['headers']> : undefined;
|
|
20
|
-
},
|
|
20
|
+
}, InferRouteHandlerResult<Extract<TAction['schema'], RouteSchema>>> : RouteRequestHandler<TMiddleware, {
|
|
21
21
|
path: TAction['schema'] extends {
|
|
22
22
|
path: any;
|
|
23
23
|
} ? z.infer<TAction['schema']['path']> : MatchParams<TPath>;
|
|
@@ -27,5 +27,5 @@ export type InferActionHandler<TMiddleware extends AnyMiddlewareChain, TAction e
|
|
|
27
27
|
headers: TAction['schema'] extends {
|
|
28
28
|
headers: any;
|
|
29
29
|
} ? z.infer<TAction['schema']['headers']> : undefined;
|
|
30
|
-
},
|
|
30
|
+
}, InferRouteHandlerResult<Extract<TAction['schema'], RouteSchema>>>;
|
|
31
31
|
export {};
|
package/dist/types/request.d.ts
CHANGED
|
@@ -30,6 +30,6 @@ export type RouteRequestFactory<T extends RouteSchema, P extends string> = {
|
|
|
30
30
|
(...p: RouteArgs<T, P> extends infer TArgs ? {} extends TArgs ? [args?: TArgs] : [args: TArgs] : never): RouteRequest<InferRouteResponse<T>>;
|
|
31
31
|
/** Inferred argument type for this request factory. */
|
|
32
32
|
$args: RouteArgs<T, P>;
|
|
33
|
-
/** Inferred
|
|
33
|
+
/** Inferred response type for this request factory. */
|
|
34
34
|
$response: InferRouteResponse<T>;
|
|
35
35
|
};
|
package/dist/types/response.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ResponsePluginMarker, RouteSchema, Unchecked } from './schema.js';
|
|
2
2
|
/** `Response` whose `.json()` method resolves to a known payload type. */
|
|
3
3
|
export type RouteResponse<TResult = any> = Response & {
|
|
4
4
|
json(): Promise<TResult>;
|
|
5
5
|
};
|
|
6
|
-
/** Infer the
|
|
6
|
+
/** Infer the client response type from an action schema. */
|
|
7
7
|
export type InferRouteResponse<T extends RouteSchema> = T extends {
|
|
8
|
+
response: ResponsePluginMarker<infer TClient, any>;
|
|
9
|
+
} ? TClient : T extends {
|
|
10
|
+
response: Unchecked<infer TResponse>;
|
|
11
|
+
} ? TResponse : void;
|
|
12
|
+
/** Infer the non-`Response` handler result type from an action schema. */
|
|
13
|
+
export type InferRouteHandlerResult<T extends RouteSchema> = T extends {
|
|
14
|
+
response: ResponsePluginMarker<any, infer TRouter>;
|
|
15
|
+
} ? TRouter : T extends {
|
|
8
16
|
response: Unchecked<infer TResponse>;
|
|
9
17
|
} ? TResponse : void;
|
package/dist/types/schema.d.ts
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
|
-
import { Unchecked } from '../common.js';
|
|
2
|
+
import type { Unchecked } from '../common.js';
|
|
3
|
+
import type { ResponsePluginMarker } from '../response.js';
|
|
3
4
|
/**
|
|
4
|
-
* Compile-time-only marker used by `$type<T>()` for unchecked response
|
|
5
|
+
* Compile-time-only marker used by `$type<T>()` for unchecked JSON response
|
|
6
|
+
* types.
|
|
5
7
|
*
|
|
6
8
|
* @remarks Application code should usually call `$type<T>()` instead of naming
|
|
7
9
|
* this marker directly.
|
|
8
10
|
*/
|
|
9
11
|
export type { Unchecked };
|
|
12
|
+
export type { ResponsePluginMarker };
|
|
13
|
+
/** Response marker accepted by HTTP action schemas. */
|
|
14
|
+
export type RouteResponseSchema = Unchecked<any> | ResponsePluginMarker<any, any>;
|
|
10
15
|
/** Schema shape for `GET` route methods. */
|
|
11
16
|
export type QueryRouteSchema = {
|
|
12
17
|
/** Optional Zod object used to validate path params. */
|
|
@@ -17,8 +22,8 @@ export type QueryRouteSchema = {
|
|
|
17
22
|
body?: never;
|
|
18
23
|
/** Optional Zod object used to validate request headers. */
|
|
19
24
|
headers?: z.ZodObject<any>;
|
|
20
|
-
/** Optional compile-time-only JSON response type marker. */
|
|
21
|
-
response?:
|
|
25
|
+
/** Optional compile-time-only JSON or plugin response type marker. */
|
|
26
|
+
response?: RouteResponseSchema;
|
|
22
27
|
};
|
|
23
28
|
/** Schema shape for mutation route methods. */
|
|
24
29
|
export type MutationRouteSchema = {
|
|
@@ -30,8 +35,8 @@ export type MutationRouteSchema = {
|
|
|
30
35
|
body?: z.ZodType<any, any>;
|
|
31
36
|
/** Optional Zod object used to validate request headers. */
|
|
32
37
|
headers?: z.ZodObject<any>;
|
|
33
|
-
/** Optional compile-time-only JSON response type marker. */
|
|
34
|
-
response?:
|
|
38
|
+
/** Optional compile-time-only JSON or plugin response type marker. */
|
|
39
|
+
response?: RouteResponseSchema;
|
|
35
40
|
};
|
|
36
41
|
/** Any HTTP action schema Rouzer can execute. */
|
|
37
42
|
export type RouteSchema = QueryRouteSchema | MutationRouteSchema;
|
package/docs/context.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Rouzer is for applications that want one TypeScript HTTP route tree to drive
|
|
4
4
|
both the server and the client that calls it. A route tree combines URL
|
|
5
|
-
patterns, named actions, HTTP method schemas, and optional compile-time
|
|
6
|
-
types.
|
|
5
|
+
patterns, named actions, HTTP method schemas, and optional compile-time JSON or
|
|
6
|
+
NDJSON response types.
|
|
7
7
|
|
|
8
8
|
## When to use Rouzer
|
|
9
9
|
|
|
@@ -18,8 +18,8 @@ Use Rouzer when:
|
|
|
18
18
|
produced by a separate OpenAPI build step
|
|
19
19
|
|
|
20
20
|
Rouzer is not a response validation library, an OpenAPI generator, or a complete
|
|
21
|
-
server framework. It focuses on typed route contracts, validation,
|
|
22
|
-
small client wrapper.
|
|
21
|
+
server framework. It focuses on typed route contracts, request validation,
|
|
22
|
+
routing, and a small client wrapper.
|
|
23
23
|
|
|
24
24
|
## Core abstractions
|
|
25
25
|
|
|
@@ -79,9 +79,9 @@ them out of resource/base-path composition.
|
|
|
79
79
|
|
|
80
80
|
Method schemas describe the request pieces Rouzer should validate:
|
|
81
81
|
|
|
82
|
-
| Action helper
|
|
83
|
-
|
|
|
84
|
-
| `http.get(...)`
|
|
82
|
+
| Action helper | Request schemas | Notes |
|
|
83
|
+
| --------------------------------- | -------------------------------------- | ---------------- |
|
|
84
|
+
| `http.get(...)` | `path`, `query`, `headers`, `response` | No request body. |
|
|
85
85
|
| `http.post/put/patch/delete(...)` | `path`, `body`, `headers`, `response` | No query schema. |
|
|
86
86
|
|
|
87
87
|
If you omit a `path` schema, TypeScript infers path params from the pattern and
|
|
@@ -92,15 +92,39 @@ The HTTP action API models explicit operations. It does not expose the old
|
|
|
92
92
|
method-map `ALL` fallback route shape; declare the concrete methods your client
|
|
93
93
|
and server support.
|
|
94
94
|
|
|
95
|
-
### `$type<T>()`
|
|
95
|
+
### `$type<T>()` and `ndjson.$type<T>()`
|
|
96
96
|
|
|
97
|
-
`response: $type<T>()` is a TypeScript-only marker
|
|
98
|
-
action functions what response payload type to
|
|
99
|
-
validate response bodies at runtime.
|
|
97
|
+
`response: $type<T>()` is a TypeScript-only marker for JSON response payloads.
|
|
98
|
+
It tells handlers and client action functions what response payload type to
|
|
99
|
+
expect, but Rouzer does not validate response bodies at runtime.
|
|
100
|
+
|
|
101
|
+
`response: ndjson.$type<T>()` is a TypeScript-only marker for newline-delimited
|
|
102
|
+
JSON response streams from the `rouzer/ndjson` subpath. Register
|
|
103
|
+
`ndjson.routerPlugin` with `createRouter(...)` and `ndjson.clientPlugin` with
|
|
104
|
+
`createClient(...)` for routes that use this marker. Handlers return an
|
|
105
|
+
`Iterable<T>` or `AsyncIterable<T>`; Rouzer serializes each item as one JSON line
|
|
106
|
+
and sets the response content type to `application/x-ndjson; charset=utf-8`.
|
|
107
|
+
Client action functions resolve to an `AsyncIterable<T>` parsed from the
|
|
108
|
+
response body. Streamed items are parsed as JSON but are not validated against a
|
|
109
|
+
Zod schema.
|
|
100
110
|
|
|
101
111
|
Actions without a `response` marker return a raw `Response` from client action
|
|
102
|
-
functions. Actions with
|
|
103
|
-
and return parsed JSON typed as `T`.
|
|
112
|
+
functions. Actions with `response: $type<T>()` use `client.json(...)` under the
|
|
113
|
+
hood and return parsed JSON typed as `T`.
|
|
114
|
+
|
|
115
|
+
### Response plugins
|
|
116
|
+
|
|
117
|
+
Response plugins add non-JSON response codecs without changing route matching or
|
|
118
|
+
request validation. A plugin package provides a compile-time response marker and
|
|
119
|
+
matching runtime plugins. For NDJSON, those are `ndjson.$type<T>()`,
|
|
120
|
+
`ndjson.routerPlugin`, and `ndjson.clientPlugin`.
|
|
121
|
+
|
|
122
|
+
The router plugin encodes non-`Response` handler results into an HTTP `Response`.
|
|
123
|
+
The client plugin decodes successful HTTP responses for generated client action
|
|
124
|
+
functions. Rouzer validates plugin registration when routes are attached to a
|
|
125
|
+
router or client, so routes that use an unregistered response marker fail fast
|
|
126
|
+
instead of falling back to JSON. Response plugins do not automatically validate
|
|
127
|
+
response payloads unless the plugin itself implements validation.
|
|
104
128
|
|
|
105
129
|
### Router
|
|
106
130
|
|
|
@@ -133,7 +157,10 @@ Handlers receive a context typed from middleware plus the action schema:
|
|
|
133
157
|
- `GET` handlers receive `ctx.path`, `ctx.query`, and `ctx.headers`
|
|
134
158
|
- mutation handlers receive `ctx.path`, `ctx.body`, and `ctx.headers`
|
|
135
159
|
- handlers may return a plain JSON-serializable value or a `Response`
|
|
160
|
+
- `ndjson.$type<T>()` handlers return an `Iterable<T>` or `AsyncIterable<T>`
|
|
161
|
+
unless they return a custom `Response`
|
|
136
162
|
- plain values are returned with `Response.json(value)`
|
|
163
|
+
- NDJSON iterables are returned as `application/x-ndjson` streams
|
|
137
164
|
- return a `Response` when you need custom status, headers, or body handling
|
|
138
165
|
|
|
139
166
|
`basePath` is prepended to route tree paths, `debug` adds matched-route debug
|
|
@@ -148,6 +175,8 @@ requests with an `Origin` header.
|
|
|
148
175
|
request factory contains the full path you want to call
|
|
149
176
|
- `client.json(action.request(args))` for parsed JSON and default non-2xx
|
|
150
177
|
throwing
|
|
178
|
+
- response plugin support for generated client action functions, such as
|
|
179
|
+
`ndjson.clientPlugin` for NDJSON response streams
|
|
151
180
|
- a client tree that mirrors `routes`, with action functions such as
|
|
152
181
|
`client.profiles.get(args)` when `routes` is supplied
|
|
153
182
|
|
|
@@ -167,14 +196,18 @@ runtimes.
|
|
|
167
196
|
## Lifecycle
|
|
168
197
|
|
|
169
198
|
1. Define shared HTTP actions/resources with `rouzer/http` and Zod schemas.
|
|
170
|
-
2. Attach that route tree to a server with `createRouter().use(routes, handlers)
|
|
171
|
-
|
|
199
|
+
2. Attach that route tree to a server with `createRouter().use(routes, handlers)`
|
|
200
|
+
or `createRouter({ plugins }).use(routes, handlers)` when response plugins
|
|
201
|
+
are needed.
|
|
202
|
+
3. Create a client with the same route tree, plus matching client response
|
|
203
|
+
plugins when needed.
|
|
172
204
|
4. Client action calls validate `path`, `query`, `body`, and `headers` before
|
|
173
205
|
`fetch`.
|
|
174
206
|
5. The router matches the request, validates the matched inputs, and calls the
|
|
175
207
|
handler.
|
|
176
|
-
6. Plain handler results become JSON responses
|
|
177
|
-
through
|
|
208
|
+
6. Plain handler results become JSON responses, plugin handler results become
|
|
209
|
+
plugin-encoded responses, and explicit `Response` objects pass through
|
|
210
|
+
unchanged.
|
|
178
211
|
|
|
179
212
|
On the server, `path`, `query`, and `headers` values originate as strings. Rouzer
|
|
180
213
|
coerces Zod `number` schemas with `Number(value)` and Zod `boolean` schemas from
|
|
@@ -214,6 +247,55 @@ const json = await client.json(
|
|
|
214
247
|
)
|
|
215
248
|
```
|
|
216
249
|
|
|
250
|
+
Response plugins are applied by generated client action functions. For longhand
|
|
251
|
+
calls to plugin-backed routes, use `client.request(...)` for the raw `Response`
|
|
252
|
+
and call the plugin subpath's decoder yourself.
|
|
253
|
+
|
|
254
|
+
### Stream newline-delimited JSON
|
|
255
|
+
|
|
256
|
+
Use `ndjson.$type<T>()` when a handler should produce a sequence of JSON values
|
|
257
|
+
without buffering the whole response:
|
|
258
|
+
|
|
259
|
+
```ts
|
|
260
|
+
import { createClient, createRouter } from 'rouzer'
|
|
261
|
+
import * as http from 'rouzer/http'
|
|
262
|
+
import * as ndjson from 'rouzer/ndjson'
|
|
263
|
+
|
|
264
|
+
export const events = http.get('events', {
|
|
265
|
+
response: ndjson.$type<{ id: number; message: string }>(),
|
|
266
|
+
})
|
|
267
|
+
export const routes = { events }
|
|
268
|
+
|
|
269
|
+
createRouter({ plugins: [ndjson.routerPlugin] }).use(routes, {
|
|
270
|
+
async *events() {
|
|
271
|
+
yield { id: 1, message: 'ready' }
|
|
272
|
+
yield { id: 2, message: 'done' }
|
|
273
|
+
},
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
const client = createClient({
|
|
277
|
+
baseURL: 'https://example.com/api/',
|
|
278
|
+
routes,
|
|
279
|
+
plugins: [ndjson.clientPlugin],
|
|
280
|
+
})
|
|
281
|
+
for await (const event of await client.events()) {
|
|
282
|
+
console.log(event.message)
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
A complete runnable version lives in
|
|
287
|
+
[`examples/ndjson-stream.ts`](../examples/ndjson-stream.ts).
|
|
288
|
+
|
|
289
|
+
Rouzer's decoder accepts `\n` and `\r\n`, handles UTF-8 chunk boundaries, and
|
|
290
|
+
throws a `SyntaxError` with a line number for malformed JSON. If a consumer stops
|
|
291
|
+
reading early, the response body is cancelled.
|
|
292
|
+
|
|
293
|
+
Rouzer does not convert handler or generator failures into extra NDJSON items. If
|
|
294
|
+
an async generator throws after the response starts, the response stream errors
|
|
295
|
+
and the client's `for await` loop throws. Model application-level stream errors
|
|
296
|
+
as part of your item type, for example `{ type: 'error'; message: string }`, when
|
|
297
|
+
clients should receive them as data.
|
|
298
|
+
|
|
217
299
|
### Group resource actions
|
|
218
300
|
|
|
219
301
|
Use resources when the public API reads better as a tree or when actions share
|
|
@@ -239,17 +321,18 @@ custom headers. Return a plain value for the default `Response.json(value)` path
|
|
|
239
321
|
|
|
240
322
|
### Customize JSON errors
|
|
241
323
|
|
|
242
|
-
By default, `client.json(...)`
|
|
243
|
-
is JSON, its properties are copied onto
|
|
324
|
+
By default, `client.json(...)` and generated client action functions throw for
|
|
325
|
+
non-2xx responses. If the response body is JSON, its properties are copied onto
|
|
326
|
+
the thrown `Error`.
|
|
244
327
|
|
|
245
|
-
`onJsonError` can override that behavior. Its return value is returned from
|
|
246
|
-
|
|
247
|
-
|
|
328
|
+
`onJsonError` can override that behavior. Its return value is returned from the
|
|
329
|
+
response helper as-is; Rouzer does not automatically parse a returned `Response`
|
|
330
|
+
from `onJsonError`.
|
|
248
331
|
|
|
249
|
-
###
|
|
332
|
+
### v2->v3 migration
|
|
250
333
|
|
|
251
334
|
Rouzer now uses action/resource route trees for router registration and client
|
|
252
|
-
shorthands.
|
|
335
|
+
shorthands. In the v2->v3 migration, a method-map route such as this:
|
|
253
336
|
|
|
254
337
|
```ts
|
|
255
338
|
export const profileRoute = route('profiles/:id', {
|
|
@@ -307,6 +390,9 @@ await client.profiles.update({
|
|
|
307
390
|
only when string params are sufficient.
|
|
308
391
|
- Use `response: $type<T>()` for JSON endpoints that should have typed client
|
|
309
392
|
action functions.
|
|
393
|
+
- Use `response: ndjson.$type<T>()` plus `ndjson.routerPlugin` and
|
|
394
|
+
`ndjson.clientPlugin` for response streams where each line is a JSON value and
|
|
395
|
+
the client should consume an `AsyncIterable<T>`.
|
|
310
396
|
- Name actions after domain operations (`get`, `list`, `update`, `archive`) and
|
|
311
397
|
let `http.get/post/put/patch/delete` own the transport method.
|
|
312
398
|
- Set `content-type: application/json` yourself when your server or middleware
|
|
@@ -314,7 +400,12 @@ await client.profiles.update({
|
|
|
314
400
|
|
|
315
401
|
## Constraints and gotchas
|
|
316
402
|
|
|
317
|
-
- `$type<T>()`
|
|
403
|
+
- `$type<T>()` and `ndjson.$type<T>()` are compile-time only and do not validate
|
|
404
|
+
response payloads or streamed items.
|
|
405
|
+
- NDJSON support is for response streams; request bodies still use the existing
|
|
406
|
+
JSON body schema path.
|
|
407
|
+
- Routes that use a response plugin fail fast if the matching client or router
|
|
408
|
+
plugin is not registered.
|
|
318
409
|
- Pathname route patterns expect an absolute client `baseURL`.
|
|
319
410
|
- Resource and action keys are API names only; paths come from the pattern
|
|
320
411
|
strings passed to `http.resource(...)` and action helpers.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { HattipHandler } from '@hattip/core'
|
|
2
|
+
import { createClient, createRouter } from 'rouzer'
|
|
3
|
+
import * as http from 'rouzer/http'
|
|
4
|
+
import * as ndjson from 'rouzer/ndjson'
|
|
5
|
+
|
|
6
|
+
type Event = {
|
|
7
|
+
id: number
|
|
8
|
+
message: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const events = http.get('events', {
|
|
12
|
+
response: ndjson.$type<Event>(),
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
export const routes = { events }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tiny Hattip adapter used only to keep this example self-contained. Real apps
|
|
19
|
+
* mount the handler with a Hattip adapter for their runtime.
|
|
20
|
+
*/
|
|
21
|
+
function createLocalFetch(handler: HattipHandler): typeof fetch {
|
|
22
|
+
return async (input, init) => {
|
|
23
|
+
const request = new Request(input, init)
|
|
24
|
+
const response = await handler({
|
|
25
|
+
request,
|
|
26
|
+
ip: '127.0.0.1',
|
|
27
|
+
platform: undefined,
|
|
28
|
+
env() {
|
|
29
|
+
return undefined
|
|
30
|
+
},
|
|
31
|
+
passThrough() {},
|
|
32
|
+
waitUntil(promise) {
|
|
33
|
+
void promise
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
return response ?? new Response(null, { status: 404 })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function collect<T>(source: AsyncIterable<T>) {
|
|
42
|
+
const values: T[] = []
|
|
43
|
+
for await (const value of source) {
|
|
44
|
+
values.push(value)
|
|
45
|
+
}
|
|
46
|
+
return values
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function runNdjsonStreamExample() {
|
|
50
|
+
const handler = createRouter({
|
|
51
|
+
basePath: 'api/',
|
|
52
|
+
plugins: [ndjson.routerPlugin],
|
|
53
|
+
}).use(routes, {
|
|
54
|
+
async *events() {
|
|
55
|
+
yield { id: 1, message: 'ready' }
|
|
56
|
+
yield { id: 2, message: 'done' }
|
|
57
|
+
},
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const client = createClient({
|
|
61
|
+
baseURL: 'https://example.test/api/',
|
|
62
|
+
routes,
|
|
63
|
+
plugins: [ndjson.clientPlugin],
|
|
64
|
+
fetch: createLocalFetch(handler),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
return collect(await client.events())
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rouzer",
|
|
3
|
-
"version": "3.0
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"./http": {
|
|
11
11
|
"types": "./dist/http.d.ts",
|
|
12
12
|
"import": "./dist/http.js"
|
|
13
|
+
},
|
|
14
|
+
"./ndjson": {
|
|
15
|
+
"types": "./dist/ndjson.d.ts",
|
|
16
|
+
"import": "./dist/ndjson.js"
|
|
13
17
|
}
|
|
14
18
|
},
|
|
15
19
|
"peerDependencies": {
|
|
@@ -47,7 +51,7 @@
|
|
|
47
51
|
"!*.tsbuildinfo"
|
|
48
52
|
],
|
|
49
53
|
"scripts": {
|
|
50
|
-
"build": "tsgo -b tsconfig.json",
|
|
54
|
+
"build": "rm -rf dist && tsgo -b tsconfig.json",
|
|
51
55
|
"test": "vitest run"
|
|
52
56
|
}
|
|
53
57
|
}
|
package/dist/internal.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { RoutePattern } from '@remix-run/route-pattern';
|
|
2
|
-
import type { RouteRequestFactory, RouteSchema } from './types.js';
|
|
3
|
-
/** @internal */
|
|
4
|
-
export type RouteSchemaMap = {
|
|
5
|
-
GET?: RouteSchema;
|
|
6
|
-
POST?: RouteSchema;
|
|
7
|
-
PUT?: RouteSchema;
|
|
8
|
-
PATCH?: RouteSchema;
|
|
9
|
-
DELETE?: RouteSchema;
|
|
10
|
-
};
|
|
11
|
-
/** @internal */
|
|
12
|
-
export type Route<P extends string = string, T extends RouteSchemaMap = RouteSchemaMap> = {
|
|
13
|
-
path: RoutePattern<P>;
|
|
14
|
-
methods: T;
|
|
15
|
-
} & {
|
|
16
|
-
[K in keyof T]: RouteRequestFactory<Extract<T[K], RouteSchema>, P>;
|
|
17
|
-
};
|
package/dist/internal.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/server/types.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { MatchParams } from '@remix-run/route-pattern/match';
|
|
2
|
-
import type { AnyMiddlewareChain, MiddlewareChain, MiddlewareContext } from 'alien-middleware';
|
|
3
|
-
import type * as z from 'zod';
|
|
4
|
-
import { Promisable } from '../common.js';
|
|
5
|
-
import type { HttpAction, HttpResource, HttpRouteTree } from '../http.js';
|
|
6
|
-
import type { InferRouteResponse, RouteSchema } from '../types.js';
|
|
7
|
-
type RequestContext<TMiddleware extends AnyMiddlewareChain> = MiddlewareContext<TMiddleware>;
|
|
8
|
-
type RouteRequestHandler<TMiddleware extends AnyMiddlewareChain, TArgs extends object, TResult> = (context: RequestContext<TMiddleware> & TArgs) => Promisable<TResult | Response>;
|
|
9
|
-
type InferActionHandler<TMiddleware extends AnyMiddlewareChain, TAction extends HttpAction, TPath extends string> = TAction['method'] extends 'GET' ? RouteRequestHandler<TMiddleware, {
|
|
10
|
-
path: TAction['schema'] extends {
|
|
11
|
-
path: any;
|
|
12
|
-
} ? z.infer<TAction['schema']['path']> : MatchParams<TPath>;
|
|
13
|
-
query: TAction['schema'] extends {
|
|
14
|
-
query: any;
|
|
15
|
-
} ? z.infer<TAction['schema']['query']> : undefined;
|
|
16
|
-
headers: TAction['schema'] extends {
|
|
17
|
-
headers: any;
|
|
18
|
-
} ? z.infer<TAction['schema']['headers']> : undefined;
|
|
19
|
-
}, InferRouteResponse<Extract<TAction['schema'], RouteSchema>>> : RouteRequestHandler<TMiddleware, {
|
|
20
|
-
path: TAction['schema'] extends {
|
|
21
|
-
path: any;
|
|
22
|
-
} ? z.infer<TAction['schema']['path']> : MatchParams<TPath>;
|
|
23
|
-
body: TAction['schema'] extends {
|
|
24
|
-
body: any;
|
|
25
|
-
} ? z.infer<TAction['schema']['body']> : undefined;
|
|
26
|
-
headers: TAction['schema'] extends {
|
|
27
|
-
headers: any;
|
|
28
|
-
} ? z.infer<TAction['schema']['headers']> : undefined;
|
|
29
|
-
}, InferRouteResponse<Extract<TAction['schema'], RouteSchema>>>;
|
|
30
|
-
type Join<A extends string, B extends string> = A extends '' ? B : B extends '' ? A : `${A}/${B}`;
|
|
31
|
-
/**
|
|
32
|
-
* Handler map shape required by `createRouter().use(routes, handlers)`.
|
|
33
|
-
*
|
|
34
|
-
* @remarks The handler object mirrors the HTTP route tree. Resource nodes become
|
|
35
|
-
* nested handler objects, while action nodes become direct handler functions.
|
|
36
|
-
* Handler context is inferred from middleware plus accumulated path params,
|
|
37
|
-
* query/body schemas, and header schemas.
|
|
38
|
-
*/
|
|
39
|
-
export type RouteRequestHandlerMap<TRoutes extends HttpRouteTree = HttpRouteTree, TMiddleware extends AnyMiddlewareChain = MiddlewareChain, TPrefix extends string = ''> = {
|
|
40
|
-
[K in keyof TRoutes]: TRoutes[K] extends HttpResource<infer P, infer C> ? RouteRequestHandlerMap<C, TMiddleware, Join<TPrefix, P>> : TRoutes[K] extends HttpAction<infer P, any, any> ? InferActionHandler<TMiddleware, TRoutes[K], Join<TPrefix, P>> : never;
|
|
41
|
-
};
|
|
42
|
-
export {};
|
package/dist/server/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types.d.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import type { MatchParams } from '@remix-run/route-pattern/match';
|
|
2
|
-
import { RoutePattern } from '@remix-run/route-pattern';
|
|
3
|
-
import * as z from 'zod';
|
|
4
|
-
import { Unchecked } from './common.js';
|
|
5
|
-
/**
|
|
6
|
-
* Compile-time-only marker used by `$type<T>()` for unchecked response types.
|
|
7
|
-
*
|
|
8
|
-
* @remarks Application code should usually call `$type<T>()` instead of naming
|
|
9
|
-
* this marker directly.
|
|
10
|
-
*/
|
|
11
|
-
export type { Unchecked };
|
|
12
|
-
/** Schema shape for `GET` route methods. */
|
|
13
|
-
export type QueryRouteSchema = {
|
|
14
|
-
/** Optional Zod object used to validate path params. */
|
|
15
|
-
path?: z.ZodObject<any>;
|
|
16
|
-
/** Optional Zod object used to validate URL query params. */
|
|
17
|
-
query?: z.ZodObject<any>;
|
|
18
|
-
/** `GET` routes do not accept request bodies. */
|
|
19
|
-
body?: never;
|
|
20
|
-
/** Optional Zod object used to validate request headers. */
|
|
21
|
-
headers?: z.ZodObject<any>;
|
|
22
|
-
/** Optional compile-time-only JSON response type marker. */
|
|
23
|
-
response?: Unchecked<any>;
|
|
24
|
-
};
|
|
25
|
-
/** Schema shape for mutation route methods. */
|
|
26
|
-
export type MutationRouteSchema = {
|
|
27
|
-
/** Optional Zod object used to validate path params. */
|
|
28
|
-
path?: z.ZodObject<any>;
|
|
29
|
-
/** Mutation routes do not accept query schemas. */
|
|
30
|
-
query?: never;
|
|
31
|
-
/** Optional Zod schema used to validate the JSON request body. */
|
|
32
|
-
body?: z.ZodType<any, any>;
|
|
33
|
-
/** Optional Zod object used to validate request headers. */
|
|
34
|
-
headers?: z.ZodObject<any>;
|
|
35
|
-
/** Optional compile-time-only JSON response type marker. */
|
|
36
|
-
response?: Unchecked<any>;
|
|
37
|
-
};
|
|
38
|
-
/** Any HTTP action schema Rouzer can execute. */
|
|
39
|
-
export type RouteSchema = QueryRouteSchema | MutationRouteSchema;
|
|
40
|
-
declare class Any {
|
|
41
|
-
private isAny;
|
|
42
|
-
}
|
|
43
|
-
type PathArgs<T, P extends string> = T extends {
|
|
44
|
-
path: infer TPath;
|
|
45
|
-
} ? {} extends z.infer<TPath> ? {
|
|
46
|
-
[K in keyof T as 'path']?: z.infer<TPath>;
|
|
47
|
-
} : {
|
|
48
|
-
[K in keyof T as 'path']: z.infer<TPath>;
|
|
49
|
-
} : MatchParams<P> extends infer TParams ? {} extends TParams ? {
|
|
50
|
-
[K in keyof T as 'path']?: TParams;
|
|
51
|
-
} : {
|
|
52
|
-
[K in keyof T as 'path']: TParams;
|
|
53
|
-
} : unknown;
|
|
54
|
-
type QueryArgs<T> = T extends QueryRouteSchema & {
|
|
55
|
-
query: infer TQuery;
|
|
56
|
-
} ? {} extends z.infer<TQuery> ? {
|
|
57
|
-
[K in keyof T as 'query']?: z.infer<TQuery>;
|
|
58
|
-
} : {
|
|
59
|
-
[K in keyof T as 'query']: z.infer<TQuery>;
|
|
60
|
-
} : unknown;
|
|
61
|
-
type MutationArgs<T> = T extends MutationRouteSchema ? T extends {
|
|
62
|
-
body: infer TBody;
|
|
63
|
-
} ? {} extends z.infer<TBody> ? {
|
|
64
|
-
[K in keyof T as 'body']?: z.infer<TBody>;
|
|
65
|
-
} : {
|
|
66
|
-
[K in keyof T as 'body']: z.infer<TBody>;
|
|
67
|
-
} : {
|
|
68
|
-
body?: unknown;
|
|
69
|
-
} : unknown;
|
|
70
|
-
/**
|
|
71
|
-
* Arguments accepted by a client action function or low-level request factory.
|
|
72
|
-
*
|
|
73
|
-
* @remarks The type is derived from an action schema and route pattern. `path`,
|
|
74
|
-
* `query`, `body`, and `headers` are validated by the client before `fetch` when
|
|
75
|
-
* a matching schema exists. Other `RequestInit` fields are forwarded to `fetch`,
|
|
76
|
-
* except `method`, `body`, and `headers`, which Rouzer derives from the action
|
|
77
|
-
* schema and call arguments.
|
|
78
|
-
*/
|
|
79
|
-
export type RouteArgs<T extends RouteSchema = any, P extends string = string> = ([T] extends [Any] ? {
|
|
80
|
-
query?: any;
|
|
81
|
-
body?: any;
|
|
82
|
-
path?: any;
|
|
83
|
-
} : QueryArgs<T> & MutationArgs<T> & PathArgs<T, P>) & Omit<RequestInit, 'method' | 'body' | 'headers'> & {
|
|
84
|
-
/** Headers for this request. Undefined values are removed before `fetch`. */
|
|
85
|
-
headers?: Record<string, string | undefined>;
|
|
86
|
-
};
|
|
87
|
-
/**
|
|
88
|
-
* Request descriptor produced by an HTTP action request factory.
|
|
89
|
-
*
|
|
90
|
-
* @remarks Pass this object to `client.request(...)` for a raw `Response` or
|
|
91
|
-
* `client.json(...)` for parsed JSON handling.
|
|
92
|
-
*/
|
|
93
|
-
export type RouteRequest<TResult = any> = {
|
|
94
|
-
/** Method schema used for client-side validation. */
|
|
95
|
-
schema: RouteSchema;
|
|
96
|
-
/** Parsed route pattern used to generate the request URL. */
|
|
97
|
-
path: RoutePattern;
|
|
98
|
-
/** HTTP method to send. */
|
|
99
|
-
method: string;
|
|
100
|
-
/** Validated route arguments and request options. */
|
|
101
|
-
args: RouteArgs;
|
|
102
|
-
/** Phantom result type consumed by `client.json(...)`. */
|
|
103
|
-
$result: TResult;
|
|
104
|
-
};
|
|
105
|
-
/** `Response` whose `.json()` method resolves to a known payload type. */
|
|
106
|
-
export type RouteResponse<TResult = any> = Response & {
|
|
107
|
-
json(): Promise<TResult>;
|
|
108
|
-
};
|
|
109
|
-
/** Infer the JSON response payload type from an action schema. */
|
|
110
|
-
export type InferRouteResponse<T extends RouteSchema> = T extends {
|
|
111
|
-
response: Unchecked<infer TResponse>;
|
|
112
|
-
} ? TResponse : void;
|
|
113
|
-
type InferRouteSchemaBody<TSchema> = TSchema extends MutationRouteSchema ? TSchema extends {
|
|
114
|
-
body: infer TBody;
|
|
115
|
-
} ? z.infer<TBody> : unknown : never;
|
|
116
|
-
type InferRouteArgsBody<TArgs> = TArgs extends {
|
|
117
|
-
body?: infer TBody;
|
|
118
|
-
} ? TBody : never;
|
|
119
|
-
/**
|
|
120
|
-
* Infer the request body type from an action schema or request factory.
|
|
121
|
-
*
|
|
122
|
-
* @remarks HTTP action schemas can be inspected with
|
|
123
|
-
* `InferRouteBody<typeof action.schema>`. Request factories for mutation actions
|
|
124
|
-
* infer their `body` argument type. Schemas without a body schema infer
|
|
125
|
-
* `unknown`.
|
|
126
|
-
*/
|
|
127
|
-
export type InferRouteBody<T> = T extends RouteRequestFactory<any, any> ? InferRouteArgsBody<T['$args']> : T extends RouteSchema ? InferRouteSchemaBody<T> : never;
|
|
128
|
-
/**
|
|
129
|
-
* Callable factory attached to an HTTP action.
|
|
130
|
-
*
|
|
131
|
-
* @remarks Calling a factory validates no data by itself; it creates a typed
|
|
132
|
-
* `RouteRequest` descriptor for `createClient` to validate and send.
|
|
133
|
-
*/
|
|
134
|
-
export type RouteRequestFactory<T extends RouteSchema, P extends string> = {
|
|
135
|
-
(...p: RouteArgs<T, P> extends infer TArgs ? {} extends TArgs ? [args?: TArgs] : [args: TArgs] : never): RouteRequest<InferRouteResponse<T>>;
|
|
136
|
-
/** Inferred argument type for this request factory. */
|
|
137
|
-
$args: RouteArgs<T, P>;
|
|
138
|
-
/** Inferred JSON response type for this request factory. */
|
|
139
|
-
$response: InferRouteResponse<T>;
|
|
140
|
-
};
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|