rouzer 5.3.0 → 6.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 +113 -231
- package/dist/http.d.ts +12 -13
- package/dist/metadata.d.ts +1 -4
- package/dist/server/router.d.ts +6 -7
- package/dist/server/router.js +2 -2
- package/docs/client.md +203 -0
- package/docs/concepts.md +113 -0
- package/docs/handlers.md +202 -0
- package/docs/index.md +78 -0
- package/docs/middleware.md +185 -0
- package/docs/migration-v5-to-v6.md +200 -0
- package/docs/patterns.md +139 -0
- package/docs/responses.md +183 -0
- package/docs/routes.md +195 -0
- package/docs/runtime.md +136 -0
- package/docs/streaming.md +131 -0
- package/examples/basic-usage.ts +16 -25
- package/examples/error-responses.ts +12 -24
- package/examples/ndjson-stream.ts +6 -24
- package/package.json +2 -4
- package/docs/context.md +0 -623
package/docs/client.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Typed client
|
|
2
|
+
|
|
3
|
+
`createClient({ baseURL, routes })` creates a typed fetch client from the same
|
|
4
|
+
route tree used by the server.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
const client = createClient({
|
|
8
|
+
baseURL: 'https://example.com/api/',
|
|
9
|
+
routes,
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const profile = await client.profiles.get({
|
|
13
|
+
id: '42',
|
|
14
|
+
includePosts: true,
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The returned client mirrors resources and actions from the route tree. Each
|
|
19
|
+
action function validates input before calling `fetch`.
|
|
20
|
+
|
|
21
|
+
## Configuration
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const client = createClient({
|
|
25
|
+
baseURL: new URL('/api/', window.location.origin).href,
|
|
26
|
+
routes,
|
|
27
|
+
headers: {
|
|
28
|
+
'content-type': 'application/json',
|
|
29
|
+
},
|
|
30
|
+
fetch: globalThis.fetch,
|
|
31
|
+
plugins: [ndjson.clientPlugin],
|
|
32
|
+
onJsonError(response) {
|
|
33
|
+
return response.json()
|
|
34
|
+
},
|
|
35
|
+
clientHook(event) {
|
|
36
|
+
console.log(event.type, event.routeName)
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Option | Purpose |
|
|
42
|
+
| ------------- | ------------------------------------------------------------------------------------- |
|
|
43
|
+
| `baseURL` | Absolute base URL used to build request URLs. A trailing slash is added when missing. |
|
|
44
|
+
| `routes` | Shared HTTP route tree. Required. |
|
|
45
|
+
| `headers` | Default headers merged into every request. |
|
|
46
|
+
| `fetch` | Custom fetch implementation for tests or non-browser runtimes. |
|
|
47
|
+
| `plugins` | Client response plugins such as `ndjson.clientPlugin`. |
|
|
48
|
+
| `onJsonError` | Custom handler for non-2xx responses not declared in a response map. |
|
|
49
|
+
| `clientHook` | Best-effort lifecycle observer for generated action calls. |
|
|
50
|
+
|
|
51
|
+
The returned client exposes the original options as `client.clientConfig`, so a
|
|
52
|
+
route action named `config` remains available as `client.config(...)`.
|
|
53
|
+
|
|
54
|
+
## Action Arguments
|
|
55
|
+
|
|
56
|
+
Generated action functions use a flat first argument for path, query, and JSON
|
|
57
|
+
body fields.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
export const updateProfile = http.patch('profiles/:id', {
|
|
61
|
+
body: z.object({
|
|
62
|
+
name: z.string(),
|
|
63
|
+
}),
|
|
64
|
+
response: $type<Profile>(),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
await client.updateProfile({
|
|
68
|
+
id: '42',
|
|
69
|
+
name: 'Ada',
|
|
70
|
+
})
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Per-request `RequestInit` options are the second argument. Rouzer reserves
|
|
74
|
+
`method` and owns JSON body encoding. Headers are typed from the route header
|
|
75
|
+
schema when one exists.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
await client.profiles.get(
|
|
79
|
+
{ id: '42', includePosts: false },
|
|
80
|
+
{
|
|
81
|
+
signal: abortController.signal,
|
|
82
|
+
headers: { 'x-request-id': 'docs' },
|
|
83
|
+
}
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Avoid duplicate keys across path, query, and JSON body schemas. The flat client
|
|
88
|
+
input cannot distinguish duplicate field names from different request
|
|
89
|
+
locations.
|
|
90
|
+
|
|
91
|
+
## Raw Body Routes
|
|
92
|
+
|
|
93
|
+
For raw-body routes with path or query input, pass the `BodyInit` as
|
|
94
|
+
`options.body`.
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
await client.uploadAvatar(
|
|
98
|
+
{ id: '42' },
|
|
99
|
+
{ body: file, headers: { 'content-type': file.type } }
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For raw-body routes without route input, pass the body as the first argument.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
await client.upload(file, {
|
|
107
|
+
headers: { 'content-type': file.type },
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Rouzer does not JSON encode or validate raw bodies.
|
|
112
|
+
|
|
113
|
+
## Response Results
|
|
114
|
+
|
|
115
|
+
Generated action return types depend on the action response schema:
|
|
116
|
+
|
|
117
|
+
| Route response | Client result |
|
|
118
|
+
| ------------------------- | ---------------------------------------------------------------- |
|
|
119
|
+
| No `response` marker | Raw `Response`. |
|
|
120
|
+
| `response: $type<T>()` | Parsed JSON typed as `T`. |
|
|
121
|
+
| Status-keyed response map | Tuple union: `[null, value, status]` or `[error, null, status]`. |
|
|
122
|
+
| Response plugin marker | Plugin-decoded value, such as `AsyncIterable<T>` for NDJSON. |
|
|
123
|
+
|
|
124
|
+
Non-2xx responses reject unless the status is declared in a response map or
|
|
125
|
+
`onJsonError` returns a value.
|
|
126
|
+
|
|
127
|
+
## Error Handling
|
|
128
|
+
|
|
129
|
+
By default, undeclared non-2xx responses throw an `Error` with the HTTP status
|
|
130
|
+
in the message. If the response has a JSON content type, Rouzer copies parsed
|
|
131
|
+
JSON properties onto the thrown error.
|
|
132
|
+
|
|
133
|
+
Use `onJsonError` to override that behavior:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const client = createClient({
|
|
137
|
+
baseURL,
|
|
138
|
+
routes,
|
|
139
|
+
async onJsonError(response) {
|
|
140
|
+
return {
|
|
141
|
+
status: response.status,
|
|
142
|
+
body: await response.json(),
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Rouzer returns the `onJsonError` result as-is. It does not automatically parse a
|
|
149
|
+
`Response` returned by `onJsonError`.
|
|
150
|
+
|
|
151
|
+
## Lifecycle Hooks
|
|
152
|
+
|
|
153
|
+
Use `clientHook` for observability without wrapping every generated action.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const client = createClient({
|
|
157
|
+
baseURL,
|
|
158
|
+
routes,
|
|
159
|
+
clientHook(event) {
|
|
160
|
+
if (event.type === 'request.success') {
|
|
161
|
+
console.log(event.routeName, event.durationMs)
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Rouzer emits:
|
|
168
|
+
|
|
169
|
+
- `request.start` before client-side validation
|
|
170
|
+
- `request.success` when the generated action resolves
|
|
171
|
+
- `request.error` when the generated action rejects
|
|
172
|
+
|
|
173
|
+
Each event includes an opaque `opId`, `routeName`, HTTP `method`,
|
|
174
|
+
`pathPattern`, and original `payload`. Terminal events include `durationMs` and
|
|
175
|
+
either `response` or `error`. If an HTTP response was received, terminal events
|
|
176
|
+
also include `status`.
|
|
177
|
+
|
|
178
|
+
Hook errors are swallowed. Lifecycle hooks are observability-only and must not
|
|
179
|
+
change request behavior.
|
|
180
|
+
|
|
181
|
+
For streaming response plugins, `request.success` is emitted when the generated
|
|
182
|
+
action resolves to the stream object. Errors that happen while consuming the
|
|
183
|
+
stream are outside the first lifecycle hook surface.
|
|
184
|
+
|
|
185
|
+
## Testing With Local Fetch
|
|
186
|
+
|
|
187
|
+
Rouzer handlers accept a request context, while `fetch` accepts `(input, init)`.
|
|
188
|
+
Use `toFetchHandler` plus a small wrapper in tests.
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
import { toFetchHandler, type RequestHandler } from 'rouzer'
|
|
192
|
+
|
|
193
|
+
function createLocalFetch(handler: RequestHandler): typeof fetch {
|
|
194
|
+
const fetchHandler = toFetchHandler(handler)
|
|
195
|
+
return (input, init) => fetchHandler(new Request(input, init))
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const client = createClient({
|
|
199
|
+
baseURL: 'https://example.test/api/',
|
|
200
|
+
routes,
|
|
201
|
+
fetch: createLocalFetch(router),
|
|
202
|
+
})
|
|
203
|
+
```
|
package/docs/concepts.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Framework concepts
|
|
2
|
+
|
|
3
|
+
Rouzer is for applications that can share a TypeScript HTTP contract between
|
|
4
|
+
server and client code. The central object is an HTTP route tree. That tree
|
|
5
|
+
describes URL paths, action names, request schemas, and response contracts once,
|
|
6
|
+
then it is reused by the router and generated client.
|
|
7
|
+
|
|
8
|
+
Rouzer is not an OpenAPI generator, a response validator, or a full application
|
|
9
|
+
framework. It focuses on shared route contracts, request validation, typed
|
|
10
|
+
handlers, typed clients, response helpers, middleware, and request context.
|
|
11
|
+
|
|
12
|
+
## The Main Objects
|
|
13
|
+
|
|
14
|
+
Route tree:
|
|
15
|
+
|
|
16
|
+
- a plain object whose leaves are HTTP actions and whose branches are resources
|
|
17
|
+
- exported from a shared module that server and client code can both import
|
|
18
|
+
- passed to `createRouter().use(routes, handlers)` and
|
|
19
|
+
`createClient({ routes, baseURL })`
|
|
20
|
+
|
|
21
|
+
Action:
|
|
22
|
+
|
|
23
|
+
- a concrete HTTP operation declared with `http.get`, `http.post`, `http.put`,
|
|
24
|
+
`http.patch`, or `http.delete`
|
|
25
|
+
- owns an optional action-local path, request schemas, and an optional response
|
|
26
|
+
marker or response map
|
|
27
|
+
- becomes one handler function on the server and one generated client function
|
|
28
|
+
|
|
29
|
+
Resource:
|
|
30
|
+
|
|
31
|
+
- a path-scoped namespace declared with `http.resource(path, children)`
|
|
32
|
+
- contributes path params to child actions
|
|
33
|
+
- creates nested handler and client objects
|
|
34
|
+
|
|
35
|
+
Router:
|
|
36
|
+
|
|
37
|
+
- a fetch-compatible request handler returned by `createRouter(...)`
|
|
38
|
+
- accepts middleware with `.use(middleware)`
|
|
39
|
+
- accepts route trees with `.use(routes, handlers)`
|
|
40
|
+
- validates matched requests before handlers run
|
|
41
|
+
|
|
42
|
+
Client:
|
|
43
|
+
|
|
44
|
+
- a typed fetch wrapper returned by `createClient({ baseURL, routes })`
|
|
45
|
+
- mirrors the route tree shape
|
|
46
|
+
- validates client input before sending requests
|
|
47
|
+
- parses responses according to the route response contract
|
|
48
|
+
|
|
49
|
+
Middleware:
|
|
50
|
+
|
|
51
|
+
- functions that receive a shared `RequestContext`
|
|
52
|
+
- can add typed properties, environment bindings, response callbacks, or runtime
|
|
53
|
+
type markers
|
|
54
|
+
- run before route handlers when attached before `.use(routes, handlers)`
|
|
55
|
+
|
|
56
|
+
## What Validation Covers
|
|
57
|
+
|
|
58
|
+
Rouzer validates request inputs:
|
|
59
|
+
|
|
60
|
+
- path params from route patterns or a `path` Zod object
|
|
61
|
+
- URL query values on `GET` actions with a `query` schema
|
|
62
|
+
- JSON request bodies on mutation actions with a Zod `body` schema
|
|
63
|
+
- request headers with a `headers` schema
|
|
64
|
+
|
|
65
|
+
Path, query, and header values arrive as strings. Rouzer adds string parsing for
|
|
66
|
+
Zod number and boolean schemas in those locations, including nested object and
|
|
67
|
+
array schemas. JSON request bodies are parsed from the request body and then
|
|
68
|
+
validated as JSON values.
|
|
69
|
+
|
|
70
|
+
Rouzer does not validate handler return values just because a route uses
|
|
71
|
+
`$type<T>()`, `$error<T>()`, or `ndjson.$type<T>()`. Those markers are TypeScript
|
|
72
|
+
contracts. Validate untrusted response data where it enters your system.
|
|
73
|
+
|
|
74
|
+
## Lifecycle
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// shared/routes.ts
|
|
78
|
+
export const routes = {
|
|
79
|
+
profiles: http.resource('profiles/:id', {
|
|
80
|
+
get: http.get({ response: $type<Profile>() }),
|
|
81
|
+
}),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// server.ts
|
|
85
|
+
export const router = createRouter()
|
|
86
|
+
.use(requestMiddleware)
|
|
87
|
+
.use(routes, handlers)
|
|
88
|
+
|
|
89
|
+
// client.ts
|
|
90
|
+
export const client = createClient({
|
|
91
|
+
baseURL: 'https://example.com/api/',
|
|
92
|
+
routes,
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The same action key becomes:
|
|
97
|
+
|
|
98
|
+
- `handlers.profiles.get(ctx)` on the server
|
|
99
|
+
- `client.profiles.get(input, options)` on the client
|
|
100
|
+
- a route name such as `profiles.get` in client lifecycle hook events
|
|
101
|
+
|
|
102
|
+
## Rouzer Middleware
|
|
103
|
+
|
|
104
|
+
Rouzer's router is both callable as a request handler and chainable with
|
|
105
|
+
middleware helpers.
|
|
106
|
+
|
|
107
|
+
Use route contracts for HTTP handler/client behavior. Use middleware when you
|
|
108
|
+
need request-scoped state, authentication, environment bindings, host runtime
|
|
109
|
+
data, response callbacks, background work, or custom adapter contexts.
|
|
110
|
+
|
|
111
|
+
The common middleware API is exported from `rouzer`, including `chain`,
|
|
112
|
+
`toFetchHandler`, `createContext`, `filterRuntime`, `RequestContext`, and
|
|
113
|
+
`RequestHandler`.
|
package/docs/handlers.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Routers and handlers
|
|
2
|
+
|
|
3
|
+
`createRouter()` returns a fetch-compatible Rouzer handler with chain methods.
|
|
4
|
+
Use it to compose middleware and attach route trees.
|
|
5
|
+
|
|
6
|
+
```ts
|
|
7
|
+
import { createRouter } from 'rouzer'
|
|
8
|
+
|
|
9
|
+
export const router = createRouter({ basePath: 'api/' })
|
|
10
|
+
.use(requestMiddleware)
|
|
11
|
+
.use(routes, handlers)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The router is also a `RequestHandler`, so it can be mounted with
|
|
15
|
+
`toFetchHandler(router)` or runtime-specific adapter helpers.
|
|
16
|
+
|
|
17
|
+
## Handler Maps
|
|
18
|
+
|
|
19
|
+
The handler object mirrors the route tree. Resource nodes become nested objects.
|
|
20
|
+
Action nodes become handler functions.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
export const routes = {
|
|
24
|
+
profiles: http.resource('profiles/:id', {
|
|
25
|
+
get: http.get({ response: $type<Profile>() }),
|
|
26
|
+
update: http.patch({
|
|
27
|
+
body: updateProfileSchema,
|
|
28
|
+
response: $type<Profile>(),
|
|
29
|
+
}),
|
|
30
|
+
posts: http.resource('posts', {
|
|
31
|
+
list: http.get({ response: $type<Post[]>() }),
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
createRouter().use(routes, {
|
|
37
|
+
profiles: {
|
|
38
|
+
get(ctx) {
|
|
39
|
+
return loadProfile(ctx.path.id)
|
|
40
|
+
},
|
|
41
|
+
update(ctx) {
|
|
42
|
+
return updateProfile(ctx.path.id, ctx.body)
|
|
43
|
+
},
|
|
44
|
+
posts: {
|
|
45
|
+
list(ctx) {
|
|
46
|
+
return listPosts(ctx.path.id)
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Handler Context
|
|
54
|
+
|
|
55
|
+
Handlers receive the Rouzer request context plus values inferred from the
|
|
56
|
+
matched route.
|
|
57
|
+
|
|
58
|
+
`GET` handlers receive:
|
|
59
|
+
|
|
60
|
+
- `ctx.path`
|
|
61
|
+
- `ctx.query`
|
|
62
|
+
- `ctx.headers`
|
|
63
|
+
|
|
64
|
+
Mutation handlers receive:
|
|
65
|
+
|
|
66
|
+
- `ctx.path`
|
|
67
|
+
- `ctx.body`
|
|
68
|
+
- `ctx.headers`
|
|
69
|
+
|
|
70
|
+
All handlers also receive middleware-provided properties and base context fields
|
|
71
|
+
such as `ctx.request`, `ctx.url`, `ctx.host`, `ctx.env(...)`,
|
|
72
|
+
`ctx.waitUntil(...)`, `ctx.setHeader(...)`, and `ctx.onResponse(...)`.
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
const requestInfo = chain().use(ctx => ({
|
|
76
|
+
requestId: ctx.request.headers.get('x-request-id') ?? 'local',
|
|
77
|
+
}))
|
|
78
|
+
|
|
79
|
+
createRouter()
|
|
80
|
+
.use(requestInfo)
|
|
81
|
+
.use(routes, {
|
|
82
|
+
getProfile(ctx) {
|
|
83
|
+
return {
|
|
84
|
+
id: ctx.path.id,
|
|
85
|
+
requestId: ctx.requestId,
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Validation Order
|
|
92
|
+
|
|
93
|
+
For a matched route, Rouzer validates before the handler runs:
|
|
94
|
+
|
|
95
|
+
1. path params, if a `path` schema is declared
|
|
96
|
+
2. headers, if a `headers` schema is declared
|
|
97
|
+
3. query string, for `GET` actions with a `query` schema
|
|
98
|
+
4. JSON body, for mutation actions with a non-raw `body` schema
|
|
99
|
+
|
|
100
|
+
If validation fails, Rouzer returns a `400` JSON response and does not call the
|
|
101
|
+
handler. In `debug` mode, validation responses include more specific Zod error
|
|
102
|
+
messages.
|
|
103
|
+
|
|
104
|
+
If no `path` schema is declared, `ctx.path` is inferred from the route pattern
|
|
105
|
+
and contains string params.
|
|
106
|
+
|
|
107
|
+
## Return Values
|
|
108
|
+
|
|
109
|
+
Handlers can return:
|
|
110
|
+
|
|
111
|
+
- a plain value, which Rouzer sends with `Response.json(value)`
|
|
112
|
+
- a Web `Response`, which Rouzer passes through unchanged
|
|
113
|
+
- `ctx.error(status, body)` for a declared error response-map entry
|
|
114
|
+
- `ctx.success(status, body)` for a declared non-default success response-map
|
|
115
|
+
entry
|
|
116
|
+
- a response-plugin value, such as an NDJSON iterable, when the route response
|
|
117
|
+
marker and router plugin match
|
|
118
|
+
|
|
119
|
+
Return a `Response` when you need custom status codes, headers, redirects,
|
|
120
|
+
non-JSON payloads, or hand-written error bodies.
|
|
121
|
+
|
|
122
|
+
## Response Maps In Handlers
|
|
123
|
+
|
|
124
|
+
Routes with a status-keyed response map add `ctx.error` and `ctx.success`
|
|
125
|
+
helpers.
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
export const getUser = http.get('users/:id', {
|
|
129
|
+
response: {
|
|
130
|
+
200: $type<User>(),
|
|
131
|
+
201: $type<User>(),
|
|
132
|
+
404: $error<NotFound>(),
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
createRouter().use(
|
|
137
|
+
{ getUser },
|
|
138
|
+
{
|
|
139
|
+
getUser(ctx) {
|
|
140
|
+
if (ctx.path.id === 'created') {
|
|
141
|
+
return ctx.success(201, { id: 'created', name: 'Grace' })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const user = users.get(ctx.path.id)
|
|
145
|
+
if (!user) {
|
|
146
|
+
return ctx.error(404, {
|
|
147
|
+
code: 'NOT_FOUND',
|
|
148
|
+
message: 'User not found',
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return user
|
|
153
|
+
},
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The helpers only accept statuses and body types declared in the response map.
|
|
159
|
+
|
|
160
|
+
## Router Configuration
|
|
161
|
+
|
|
162
|
+
`createRouter(config)` accepts:
|
|
163
|
+
|
|
164
|
+
| Option | Purpose |
|
|
165
|
+
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
166
|
+
| `basePath` | Prepends a normalized path prefix to all route patterns. |
|
|
167
|
+
| `debug` | Adds matched-route debug headers, logs missing route handlers, and includes more specific validation messages. |
|
|
168
|
+
| `plugins` | Router response plugins such as `ndjson.routerPlugin`. |
|
|
169
|
+
| `cors.allowOrigins` | Restricts requests with an `Origin` header. |
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
createRouter({
|
|
173
|
+
basePath: 'api/',
|
|
174
|
+
debug: process.env.NODE_ENV === 'development',
|
|
175
|
+
cors: {
|
|
176
|
+
allowOrigins: ['example.net', 'https://*.example.com'],
|
|
177
|
+
},
|
|
178
|
+
})
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
When an allowed request includes an `Origin` header, Rouzer sets
|
|
182
|
+
`Access-Control-Allow-Origin` to that origin. For preflight requests, Rouzer
|
|
183
|
+
returns `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and
|
|
184
|
+
`Access-Control-Allow-Headers`.
|
|
185
|
+
|
|
186
|
+
Rouzer does not automatically set `Access-Control-Allow-Credentials`. Set that
|
|
187
|
+
header yourself when credentialed cross-origin requests need it.
|
|
188
|
+
|
|
189
|
+
## Middleware Ordering
|
|
190
|
+
|
|
191
|
+
Attach middleware before route handlers that depend on it.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
createRouter().use(authMiddleware).use(routes, secureHandlers)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Middleware attached after a route tree only runs if an earlier route handler
|
|
198
|
+
does not produce a response. Most applications put shared middleware before
|
|
199
|
+
route registration.
|
|
200
|
+
|
|
201
|
+
Use a middleware `Response` return for cross-cutting rejection, such as auth or
|
|
202
|
+
rate limits. Use route handlers for route-specific application results.
|
package/docs/index.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Rouzer documentation
|
|
2
|
+
|
|
3
|
+
Rouzer combines a shared TypeScript route tree, a fetch-compatible server
|
|
4
|
+
router, a typed fetch client, and request context composition. These guides are
|
|
5
|
+
organized by concern so the docs directory is the complete entry point for what
|
|
6
|
+
you can do and how the pieces fit together.
|
|
7
|
+
|
|
8
|
+
## Learning Path
|
|
9
|
+
|
|
10
|
+
1. Read [Framework concepts](concepts.md) for the lifecycle and boundaries.
|
|
11
|
+
2. Read [Route contracts](routes.md) to declare resources, actions, schemas, and
|
|
12
|
+
metadata.
|
|
13
|
+
3. Read [Middleware and request context](middleware.md) before writing shared
|
|
14
|
+
context, auth, environment, tracing, or runtime-specific middleware.
|
|
15
|
+
4. Read [Routers and handlers](handlers.md) to attach route trees and implement
|
|
16
|
+
handlers.
|
|
17
|
+
5. Read [Typed client](client.md) to call the same route tree from application
|
|
18
|
+
code.
|
|
19
|
+
6. Read [Responses, errors, and plugins](responses.md) and
|
|
20
|
+
[NDJSON streaming](streaming.md) when routes need more than a simple JSON
|
|
21
|
+
success body.
|
|
22
|
+
7. Read [Runtime and adapters](runtime.md) when mounting Rouzer in a server or
|
|
23
|
+
test harness.
|
|
24
|
+
8. Read [Migration from v5 to v6](migration-v5-to-v6.md) when upgrading
|
|
25
|
+
from the Hattip-compatible server boundary.
|
|
26
|
+
9. Read [Patterns, constraints, and migrations](patterns.md) for conventions,
|
|
27
|
+
gotchas, and upgrade notes.
|
|
28
|
+
|
|
29
|
+
## Guide Map
|
|
30
|
+
|
|
31
|
+
| Guide | Concern |
|
|
32
|
+
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| [Framework concepts](concepts.md) | The high-level model, request lifecycle, and Rouzer's framework responsibilities. |
|
|
34
|
+
| [Route contracts](routes.md) | `rouzer/http` resources, actions, Zod schemas, raw bodies, path patterns, and metadata. |
|
|
35
|
+
| [Middleware and request context](middleware.md) | `chain`, request plugins, `RequestContext`, `context.host`, env access, `waitUntil`, `onResponse`, `passThrough`, isolation, and runtime filtering. |
|
|
36
|
+
| [Routers and handlers](handlers.md) | `createRouter`, route handler maps, validated handler context, CORS, debug mode, middleware ordering, and handler return values. |
|
|
37
|
+
| [Typed client](client.md) | `createClient`, generated action functions, flat input objects, headers, custom fetch, `onJsonError`, and lifecycle hooks. |
|
|
38
|
+
| [Responses, errors, and plugins](responses.md) | `$type`, `$error`, response maps, status tuples, custom `Response` returns, and response plugin contracts. |
|
|
39
|
+
| [NDJSON streaming](streaming.md) | Streaming response markers, router/client plugin registration, cancellation, and stream error modeling. |
|
|
40
|
+
| [Runtime and adapters](runtime.md) | Root `toFetchHandler`, fetch-compatible mounting, custom host data, tests, CORS, and background work. |
|
|
41
|
+
| [Patterns, constraints, and migrations](patterns.md) | Preferred project structure, common constraints, and migration notes from older Rouzer shapes. |
|
|
42
|
+
| [Migration from v5 to v6](migration-v5-to-v6.md) | Hattip-compatible to fetch-compatible server mounting, type, test, host data, and runtime-filter updates. |
|
|
43
|
+
|
|
44
|
+
## Request Lifecycle
|
|
45
|
+
|
|
46
|
+
1. A shared route module exports resources and actions.
|
|
47
|
+
2. Server code creates a router, appends middleware, and attaches the route tree
|
|
48
|
+
with a handler map.
|
|
49
|
+
3. Runtime code mounts the router with `toFetchHandler` or an adapter-specific
|
|
50
|
+
helper.
|
|
51
|
+
4. Client code creates a generated client from the same route tree.
|
|
52
|
+
5. A client action validates route input, builds a `fetch` request, and sends
|
|
53
|
+
it.
|
|
54
|
+
6. The router matches the request, validates path/query/body/header data, and
|
|
55
|
+
calls the typed handler.
|
|
56
|
+
7. The handler returns JSON data, a custom `Response`, a declared response-map
|
|
57
|
+
helper, or a response-plugin value such as an NDJSON source.
|
|
58
|
+
8. Response callbacks registered by middleware can finalize headers or replace
|
|
59
|
+
the response before it leaves the chain.
|
|
60
|
+
|
|
61
|
+
## Framework Surface
|
|
62
|
+
|
|
63
|
+
Rouzer owns:
|
|
64
|
+
|
|
65
|
+
- route tree declarations and handler/client type inference
|
|
66
|
+
- request validation from route schemas
|
|
67
|
+
- route matching and handler dispatch
|
|
68
|
+
- response markers, response maps, and response plugin integration
|
|
69
|
+
- generated client action functions
|
|
70
|
+
|
|
71
|
+
Rouzer middleware and runtime helpers cover:
|
|
72
|
+
|
|
73
|
+
- middleware chaining and short-circuit behavior
|
|
74
|
+
- request context creation and extension
|
|
75
|
+
- host data such as `context.host.ip` and `context.host.runtime`
|
|
76
|
+
- typed environment access through `context.env(...)`
|
|
77
|
+
- `waitUntil`, `setHeader`, `onResponse`, `passThrough`, and chain isolation
|
|
78
|
+
- adapter helpers such as `toFetchHandler`
|