rouzer 5.3.1 → 6.0.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.
@@ -0,0 +1,118 @@
1
+ # Framework concepts
2
+
3
+ > Use the route tree as the shared contract, then decide which behavior belongs
4
+ > in routes, handlers, clients, or middleware.
5
+
6
+ Rouzer is for applications that can share a TypeScript HTTP contract between
7
+ server and client code. The central object is an HTTP route tree. That tree
8
+ describes URL paths, action names, request schemas, and response contracts once,
9
+ then the router and generated client reuse it.
10
+
11
+ Rouzer is not an OpenAPI generator, a response validator, or a full application
12
+ framework. It focuses on shared route contracts, request validation, typed
13
+ handlers, typed clients, response helpers, middleware, and request context.
14
+
15
+ ## The Main Objects
16
+
17
+ Route tree:
18
+
19
+ - a plain object whose leaves are HTTP actions and whose branches are resources
20
+ - exported from a shared module that server and client code can both import
21
+ - passed to `createRouter().use(routes, handlers)` and
22
+ `createClient({ routes, baseURL })`
23
+
24
+ Action:
25
+
26
+ - a concrete HTTP operation declared with `http.get`, `http.post`, `http.put`,
27
+ `http.patch`, or `http.delete`
28
+ - owns an optional action-local path, request schemas, and an optional response
29
+ marker or response map
30
+ - becomes one handler function on the server and one generated client function
31
+
32
+ Resource:
33
+
34
+ - a path-scoped namespace declared with `http.resource(path, children)`
35
+ - contributes path params to child actions
36
+ - creates nested handler and client objects
37
+
38
+ Router:
39
+
40
+ - a fetch-compatible request handler returned by `createRouter(...)`
41
+ - accepts middleware with `.use(middleware)`
42
+ - accepts route trees with `.use(routes, handlers)`
43
+ - validates matched requests before handlers run
44
+
45
+ Client:
46
+
47
+ - a typed fetch wrapper returned by `createClient({ baseURL, routes })`
48
+ - mirrors the route tree shape
49
+ - validates client input before sending requests
50
+ - parses responses according to the route response contract
51
+
52
+ Middleware:
53
+
54
+ - functions that receive a shared `RequestContext`
55
+ - can add typed properties, environment bindings, response callbacks, or runtime
56
+ type markers
57
+ - run before route handlers when attached before `.use(routes, handlers)`
58
+
59
+ ## What Validation Covers
60
+
61
+ Rouzer validates request inputs:
62
+
63
+ - path params from route patterns or a `path` Zod object
64
+ - URL query values on `GET` actions with a `query` schema
65
+ - JSON request bodies on mutation actions with a Zod `body` schema
66
+ - request headers with a `headers` schema
67
+
68
+ Path, query, and header values arrive as strings. Rouzer adds string parsing for
69
+ Zod number and boolean schemas in those locations, including nested object and
70
+ array schemas. JSON request bodies are parsed from the request body and then
71
+ validated as JSON values.
72
+
73
+ > [!IMPORTANT]
74
+ > Rouzer does not validate handler return values just because a route uses
75
+ > `$type<T>()`, `$error<T>()`, or `ndjson.$type<T>()`. Those markers are
76
+ > TypeScript contracts. Validate untrusted response data where it enters your
77
+ > system.
78
+
79
+ ## Lifecycle
80
+
81
+ ```ts
82
+ // shared/routes.ts
83
+ export const routes = {
84
+ profiles: http.resource('profiles/:id', {
85
+ get: http.get({ response: $type<Profile>() }),
86
+ }),
87
+ }
88
+
89
+ // server.ts
90
+ export const router = createRouter()
91
+ .use(requestMiddleware)
92
+ .use(routes, handlers)
93
+
94
+ // client.ts
95
+ export const client = createClient({
96
+ baseURL: 'https://example.com/api/',
97
+ routes,
98
+ })
99
+ ```
100
+
101
+ The same action key becomes:
102
+
103
+ - `handlers.profiles.get(ctx)` on the server
104
+ - `client.profiles.get(input, options)` on the client
105
+ - a route name such as `profiles.get` in client lifecycle hook events
106
+
107
+ ## Rouzer Middleware
108
+
109
+ Rouzer's router is both callable as a request handler and chainable with
110
+ middleware helpers.
111
+
112
+ Use route contracts for HTTP handler/client behavior. Use middleware when you
113
+ need request-scoped state, authentication, environment bindings, host runtime
114
+ data, response callbacks, background work, or custom adapter contexts.
115
+
116
+ The common middleware API is exported from `rouzer`, including `chain`,
117
+ `toFetchHandler`, `createContext`, `filterRuntime`, `RequestContext`, and
118
+ `RequestHandler`.
@@ -0,0 +1,18 @@
1
+ {
2
+ "projectName": "Rouzer",
3
+ "navigation": {
4
+ "order": [
5
+ "index.md",
6
+ "concepts.md",
7
+ "routes.md",
8
+ "middleware.md",
9
+ "handlers.md",
10
+ "client.md",
11
+ "responses.md",
12
+ "streaming.md",
13
+ "runtime.md",
14
+ "patterns.md",
15
+ "migration-v5-to-v6.md"
16
+ ]
17
+ }
18
+ }
@@ -0,0 +1,207 @@
1
+ # Routers and handlers
2
+
3
+ > Attach a shared route tree to its handler map, then order middleware so every
4
+ > handler receives the validated request context it expects.
5
+
6
+ `createRouter()` returns a fetch-compatible Rouzer handler with chain methods.
7
+ Use it to compose middleware and attach route trees.
8
+
9
+ ```ts
10
+ import { createRouter } from 'rouzer'
11
+
12
+ export const router = createRouter({ basePath: 'api/' })
13
+ .use(requestMiddleware)
14
+ .use(routes, handlers)
15
+ ```
16
+
17
+ The router is also a `RequestHandler`, so it can be mounted with
18
+ `toFetchHandler(router)` or runtime-specific adapter helpers.
19
+
20
+ ## Handler Maps
21
+
22
+ The handler object mirrors the route tree. Resource nodes become nested objects.
23
+ Action nodes become handler functions.
24
+
25
+ ```ts
26
+ export const routes = {
27
+ profiles: http.resource('profiles/:id', {
28
+ get: http.get({ response: $type<Profile>() }),
29
+ update: http.patch({
30
+ body: updateProfileSchema,
31
+ response: $type<Profile>(),
32
+ }),
33
+ posts: http.resource('posts', {
34
+ list: http.get({ response: $type<Post[]>() }),
35
+ }),
36
+ }),
37
+ }
38
+
39
+ createRouter().use(routes, {
40
+ profiles: {
41
+ get(ctx) {
42
+ return loadProfile(ctx.path.id)
43
+ },
44
+ update(ctx) {
45
+ return updateProfile(ctx.path.id, ctx.body)
46
+ },
47
+ posts: {
48
+ list(ctx) {
49
+ return listPosts(ctx.path.id)
50
+ },
51
+ },
52
+ },
53
+ })
54
+ ```
55
+
56
+ ## Handler Context
57
+
58
+ Handlers receive the Rouzer request context plus values inferred from the
59
+ matched route.
60
+
61
+ `GET` handlers receive:
62
+
63
+ - `ctx.path`
64
+ - `ctx.query`
65
+ - `ctx.headers`
66
+
67
+ Mutation handlers receive:
68
+
69
+ - `ctx.path`
70
+ - `ctx.body`
71
+ - `ctx.headers`
72
+
73
+ All handlers also receive middleware-provided properties and base context fields
74
+ such as `ctx.request`, `ctx.url`, `ctx.host`, `ctx.env(...)`,
75
+ `ctx.waitUntil(...)`, `ctx.setHeader(...)`, and `ctx.onResponse(...)`.
76
+
77
+ ```ts
78
+ const requestInfo = chain().use(ctx => ({
79
+ requestId: ctx.request.headers.get('x-request-id') ?? 'local',
80
+ }))
81
+
82
+ createRouter()
83
+ .use(requestInfo)
84
+ .use(routes, {
85
+ getProfile(ctx) {
86
+ return {
87
+ id: ctx.path.id,
88
+ requestId: ctx.requestId,
89
+ }
90
+ },
91
+ })
92
+ ```
93
+
94
+ ## Validation Order
95
+
96
+ For a matched route, Rouzer validates before the handler runs:
97
+
98
+ 1. path params, if a `path` schema is declared
99
+ 2. headers, if a `headers` schema is declared
100
+ 3. query string, for `GET` actions with a `query` schema
101
+ 4. JSON body, for mutation actions with a non-raw `body` schema
102
+
103
+ > [!NOTE]
104
+ > If validation fails, Rouzer returns a `400` JSON response and does not call
105
+ > the handler. In `debug` mode, validation responses include more specific Zod
106
+ > error messages.
107
+
108
+ If no `path` schema is declared, `ctx.path` is inferred from the route pattern
109
+ and contains string params.
110
+
111
+ ## Return Values
112
+
113
+ Handlers can return:
114
+
115
+ - a plain value, which Rouzer sends with `Response.json(value)`
116
+ - a Web `Response`, which Rouzer passes through unchanged
117
+ - `ctx.error(status, body)` for a declared error response-map entry
118
+ - `ctx.success(status, body)` for a declared non-default success response-map
119
+ entry
120
+ - a response-plugin value, such as an NDJSON iterable, when the route response
121
+ marker and router plugin match
122
+
123
+ Return a `Response` when you need custom status codes, headers, redirects,
124
+ non-JSON payloads, or hand-written error bodies.
125
+
126
+ ## Response Maps In Handlers
127
+
128
+ Routes with a status-keyed response map add `ctx.error` and `ctx.success`
129
+ helpers.
130
+
131
+ ```ts
132
+ export const getUser = http.get('users/:id', {
133
+ response: {
134
+ 200: $type<User>(),
135
+ 201: $type<User>(),
136
+ 404: $error<NotFound>(),
137
+ },
138
+ })
139
+
140
+ createRouter().use(
141
+ { getUser },
142
+ {
143
+ getUser(ctx) {
144
+ if (ctx.path.id === 'created') {
145
+ return ctx.success(201, { id: 'created', name: 'Grace' })
146
+ }
147
+
148
+ const user = users.get(ctx.path.id)
149
+ if (!user) {
150
+ return ctx.error(404, {
151
+ code: 'NOT_FOUND',
152
+ message: 'User not found',
153
+ })
154
+ }
155
+
156
+ return user
157
+ },
158
+ }
159
+ )
160
+ ```
161
+
162
+ The helpers only accept statuses and body types declared in the response map.
163
+
164
+ ## Router Configuration
165
+
166
+ `createRouter(config)` accepts:
167
+
168
+ | Option | Purpose |
169
+ | ------------------- | -------------------------------------------------------------------------------------------------------------- |
170
+ | `basePath` | Prepends a normalized path prefix to all route patterns. |
171
+ | `debug` | Adds matched-route debug headers, logs missing route handlers, and includes more specific validation messages. |
172
+ | `plugins` | Router response plugins such as `ndjson.routerPlugin`. |
173
+ | `cors.allowOrigins` | Restricts requests with an `Origin` header. |
174
+
175
+ ```ts
176
+ createRouter({
177
+ basePath: 'api/',
178
+ debug: process.env.NODE_ENV === 'development',
179
+ cors: {
180
+ allowOrigins: ['example.net', 'https://*.example.com'],
181
+ },
182
+ })
183
+ ```
184
+
185
+ When an allowed request includes an `Origin` header, Rouzer sets
186
+ `Access-Control-Allow-Origin` to that origin. For preflight requests, Rouzer
187
+ returns `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, and
188
+ `Access-Control-Allow-Headers`.
189
+
190
+ > [!WARNING]
191
+ > Rouzer does not set `Access-Control-Allow-Credentials`. Set that header
192
+ > yourself before relying on credentialed cross-origin requests.
193
+
194
+ ## Middleware Ordering
195
+
196
+ Attach middleware before route handlers that depend on it.
197
+
198
+ ```ts
199
+ createRouter().use(authMiddleware).use(routes, secureHandlers)
200
+ ```
201
+
202
+ Middleware attached after a route tree only runs if an earlier route handler
203
+ does not produce a response. Most applications put shared middleware before
204
+ route registration.
205
+
206
+ Use a middleware `Response` return for cross-cutting rejection, such as auth or
207
+ rate limits. Use route handlers for route-specific application results.
package/docs/index.md ADDED
@@ -0,0 +1,103 @@
1
+ # Rouzer documentation
2
+
3
+ > Choose the guide that matches your next Rouzer decision, from defining a
4
+ > shared route contract to mounting the completed router in a Fetch runtime.
5
+
6
+ Rouzer combines a shared TypeScript route tree, a fetch-compatible server
7
+ router, a typed fetch client, and request context composition. The guides keep
8
+ those concerns separate while showing where they meet.
9
+
10
+ ## Learning Path
11
+
12
+ 1. Read [Framework concepts](concepts.md) for the lifecycle and boundaries.
13
+ 2. Read [Route contracts](routes.md) to declare resources, actions, schemas, and
14
+ metadata.
15
+ 3. Read [Middleware and request context](middleware.md) before writing shared
16
+ context, auth, environment, tracing, or runtime-specific middleware.
17
+ 4. Read [Routers and handlers](handlers.md) to attach route trees and implement
18
+ handlers.
19
+ 5. Read [Typed client](client.md) to call the same route tree from application
20
+ code.
21
+ 6. Read [Responses, errors, and plugins](responses.md) and
22
+ [NDJSON streaming](streaming.md) when routes need more than a simple JSON
23
+ success body.
24
+ 7. Read [Runtime and adapters](runtime.md) when mounting Rouzer in a server or
25
+ test harness.
26
+ 8. Read [Patterns, constraints, and migrations](patterns.md) before settling on
27
+ a project structure or reviewing an integration.
28
+
29
+ > [!IMPORTANT]
30
+ > Upgrading an existing v5 application is a different path. Start with
31
+ > [Migration from v5 to v6](migration-v5-to-v6.md), then return to the focused
32
+ > guides when a changed boundary needs more detail.
33
+
34
+ ## Guide Map
35
+
36
+ | Guide | Concern |
37
+ | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
38
+ | [Framework concepts](concepts.md) | The high-level model, request lifecycle, and Rouzer's framework responsibilities. |
39
+ | [Route contracts](routes.md) | `rouzer/http` resources, actions, Zod schemas, raw bodies, path patterns, and metadata. |
40
+ | [Middleware and request context](middleware.md) | `chain`, request plugins, `RequestContext`, `context.host`, env access, `waitUntil`, `onResponse`, `passThrough`, isolation, and runtime filtering. |
41
+ | [Routers and handlers](handlers.md) | `createRouter`, route handler maps, validated handler context, CORS, debug mode, middleware ordering, and handler return values. |
42
+ | [Typed client](client.md) | `createClient`, generated action functions, flat input objects, headers, custom fetch, `onJsonError`, and lifecycle hooks. |
43
+ | [Responses, errors, and plugins](responses.md) | `$type`, `$error`, response maps, status tuples, custom `Response` returns, and response plugin contracts. |
44
+ | [NDJSON streaming](streaming.md) | Streaming response markers, router/client plugin registration, cancellation, and stream error modeling. |
45
+ | [Runtime and adapters](runtime.md) | Root `toFetchHandler`, fetch-compatible mounting, custom host data, tests, CORS, and background work. |
46
+ | [Patterns, constraints, and migrations](patterns.md) | Preferred project structure, common constraints, and migration notes from older Rouzer shapes. |
47
+ | [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. |
48
+
49
+ ## Request Lifecycle
50
+
51
+ The route tree is the shared contract. The client uses it to construct the
52
+ request, and the router uses it to validate and dispatch that request.
53
+
54
+ ```mermaid
55
+ flowchart TD
56
+ routes["Shared route tree"]
57
+ client["Generated client"]
58
+ router["Router and handler map"]
59
+ request["Validated fetch request"]
60
+ middleware["Middleware chain"]
61
+ handler["Typed route handler"]
62
+ response["JSON, Response, response map, or plugin value"]
63
+ finalize["Response callbacks"]
64
+
65
+ routes --> client
66
+ routes --> router
67
+ client -->|"validate input and fetch"| request
68
+ request --> middleware
69
+ middleware --> router
70
+ router -->|"match and validate"| handler
71
+ handler --> response
72
+ response --> finalize
73
+ ```
74
+
75
+ After response callbacks finish, the Fetch-compatible handler returns the final
76
+ Web `Response` to the runtime.
77
+
78
+ ## Runnable Examples
79
+
80
+ | Example | Shows |
81
+ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- |
82
+ | [Shared route, router, and client](https://github.com/alloc/rouzer/blob/main/examples/basic-usage.ts) | A complete JSON route from declaration through a client call. |
83
+ | [Declared status responses](https://github.com/alloc/rouzer/blob/main/examples/error-responses.ts) | Typed success and error tuples from a response map. |
84
+ | [NDJSON response stream](https://github.com/alloc/rouzer/blob/main/examples/ndjson-stream.ts) | Plugin registration, streaming, and incremental consumption. |
85
+
86
+ ## Framework Surface
87
+
88
+ Rouzer owns:
89
+
90
+ - route tree declarations and handler/client type inference
91
+ - request validation from route schemas
92
+ - route matching and handler dispatch
93
+ - response markers, response maps, and response plugin integration
94
+ - generated client action functions
95
+
96
+ Rouzer middleware and runtime helpers cover:
97
+
98
+ - middleware chaining and short-circuit behavior
99
+ - request context creation and extension
100
+ - host data such as `context.host.ip` and `context.host.runtime`
101
+ - typed environment access through `context.env(...)`
102
+ - `waitUntil`, `setHeader`, `onResponse`, `passThrough`, and chain isolation
103
+ - adapter helpers such as `toFetchHandler`
@@ -0,0 +1,191 @@
1
+ # Middleware and request context
2
+
3
+ > Put cross-cutting request behavior in an ordered chain and expose only the
4
+ > typed context values that downstream middleware and handlers need.
5
+
6
+ Rouzer includes request context and middleware composition in its root API, so
7
+ most applications can import route, middleware, and adapter helpers from one
8
+ package.
9
+
10
+ ```ts
11
+ import {
12
+ chain,
13
+ filterRuntime,
14
+ type Middleware,
15
+ type MiddlewareContext,
16
+ type RequestContext,
17
+ type RequestHandler,
18
+ } from 'rouzer'
19
+ ```
20
+
21
+ ## Middleware Chain
22
+
23
+ `chain()` creates a `MiddlewareChain`. Rouzer routers are middleware chains too,
24
+ so you can append middleware before attaching routes.
25
+
26
+ ```ts
27
+ const requestInfo = chain().use(ctx => ({
28
+ requestId: ctx.request.headers.get('x-request-id') ?? crypto.randomUUID(),
29
+ }))
30
+
31
+ const router = createRouter()
32
+ .use(requestInfo)
33
+ .use(routes, {
34
+ getProfile(ctx) {
35
+ return loadProfile(ctx.path.id, ctx.requestId)
36
+ },
37
+ })
38
+ ```
39
+
40
+ Middleware runs in order. A middleware can:
41
+
42
+ - return `void` or `undefined` to continue without adding context
43
+ - return a `Response` to stop the chain and send that response
44
+ - return a request plugin object whose properties are merged into the downstream
45
+ context
46
+ - call `ctx.passThrough()` to skip the rest of the current chain
47
+
48
+ ## Request Context
49
+
50
+ Every middleware and Rouzer handler receives a `RequestContext`.
51
+
52
+ | Property | Purpose |
53
+ | ---------------------------- | ----------------------------------------------------------------------------- |
54
+ | `ctx.request` | The Web `Request`. |
55
+ | `ctx.url` | Lazily parsed `URL` for `ctx.request.url`. |
56
+ | `ctx.host` | Host data such as `ip`, `runtime`, `env`, and `waitUntil`. |
57
+ | `ctx.env(name)` | Typed environment lookup backed by `ctx.host.env` and middleware env plugins. |
58
+ | `ctx.waitUntil(promise)` | Delegates background work to `ctx.host.waitUntil` when available. |
59
+ | `ctx.setHeader(name, value)` | Sets a response header from request middleware. |
60
+ | `ctx.onResponse(callback)` | Registers a callback that can mutate or replace the final response. |
61
+ | `ctx.passThrough()` | Chain-local control flow for unresolved requests. |
62
+
63
+ Host-provided values live under `ctx.host`.
64
+
65
+ ```ts
66
+ const runtimeName = ctx.host.runtime?.name
67
+ const ip = ctx.host.ip
68
+ ```
69
+
70
+ Runtime-specific request data should stay behind `ctx.host.runtime`.
71
+
72
+ ## Adding Context Properties
73
+
74
+ Return a plain object to make properties available to downstream middleware and
75
+ handlers.
76
+
77
+ ```ts
78
+ const sessionMiddleware = chain().use(async ctx => {
79
+ const token = ctx.request.headers.get('authorization')
80
+ const session = token ? await readSession(token) : null
81
+
82
+ if (!session) {
83
+ return new Response('Unauthorized', { status: 401 })
84
+ }
85
+
86
+ return { session }
87
+ })
88
+ ```
89
+
90
+ Attach the middleware before route handlers that need the property.
91
+
92
+ ```ts
93
+ createRouter()
94
+ .use(sessionMiddleware)
95
+ .use(routes, {
96
+ me(ctx) {
97
+ return ctx.session.user
98
+ },
99
+ })
100
+ ```
101
+
102
+ Treat context property names as owned by the middleware that introduces them.
103
+ Avoid collisions between unrelated middleware.
104
+
105
+ ## Environment Bindings
106
+
107
+ Return an `env` object to add typed variables behind `ctx.env(...)`.
108
+
109
+ ```ts
110
+ const envMiddleware = chain().use(() => ({
111
+ env: {
112
+ DATABASE_URL: process.env.DATABASE_URL,
113
+ },
114
+ }))
115
+
116
+ const router = createRouter()
117
+ .use(envMiddleware)
118
+ .use(routes, {
119
+ health(ctx) {
120
+ return { configured: Boolean(ctx.env('DATABASE_URL')) }
121
+ },
122
+ })
123
+ ```
124
+
125
+ > [!IMPORTANT]
126
+ > The `env` key is reserved for environment bindings. Read its values through
127
+ > `ctx.env(name)`, not as `ctx.env.DATABASE_URL`.
128
+
129
+ ## Runtime Type Markers
130
+
131
+ > [!IMPORTANT]
132
+ > The `runtime` request plugin key is a type-level marker for
133
+ > `ctx.host.runtime`; it does not add `ctx.runtime`.
134
+
135
+ ```ts
136
+ const nodeOnly = chain()
137
+ .use(filterRuntime<{ name: 'node' }>('node'))
138
+ .use(ctx => {
139
+ ctx.host.runtime?.name
140
+ })
141
+ ```
142
+
143
+ `filterRuntime(name)` checks `ctx.host.runtime?.name`. If the runtime name does
144
+ not match, it calls `ctx.passThrough()`.
145
+
146
+ ## Response Callbacks
147
+
148
+ Use `ctx.onResponse(callback)` or return `{ onResponse }` when middleware needs
149
+ to finalize a response after a route handler runs.
150
+
151
+ ```ts
152
+ const timing = chain().use(ctx => {
153
+ const startedAt = Date.now()
154
+
155
+ ctx.onResponse(response => {
156
+ response.headers.set('server-timing', `app;dur=${Date.now() - startedAt}`)
157
+ })
158
+ })
159
+ ```
160
+
161
+ Use `ctx.setHeader(name, value)` from request middleware for simple headers.
162
+ Inside response callbacks, mutate `response.headers` directly or return a new
163
+ `Response`.
164
+
165
+ ## Isolation
166
+
167
+ Use `.isolate()` when you want to run a chain for side effects or early
168
+ responses without leaking its plugin properties into the parent chain.
169
+
170
+ ```ts
171
+ const optionalAuth = chain()
172
+ .use(readOptionalSession)
173
+ .use(auditSession)
174
+ .isolate()
175
+
176
+ const router = createRouter().use(optionalAuth).use(routes, handlers)
177
+ ```
178
+
179
+ An isolated chain can still return a `Response`. Its context additions stay
180
+ inside the isolated chain.
181
+
182
+ ## Pass Through
183
+
184
+ > [!NOTE]
185
+ > `ctx.passThrough()` is chain-local control flow, not an adapter pass-through.
186
+ > In a nested or isolated chain, it skips the rest of that chain and lets the
187
+ > parent chain continue. In a final fetch handler, an unresolved request becomes
188
+ > the default `404 Not Found` response.
189
+
190
+ Use `passThrough` for optional middleware branches or runtime filters. Use a
191
+ `Response` when you want to deliberately stop the request.