rouzer 5.0.0 → 5.1.1
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 +35 -3
- package/dist/client/index.d.ts +14 -5
- package/dist/client/index.js +17 -8
- package/dist/http.d.ts +7 -1
- package/dist/http.js +7 -1
- package/dist/response-map.js +1 -1
- package/dist/types/args.d.ts +9 -6
- package/docs/context.md +74 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ that contract to:
|
|
|
14
14
|
- match and validate server requests before handlers run
|
|
15
15
|
- type handler context from path, query/body, headers, and middleware
|
|
16
16
|
- attach typed client action functions such as `client.profiles.get(...)`
|
|
17
|
+
- send JSON object request bodies or raw `BodyInit` payloads
|
|
17
18
|
- parse typed JSON responses, declared error responses, and NDJSON streams
|
|
18
19
|
|
|
19
20
|
Rouzer optimizes for shared TypeScript route modules over language-agnostic API
|
|
@@ -103,8 +104,11 @@ const { message } = await client.hello({
|
|
|
103
104
|
```
|
|
104
105
|
|
|
105
106
|
`handler` can be mounted with any Hattip adapter. Generated client action calls
|
|
106
|
-
validate route arguments before `fetch`; server handlers validate matched
|
|
107
|
-
query, headers, and JSON bodies before your handler runs.
|
|
107
|
+
validate flat route arguments before `fetch`; server handlers validate matched
|
|
108
|
+
path, query, headers, and JSON bodies before your handler runs. Per-request
|
|
109
|
+
headers, abort signals, and other `RequestInit` options are passed as a second
|
|
110
|
+
client action argument. Routes declared with `body: http.rawBody()` pass a
|
|
111
|
+
`BodyInit` payload through to `fetch` without JSON encoding.
|
|
108
112
|
|
|
109
113
|
### Typed status responses
|
|
110
114
|
|
|
@@ -149,6 +153,34 @@ const [error, user, status] = await client.getUser({ id: '42' })
|
|
|
149
153
|
Success entries resolve as `[null, value, status]`; declared error entries
|
|
150
154
|
resolve as `[error, null, status]`.
|
|
151
155
|
|
|
156
|
+
### Raw request bodies
|
|
157
|
+
|
|
158
|
+
Use `http.rawBody()` when an action needs to send a `BodyInit` payload such as a
|
|
159
|
+
`Blob`, `Uint8Array`, `ReadableStream`, `FormData`, or string without JSON
|
|
160
|
+
encoding.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
export const uploadAvatar = http.post('profiles/:id/avatar', {
|
|
164
|
+
body: http.rawBody(),
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
await client.uploadAvatar({ id: '42' }, { body: file })
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
For raw-body routes without path or query input, the generated client accepts the
|
|
171
|
+
body as the first argument:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
export const upload = http.post('upload', {
|
|
175
|
+
body: http.rawBody(),
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
await client.upload(file, { headers: { 'content-type': file.type } })
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Server handlers for raw-body routes read from `ctx.request` directly with Fetch
|
|
182
|
+
APIs such as `arrayBuffer()`, `blob()`, `formData()`, or `text()`.
|
|
183
|
+
|
|
152
184
|
### NDJSON response streams
|
|
153
185
|
|
|
154
186
|
Use `response: ndjson.$type<T>()` for endpoints that stream
|
|
@@ -185,7 +217,7 @@ for await (const event of await client.events()) {
|
|
|
185
217
|
|
|
186
218
|
## Documentation
|
|
187
219
|
|
|
188
|
-
- [Concepts, API selection, and
|
|
220
|
+
- [Concepts, API selection, v5 client input notes, and migration notes](docs/context.md)
|
|
189
221
|
- [Runnable shared-route example](examples/basic-usage.ts)
|
|
190
222
|
- [Runnable typed error response example](examples/error-responses.ts)
|
|
191
223
|
- [Runnable NDJSON response-stream example](examples/ndjson-stream.ts)
|
package/dist/client/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Promisable } from '../common.js';
|
|
2
2
|
import { type HttpAction, type HttpResource, type HttpRouteTree } from '../http.js';
|
|
3
3
|
import { type ClientResponsePlugin } from '../response.js';
|
|
4
|
-
import type { RouteInput, RouteOptions } from '../types/args.js';
|
|
4
|
+
import type { RouteFetchOptions, RouteInput, RouteOptions } from '../types/args.js';
|
|
5
|
+
import type { RawBodySchema } from '../types/schema.js';
|
|
5
6
|
import type { InferRouteResponse } from '../types/response.js';
|
|
6
7
|
import type { RouteSchema } from '../types/schema.js';
|
|
7
8
|
/** Client type inferred from an HTTP route tree passed to `createClient`. */
|
|
@@ -33,7 +34,7 @@ export declare function createClient<TRoutes extends HttpRouteTree = Record<stri
|
|
|
33
34
|
* @example
|
|
34
35
|
* ```ts
|
|
35
36
|
* const client = createClient({ baseURL: 'https://example.com/api/', routes })
|
|
36
|
-
* await client.users.list({
|
|
37
|
+
* await client.users.list({ page: 1 })
|
|
37
38
|
* ```
|
|
38
39
|
*/
|
|
39
40
|
routes: TRoutes;
|
|
@@ -72,7 +73,7 @@ export declare function createClient<TRoutes extends HttpRouteTree = Record<stri
|
|
|
72
73
|
* @example
|
|
73
74
|
* ```ts
|
|
74
75
|
* const client = createClient({ baseURL: 'https://example.com/api/', routes })
|
|
75
|
-
* await client.users.list({
|
|
76
|
+
* await client.users.list({ page: 1 })
|
|
76
77
|
* ```
|
|
77
78
|
*/
|
|
78
79
|
routes: TRoutes;
|
|
@@ -104,9 +105,17 @@ export type ClientTree<T extends HttpRouteTree, TPrefix extends string = ''> = {
|
|
|
104
105
|
* union of `[null, value, status]` success entries and `[error, null, status]`
|
|
105
106
|
* error entries. Actions whose schema has a plugin response marker return the
|
|
106
107
|
* plugin's client result type. Actions without a response marker return the raw
|
|
107
|
-
* `Response`.
|
|
108
|
+
* `Response`. Raw-body actions with no path or query input accept
|
|
109
|
+
* `(body, options)`; raw-body actions with route input accept
|
|
110
|
+
* `(input, { body, ...options })`.
|
|
108
111
|
*/
|
|
109
|
-
export type RouteFunction<T extends RouteSchema, P extends string> =
|
|
112
|
+
export type RouteFunction<T extends RouteSchema, P extends string> = T extends {
|
|
113
|
+
body: RawBodySchema;
|
|
114
|
+
} ? RouteInput<T, P> extends infer TInput ? {} extends TInput ? (body: BodyInit | null, options?: RouteFetchOptions<T>) => Promise<T extends {
|
|
115
|
+
response: any;
|
|
116
|
+
} ? InferRouteResponse<T> : Response> : (input: TInput, options: RouteOptions<T>) => Promise<T extends {
|
|
117
|
+
response: any;
|
|
118
|
+
} ? InferRouteResponse<T> : Response> : never : (...p: RouteInput<T, P> extends infer TInput ? {} extends TInput ? [input?: TInput, options?: RouteOptions<T>] : [input: TInput, options?: RouteOptions<T>] : never) => Promise<T extends {
|
|
110
119
|
response: any;
|
|
111
120
|
} ? InferRouteResponse<T> : Response>;
|
|
112
121
|
export {};
|
package/dist/client/index.js
CHANGED
|
@@ -134,14 +134,20 @@ function connectTree(tree, prefix, plainRequest, parsedRequest) {
|
|
|
134
134
|
const fetch = node.schema.response ? parsedRequest : plainRequest;
|
|
135
135
|
return [
|
|
136
136
|
key,
|
|
137
|
-
(input, options) =>
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
137
|
+
(input, options) => {
|
|
138
|
+
if (isRawBodySchema(node.schema.body) && !hasRouteInput(node, path)) {
|
|
139
|
+
options = { ...options, body: input };
|
|
140
|
+
input = undefined;
|
|
141
|
+
}
|
|
142
|
+
return fetch({
|
|
143
|
+
schema: node.schema,
|
|
144
|
+
path,
|
|
145
|
+
method: node.method,
|
|
146
|
+
input,
|
|
147
|
+
options,
|
|
148
|
+
$result: undefined,
|
|
149
|
+
});
|
|
150
|
+
},
|
|
145
151
|
];
|
|
146
152
|
}));
|
|
147
153
|
}
|
|
@@ -165,6 +171,9 @@ function validateClientResponsePlugins(tree, plugins) {
|
|
|
165
171
|
function missingClientResponsePlugin(pluginId) {
|
|
166
172
|
return new Error(`Missing client response plugin for ${pluginId}`);
|
|
167
173
|
}
|
|
174
|
+
function hasRouteInput(node, path) {
|
|
175
|
+
return Boolean(node.schema.path || node.schema.query || /(^|\/)[:*]/.test(path.source));
|
|
176
|
+
}
|
|
168
177
|
function pickObjectSchemaFields(schema, input) {
|
|
169
178
|
if (typeof input !== 'object' || input === null) {
|
|
170
179
|
return input;
|
package/dist/http.d.ts
CHANGED
|
@@ -62,6 +62,12 @@ export declare function patch<const T extends RouteSchema>(schema: T): HttpActio
|
|
|
62
62
|
declare function deleteAction<const P extends string, const T extends RouteSchema>(path: P, schema: T): HttpAction<P, T, 'DELETE'>;
|
|
63
63
|
declare function deleteAction<const T extends RouteSchema>(schema: T): HttpAction<'', T, 'DELETE'>;
|
|
64
64
|
export { deleteAction as delete };
|
|
65
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* Declare a request body that is passed through to `fetch` without JSON encoding.
|
|
67
|
+
*
|
|
68
|
+
* @remarks For routes with path or query input, pass the body as
|
|
69
|
+
* `options.body`. For raw-body routes without input, generated client actions
|
|
70
|
+
* accept the body as their first argument.
|
|
71
|
+
*/
|
|
66
72
|
export declare function rawBody(): RawBodySchema;
|
|
67
73
|
export declare function isRawBodySchema(schema: unknown): schema is RawBodySchema;
|
package/dist/http.js
CHANGED
|
@@ -29,7 +29,13 @@ function deleteAction(pathOrSchema, schema) {
|
|
|
29
29
|
return action('DELETE', pathOrSchema, schema);
|
|
30
30
|
}
|
|
31
31
|
export { deleteAction as delete };
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Declare a request body that is passed through to `fetch` without JSON encoding.
|
|
34
|
+
*
|
|
35
|
+
* @remarks For routes with path or query input, pass the body as
|
|
36
|
+
* `options.body`. For raw-body routes without input, generated client actions
|
|
37
|
+
* accept the body as their first argument.
|
|
38
|
+
*/
|
|
33
39
|
export function rawBody() {
|
|
34
40
|
return { __rawBody__: Symbol('rouzer.rawBody') };
|
|
35
41
|
}
|
package/dist/response-map.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getResponsePluginMarkerId, responsePluginMarker
|
|
1
|
+
import { getResponsePluginMarkerId, responsePluginMarker } from './response.js';
|
|
2
2
|
import { $error } from './type.js';
|
|
3
3
|
/** Return true when the response schema is a status-keyed response map. */
|
|
4
4
|
export function isResponseMap(response) {
|
package/dist/types/args.d.ts
CHANGED
|
@@ -25,18 +25,21 @@ type HeaderInput<T> = T extends {
|
|
|
25
25
|
*/
|
|
26
26
|
export type RouteInput<T extends RouteSchema = any, P extends string = string> = [T] extends [Any] ? any : PathInput<T, P> & QueryInput<T> & BodyInput<T>;
|
|
27
27
|
/**
|
|
28
|
-
* Fetch options accepted
|
|
28
|
+
* Fetch options accepted by a generated client action.
|
|
29
29
|
*
|
|
30
30
|
* @remarks `headers` remains optional because required route headers may be
|
|
31
|
-
* supplied by `createClient({ headers })` defaults.
|
|
31
|
+
* supplied by `createClient({ headers })` defaults. Raw-body routes with path or
|
|
32
|
+
* query input accept `body` here; raw-body routes without input accept the body
|
|
33
|
+
* as the first client action argument and these options as the second.
|
|
32
34
|
*/
|
|
35
|
+
export type RouteFetchOptions<T extends RouteSchema = any> = Omit<RequestInit, 'method' | 'body' | 'headers'> & {
|
|
36
|
+
/** Headers for this request. Undefined values are removed before `fetch`. */
|
|
37
|
+
headers?: HeaderInput<T>;
|
|
38
|
+
};
|
|
33
39
|
type RouteBodyOption<T> = T extends {
|
|
34
40
|
body: RawBodySchema;
|
|
35
41
|
} ? {
|
|
36
42
|
body: BodyInit | null;
|
|
37
43
|
} : {};
|
|
38
|
-
export type RouteOptions<T extends RouteSchema = any> =
|
|
39
|
-
/** Headers for this request. Undefined values are removed before `fetch`. */
|
|
40
|
-
headers?: HeaderInput<T>;
|
|
41
|
-
};
|
|
44
|
+
export type RouteOptions<T extends RouteSchema = any> = RouteFetchOptions<T> & RouteBodyOption<T>;
|
|
42
45
|
export {};
|
package/docs/context.md
CHANGED
|
@@ -85,7 +85,7 @@ Method schemas describe the request pieces Rouzer should validate:
|
|
|
85
85
|
| Action helper | Request schemas | Notes |
|
|
86
86
|
| --------------------------------- | -------------------------------------- | ---------------- |
|
|
87
87
|
| `http.get(...)` | `path`, `query`, `headers`, `response` | No request body. |
|
|
88
|
-
| `http.post/put/patch/delete(...)` | `path`, `body`, `headers`, `response` | No query schema. |
|
|
88
|
+
| `http.post/put/patch/delete(...)` | `path`, `body`, `headers`, `response` | No query schema. `body` is a Zod object for JSON or `http.rawBody()` for pass-through payloads. |
|
|
89
89
|
|
|
90
90
|
If you omit a `path` schema, TypeScript infers path params from the pattern and
|
|
91
91
|
server handlers receive them as strings. Add a Zod `path` schema when you need
|
|
@@ -213,6 +213,12 @@ requests with an `Origin` header.
|
|
|
213
213
|
|
|
214
214
|
`createClient({ baseURL, routes })` creates a client tree that mirrors
|
|
215
215
|
`routes`, with action functions such as `client.profiles.get(args)`.
|
|
216
|
+
Generated action functions accept a flattened first argument containing path,
|
|
217
|
+
query, and JSON body fields. Per-request `RequestInit` options, including
|
|
218
|
+
headers and abort signals, are passed as the optional second argument. For
|
|
219
|
+
`http.rawBody()` routes, the raw `BodyInit` payload is passed through to `fetch`
|
|
220
|
+
without JSON encoding.
|
|
221
|
+
|
|
216
222
|
Generated action functions include:
|
|
217
223
|
|
|
218
224
|
- raw `Response` results for actions without a response schema
|
|
@@ -244,8 +250,8 @@ route actions named `config` remain available as `client.config(...)`.
|
|
|
244
250
|
are needed.
|
|
245
251
|
3. Create a client with the same route tree, plus matching client response
|
|
246
252
|
plugins when needed.
|
|
247
|
-
4. Client action calls validate `path`, `query`, `body`, and
|
|
248
|
-
`fetch`.
|
|
253
|
+
4. Client action calls validate `path`, `query`, JSON object `body`, and
|
|
254
|
+
`headers` before `fetch`. Raw bodies are passed through without validation.
|
|
249
255
|
5. The router matches the request, validates the matched inputs, and calls the
|
|
250
256
|
handler.
|
|
251
257
|
6. Plain handler results become JSON responses, response-map helpers choose
|
|
@@ -255,22 +261,29 @@ route actions named `config` remain available as `client.config(...)`.
|
|
|
255
261
|
On the server, `path`, `query`, and `headers` values originate as strings. Rouzer
|
|
256
262
|
coerces Zod `number` schemas with `Number(value)` and Zod `boolean` schemas from
|
|
257
263
|
`"true"` and `"false"`. JSON request bodies are parsed and validated without that
|
|
258
|
-
string-coercion step.
|
|
264
|
+
string-coercion step. Raw request bodies declared with `http.rawBody()` are not
|
|
265
|
+
parsed by Rouzer.
|
|
259
266
|
|
|
260
267
|
## Common tasks
|
|
261
268
|
|
|
262
269
|
### Call client actions
|
|
263
270
|
|
|
264
|
-
Use generated client action functions for application calls
|
|
271
|
+
Use generated client action functions for application calls. The first argument
|
|
272
|
+
is a flat object containing all path, query, and JSON body fields. The optional
|
|
273
|
+
second argument is for per-request `RequestInit` options such as headers or an
|
|
274
|
+
abort signal.
|
|
265
275
|
|
|
266
276
|
```ts
|
|
267
|
-
await client.profiles.get({
|
|
268
|
-
await client.profiles.update(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
277
|
+
await client.profiles.get({ id: '42', includePosts: true })
|
|
278
|
+
await client.profiles.update(
|
|
279
|
+
{ id: '42', name: 'Ada' },
|
|
280
|
+
{ headers: { 'x-request-id': 'docs' } }
|
|
281
|
+
)
|
|
272
282
|
```
|
|
273
283
|
|
|
284
|
+
Avoid duplicate field names across an action's path, query, and body schemas;
|
|
285
|
+
the client input is flat, so duplicate keys cannot represent separate values.
|
|
286
|
+
|
|
274
287
|
### Handle declared error responses
|
|
275
288
|
|
|
276
289
|
Use `$error<T>()` inside a response map when an error status is part of the route
|
|
@@ -308,9 +321,7 @@ const client = createClient({
|
|
|
308
321
|
routes,
|
|
309
322
|
})
|
|
310
323
|
|
|
311
|
-
const [error, user, status] = await client.getUser({
|
|
312
|
-
path: { id: 'missing' },
|
|
313
|
-
})
|
|
324
|
+
const [error, user, status] = await client.getUser({ id: 'missing' })
|
|
314
325
|
|
|
315
326
|
if (status === 404) {
|
|
316
327
|
console.log(error.message)
|
|
@@ -384,11 +395,47 @@ export const organizations = http.resource('orgs/:orgId', {
|
|
|
384
395
|
}),
|
|
385
396
|
})
|
|
386
397
|
|
|
387
|
-
await client.organizations.members.get({
|
|
388
|
-
|
|
398
|
+
await client.organizations.members.get({ orgId: 'acme', memberId: '42' })
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
### Send raw request bodies
|
|
402
|
+
|
|
403
|
+
Use `http.rawBody()` for mutation actions whose client should pass a `BodyInit`
|
|
404
|
+
through to `fetch` without JSON encoding or Zod body parsing:
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
export const uploadAvatar = http.post('profiles/:id/avatar', {
|
|
408
|
+
body: http.rawBody(),
|
|
409
|
+
headers: z.object({ 'content-type': z.string() }),
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
await client.uploadAvatar(
|
|
413
|
+
{ id: '42' },
|
|
414
|
+
{ body: file, headers: { 'content-type': file.type } }
|
|
415
|
+
)
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
When a raw-body route has path or query input, path/query fields still live in
|
|
419
|
+
the flat first argument. The raw body itself is passed as `body` in the second
|
|
420
|
+
argument because it is a `RequestInit` value.
|
|
421
|
+
|
|
422
|
+
For raw-body routes without path or query input, the generated client action
|
|
423
|
+
accepts the body as the first argument and fetch options as the second:
|
|
424
|
+
|
|
425
|
+
```ts
|
|
426
|
+
export const upload = http.post('uploads', {
|
|
427
|
+
body: http.rawBody(),
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
await client.upload(file, {
|
|
431
|
+
headers: { 'content-type': file.type },
|
|
389
432
|
})
|
|
390
433
|
```
|
|
391
434
|
|
|
435
|
+
Server handlers for raw-body routes read from `ctx.request` directly with Fetch
|
|
436
|
+
APIs such as `arrayBuffer()`, `blob()`, `formData()`, or `text()`. Rouzer does
|
|
437
|
+
not parse or validate raw request bodies.
|
|
438
|
+
|
|
392
439
|
### Return custom responses
|
|
393
440
|
|
|
394
441
|
Return a `Response` from a handler for non-JSON payloads, custom status codes, or
|
|
@@ -434,7 +481,8 @@ export const profiles = http.resource('profiles/:id', {
|
|
|
434
481
|
export const routes = { profiles }
|
|
435
482
|
```
|
|
436
483
|
|
|
437
|
-
Handler maps
|
|
484
|
+
Handler maps mirror the action names, while v5 client calls use flat input
|
|
485
|
+
objects:
|
|
438
486
|
|
|
439
487
|
```ts
|
|
440
488
|
createRouter().use(routes, {
|
|
@@ -448,11 +496,8 @@ createRouter().use(routes, {
|
|
|
448
496
|
},
|
|
449
497
|
})
|
|
450
498
|
|
|
451
|
-
await client.profiles.get({
|
|
452
|
-
await client.profiles.update({
|
|
453
|
-
path: { id: '42' },
|
|
454
|
-
body: { name: 'Ada' },
|
|
455
|
-
})
|
|
499
|
+
await client.profiles.get({ id: '42' })
|
|
500
|
+
await client.profiles.update({ id: '42', name: 'Ada' })
|
|
456
501
|
```
|
|
457
502
|
|
|
458
503
|
## Patterns to prefer
|
|
@@ -480,8 +525,8 @@ await client.profiles.update({
|
|
|
480
525
|
- `$type<T>()`, `$error<T>()`, and `ndjson.$type<T>()` are compile-time-only type
|
|
481
526
|
contracts. Rouzer does not re-validate handler return values at the server
|
|
482
527
|
boundary.
|
|
483
|
-
- NDJSON support is for response streams; request bodies
|
|
484
|
-
|
|
528
|
+
- NDJSON support is for response streams; request bodies use JSON body schemas
|
|
529
|
+
unless an action declares `body: http.rawBody()`.
|
|
485
530
|
- Declared `$error<T>()` responses are JSON responses. Use a custom `Response`
|
|
486
531
|
for non-JSON error payloads.
|
|
487
532
|
- Routes that use a response plugin fail fast if the matching client or router
|
|
@@ -489,10 +534,12 @@ await client.profiles.update({
|
|
|
489
534
|
- Pathname route patterns expect an absolute client `baseURL`.
|
|
490
535
|
- Resource and action keys are API names only; paths come from the pattern
|
|
491
536
|
strings passed to `http.resource(...)` and action helpers.
|
|
492
|
-
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
537
|
+
- Path, query, and JSON body fields are flattened into the first client action
|
|
538
|
+
argument. Per-request `RequestInit` fields, such as `signal`, `credentials`,
|
|
539
|
+
and `headers`, belong in the second argument. `method` is reserved by Rouzer.
|
|
540
|
+
For `http.rawBody()` actions, `body` is accepted in the second argument when
|
|
541
|
+
the route has path or query input; raw-body actions without route input accept
|
|
542
|
+
the body as the first argument.
|
|
496
543
|
- The HTTP action API has no `ALL` fallback route. Declare explicit actions for
|
|
497
544
|
supported methods.
|
|
498
545
|
- Rouzer does not automatically set `Access-Control-Allow-Credentials`; set it in
|