@mandujs/core 0.53.3 → 0.54.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 +654 -654
- package/package.json +1 -1
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -451
- package/src/diagnose/checks.ts +832 -832
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +93 -93
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
package/src/middleware/bridge.ts
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bridge wrappers — Phase 18.ε
|
|
3
|
-
*
|
|
4
|
-
* Adapters that expose Mandu's existing ctx-based middleware
|
|
5
|
-
* (`csrf` / `session` / `secure` / `rateLimit`) through the canonical
|
|
6
|
-
* request-level {@link Middleware} interface. Both call styles remain
|
|
7
|
-
* supported forever — these bridges exist purely so the new
|
|
8
|
-
* `compose(...)` API can slot in a familiar primitive without wiring
|
|
9
|
-
* boilerplate.
|
|
10
|
-
*
|
|
11
|
-
* The bridge pattern:
|
|
12
|
-
*
|
|
13
|
-
* 1. Build a throwaway `ManduContext` from the incoming Request so the
|
|
14
|
-
* ctx-based handler has the API surface it expects (`ctx.headers`,
|
|
15
|
-
* `ctx.cookies`, `ctx.forbidden()`, etc.).
|
|
16
|
-
* 2. Invoke the ctx-based handler.
|
|
17
|
-
* 3. If it returns a Response, treat that as a short-circuit.
|
|
18
|
-
* 4. Otherwise call `next()` to continue the chain, then apply any
|
|
19
|
-
* pending cookies the ctx-based handler wrote (e.g. CSRF cookie
|
|
20
|
-
* issuance, session commits) onto the downstream Response.
|
|
21
|
-
*
|
|
22
|
-
* Note: request-level composition happens BEFORE route matching, so the
|
|
23
|
-
* `ctx.params` is always `{}` at this stage. Middleware that depends on
|
|
24
|
-
* route params (unusual) should stick with `filling().use(...)`.
|
|
25
|
-
*/
|
|
26
|
-
import { defineMiddleware, type Middleware } from "./define";
|
|
27
|
-
import { ManduContext, type CookieManager } from "../filling/context";
|
|
28
|
-
import { csrf, type CsrfMiddlewareOptions } from "./csrf";
|
|
29
|
-
import {
|
|
30
|
-
session,
|
|
31
|
-
saveSession,
|
|
32
|
-
destroySession,
|
|
33
|
-
type SessionMiddlewareOptions,
|
|
34
|
-
} from "./session";
|
|
35
|
-
import {
|
|
36
|
-
secure as secureCtx,
|
|
37
|
-
type SecureMiddlewareOptions,
|
|
38
|
-
} from "./secure";
|
|
39
|
-
import {
|
|
40
|
-
rateLimit as rateLimitCtx,
|
|
41
|
-
type RateLimitMiddlewareOptions,
|
|
42
|
-
} from "./rate-limit";
|
|
43
|
-
|
|
44
|
-
// Re-export helpers so `saveSession` / `destroySession` are still reachable
|
|
45
|
-
// from call-sites that import from the bridge surface. Backward compat.
|
|
46
|
-
export { saveSession, destroySession };
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Copy pending Set-Cookie headers recorded on a `CookieManager` onto a
|
|
50
|
-
* downstream Response. Mirrors the end-of-request commit path used by
|
|
51
|
-
* `ManduContext.json()`/`ok()` so the ctx-based middleware's cookie
|
|
52
|
-
* writes (e.g. CSRF token issuance) survive the bridge.
|
|
53
|
-
*/
|
|
54
|
-
function applyCookies(cookies: CookieManager, response: Response): Response {
|
|
55
|
-
return cookies.hasPendingCookies() ? cookies.applyToResponse(response) : response;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Build a bridge middleware from a ctx-based `(ctx) => Response | void`
|
|
60
|
-
* handler. Short-circuits when the ctx handler returns a Response;
|
|
61
|
-
* otherwise continues the chain and folds the ctx's pending cookies
|
|
62
|
-
* back onto the final Response.
|
|
63
|
-
*/
|
|
64
|
-
function bridgeCtxMiddleware(
|
|
65
|
-
name: string,
|
|
66
|
-
build: () => (ctx: ManduContext) => Promise<Response | void> | Response | void
|
|
67
|
-
): Middleware {
|
|
68
|
-
const ctxHandler = build();
|
|
69
|
-
return defineMiddleware({
|
|
70
|
-
name,
|
|
71
|
-
async handler(req, next) {
|
|
72
|
-
const ctx = new ManduContext(req);
|
|
73
|
-
const early = await ctxHandler(ctx);
|
|
74
|
-
if (early instanceof Response) {
|
|
75
|
-
return applyCookies(ctx.cookies, early);
|
|
76
|
-
}
|
|
77
|
-
const response = await next();
|
|
78
|
-
return applyCookies(ctx.cookies, response);
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Request-level CSRF middleware — issues a double-submit cookie on safe
|
|
85
|
-
* methods and rejects with 403 when an unsafe request lacks a matching
|
|
86
|
-
* token. See {@link csrf} for the full semantics.
|
|
87
|
-
*/
|
|
88
|
-
export function csrfMiddleware(options: CsrfMiddlewareOptions): Middleware {
|
|
89
|
-
return bridgeCtxMiddleware("csrf", () => csrf(options));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Request-level session middleware — attaches a `Session` under the
|
|
94
|
-
* configured key. Commit remains caller-driven via {@link saveSession}
|
|
95
|
-
* / {@link destroySession}. See {@link session} for full semantics.
|
|
96
|
-
*
|
|
97
|
-
* Note: because composition runs pre-dispatch, the attached session is
|
|
98
|
-
* ONLY observable inside route handlers that read it via the same key
|
|
99
|
-
* (e.g. `ctx.get("session")`). The bridge's `ManduContext` is discarded
|
|
100
|
-
* after cookie-commit, so this bridge is strictly a convenience for
|
|
101
|
-
* session *hydration* + cookie roll-over; writes performed inside the
|
|
102
|
-
* route handler must still call `saveSession(routeCtx)` explicitly.
|
|
103
|
-
*/
|
|
104
|
-
export function sessionMiddleware(options: SessionMiddlewareOptions): Middleware {
|
|
105
|
-
return bridgeCtxMiddleware("session", () => session(options));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Request-level secure-headers middleware. Unlike the ctx-based
|
|
110
|
-
* `secure()` which uses `afterHandle` on a filling plugin, this bridge
|
|
111
|
-
* applies security headers via the `compose()` chain — it wraps the
|
|
112
|
-
* downstream Response and mutates its headers. See {@link secureCtx}
|
|
113
|
-
* for configuration.
|
|
114
|
-
*
|
|
115
|
-
* Implementation: calls the underlying `secure()` plugin's `afterHandle`
|
|
116
|
-
* hook directly against the downstream Response, skipping its
|
|
117
|
-
* `beforeHandle` (which only sets the CSP nonce for ctx-aware SSR — a
|
|
118
|
-
* request-level chain cannot thread a nonce into the route component,
|
|
119
|
-
* so nonce plumbing is intentionally out of scope for this bridge and
|
|
120
|
-
* users who need it should keep `.use(secure(...))` inline).
|
|
121
|
-
*/
|
|
122
|
-
export function secureMiddleware(options: SecureMiddlewareOptions = {}): Middleware {
|
|
123
|
-
const plugin = secureCtx(options);
|
|
124
|
-
return defineMiddleware({
|
|
125
|
-
name: "secure",
|
|
126
|
-
async handler(req, next) {
|
|
127
|
-
const response = await next();
|
|
128
|
-
const ctx = new ManduContext(req);
|
|
129
|
-
const afterHandle = plugin.afterHandle;
|
|
130
|
-
if (typeof afterHandle !== "function") return response;
|
|
131
|
-
const mutated = await afterHandle(ctx, response);
|
|
132
|
-
return mutated ?? response;
|
|
133
|
-
},
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Request-level rate-limit middleware. Shares the underlying
|
|
139
|
-
* `rateLimit()` store semantics (sliding window, pluggable store). When
|
|
140
|
-
* the limiter rejects, returns the configured 429 Response directly
|
|
141
|
-
* (short-circuits the chain).
|
|
142
|
-
*/
|
|
143
|
-
export function rateLimitMiddleware(
|
|
144
|
-
options: RateLimitMiddlewareOptions
|
|
145
|
-
): Middleware {
|
|
146
|
-
return bridgeCtxMiddleware("rate-limit", () => rateLimitCtx(options));
|
|
147
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Bridge wrappers — Phase 18.ε
|
|
3
|
+
*
|
|
4
|
+
* Adapters that expose Mandu's existing ctx-based middleware
|
|
5
|
+
* (`csrf` / `session` / `secure` / `rateLimit`) through the canonical
|
|
6
|
+
* request-level {@link Middleware} interface. Both call styles remain
|
|
7
|
+
* supported forever — these bridges exist purely so the new
|
|
8
|
+
* `compose(...)` API can slot in a familiar primitive without wiring
|
|
9
|
+
* boilerplate.
|
|
10
|
+
*
|
|
11
|
+
* The bridge pattern:
|
|
12
|
+
*
|
|
13
|
+
* 1. Build a throwaway `ManduContext` from the incoming Request so the
|
|
14
|
+
* ctx-based handler has the API surface it expects (`ctx.headers`,
|
|
15
|
+
* `ctx.cookies`, `ctx.forbidden()`, etc.).
|
|
16
|
+
* 2. Invoke the ctx-based handler.
|
|
17
|
+
* 3. If it returns a Response, treat that as a short-circuit.
|
|
18
|
+
* 4. Otherwise call `next()` to continue the chain, then apply any
|
|
19
|
+
* pending cookies the ctx-based handler wrote (e.g. CSRF cookie
|
|
20
|
+
* issuance, session commits) onto the downstream Response.
|
|
21
|
+
*
|
|
22
|
+
* Note: request-level composition happens BEFORE route matching, so the
|
|
23
|
+
* `ctx.params` is always `{}` at this stage. Middleware that depends on
|
|
24
|
+
* route params (unusual) should stick with `filling().use(...)`.
|
|
25
|
+
*/
|
|
26
|
+
import { defineMiddleware, type Middleware } from "./define";
|
|
27
|
+
import { ManduContext, type CookieManager } from "../filling/context";
|
|
28
|
+
import { csrf, type CsrfMiddlewareOptions } from "./csrf";
|
|
29
|
+
import {
|
|
30
|
+
session,
|
|
31
|
+
saveSession,
|
|
32
|
+
destroySession,
|
|
33
|
+
type SessionMiddlewareOptions,
|
|
34
|
+
} from "./session";
|
|
35
|
+
import {
|
|
36
|
+
secure as secureCtx,
|
|
37
|
+
type SecureMiddlewareOptions,
|
|
38
|
+
} from "./secure";
|
|
39
|
+
import {
|
|
40
|
+
rateLimit as rateLimitCtx,
|
|
41
|
+
type RateLimitMiddlewareOptions,
|
|
42
|
+
} from "./rate-limit";
|
|
43
|
+
|
|
44
|
+
// Re-export helpers so `saveSession` / `destroySession` are still reachable
|
|
45
|
+
// from call-sites that import from the bridge surface. Backward compat.
|
|
46
|
+
export { saveSession, destroySession };
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Copy pending Set-Cookie headers recorded on a `CookieManager` onto a
|
|
50
|
+
* downstream Response. Mirrors the end-of-request commit path used by
|
|
51
|
+
* `ManduContext.json()`/`ok()` so the ctx-based middleware's cookie
|
|
52
|
+
* writes (e.g. CSRF token issuance) survive the bridge.
|
|
53
|
+
*/
|
|
54
|
+
function applyCookies(cookies: CookieManager, response: Response): Response {
|
|
55
|
+
return cookies.hasPendingCookies() ? cookies.applyToResponse(response) : response;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Build a bridge middleware from a ctx-based `(ctx) => Response | void`
|
|
60
|
+
* handler. Short-circuits when the ctx handler returns a Response;
|
|
61
|
+
* otherwise continues the chain and folds the ctx's pending cookies
|
|
62
|
+
* back onto the final Response.
|
|
63
|
+
*/
|
|
64
|
+
function bridgeCtxMiddleware(
|
|
65
|
+
name: string,
|
|
66
|
+
build: () => (ctx: ManduContext) => Promise<Response | void> | Response | void
|
|
67
|
+
): Middleware {
|
|
68
|
+
const ctxHandler = build();
|
|
69
|
+
return defineMiddleware({
|
|
70
|
+
name,
|
|
71
|
+
async handler(req, next) {
|
|
72
|
+
const ctx = new ManduContext(req);
|
|
73
|
+
const early = await ctxHandler(ctx);
|
|
74
|
+
if (early instanceof Response) {
|
|
75
|
+
return applyCookies(ctx.cookies, early);
|
|
76
|
+
}
|
|
77
|
+
const response = await next();
|
|
78
|
+
return applyCookies(ctx.cookies, response);
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Request-level CSRF middleware — issues a double-submit cookie on safe
|
|
85
|
+
* methods and rejects with 403 when an unsafe request lacks a matching
|
|
86
|
+
* token. See {@link csrf} for the full semantics.
|
|
87
|
+
*/
|
|
88
|
+
export function csrfMiddleware(options: CsrfMiddlewareOptions): Middleware {
|
|
89
|
+
return bridgeCtxMiddleware("csrf", () => csrf(options));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Request-level session middleware — attaches a `Session` under the
|
|
94
|
+
* configured key. Commit remains caller-driven via {@link saveSession}
|
|
95
|
+
* / {@link destroySession}. See {@link session} for full semantics.
|
|
96
|
+
*
|
|
97
|
+
* Note: because composition runs pre-dispatch, the attached session is
|
|
98
|
+
* ONLY observable inside route handlers that read it via the same key
|
|
99
|
+
* (e.g. `ctx.get("session")`). The bridge's `ManduContext` is discarded
|
|
100
|
+
* after cookie-commit, so this bridge is strictly a convenience for
|
|
101
|
+
* session *hydration* + cookie roll-over; writes performed inside the
|
|
102
|
+
* route handler must still call `saveSession(routeCtx)` explicitly.
|
|
103
|
+
*/
|
|
104
|
+
export function sessionMiddleware(options: SessionMiddlewareOptions): Middleware {
|
|
105
|
+
return bridgeCtxMiddleware("session", () => session(options));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Request-level secure-headers middleware. Unlike the ctx-based
|
|
110
|
+
* `secure()` which uses `afterHandle` on a filling plugin, this bridge
|
|
111
|
+
* applies security headers via the `compose()` chain — it wraps the
|
|
112
|
+
* downstream Response and mutates its headers. See {@link secureCtx}
|
|
113
|
+
* for configuration.
|
|
114
|
+
*
|
|
115
|
+
* Implementation: calls the underlying `secure()` plugin's `afterHandle`
|
|
116
|
+
* hook directly against the downstream Response, skipping its
|
|
117
|
+
* `beforeHandle` (which only sets the CSP nonce for ctx-aware SSR — a
|
|
118
|
+
* request-level chain cannot thread a nonce into the route component,
|
|
119
|
+
* so nonce plumbing is intentionally out of scope for this bridge and
|
|
120
|
+
* users who need it should keep `.use(secure(...))` inline).
|
|
121
|
+
*/
|
|
122
|
+
export function secureMiddleware(options: SecureMiddlewareOptions = {}): Middleware {
|
|
123
|
+
const plugin = secureCtx(options);
|
|
124
|
+
return defineMiddleware({
|
|
125
|
+
name: "secure",
|
|
126
|
+
async handler(req, next) {
|
|
127
|
+
const response = await next();
|
|
128
|
+
const ctx = new ManduContext(req);
|
|
129
|
+
const afterHandle = plugin.afterHandle;
|
|
130
|
+
if (typeof afterHandle !== "function") return response;
|
|
131
|
+
const mutated = await afterHandle(ctx, response);
|
|
132
|
+
return mutated ?? response;
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Request-level rate-limit middleware. Shares the underlying
|
|
139
|
+
* `rateLimit()` store semantics (sliding window, pluggable store). When
|
|
140
|
+
* the limiter rejects, returns the configured 429 Response directly
|
|
141
|
+
* (short-circuits the chain).
|
|
142
|
+
*/
|
|
143
|
+
export function rateLimitMiddleware(
|
|
144
|
+
options: RateLimitMiddlewareOptions
|
|
145
|
+
): Middleware {
|
|
146
|
+
return bridgeCtxMiddleware("rate-limit", () => rateLimitCtx(options));
|
|
147
|
+
}
|
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware composition — Phase 18.ε
|
|
3
|
-
*
|
|
4
|
-
* Builds a request pipeline from an array of `Middleware` layers and a
|
|
5
|
-
* final handler. The resulting function has Next.js / SvelteKit semantics:
|
|
6
|
-
*
|
|
7
|
-
* - **Declaration order = outer-to-inner.** `compose(a, b, c)` runs
|
|
8
|
-
* `a.handler(req, nextA)` first; `nextA()` runs `b.handler`, whose
|
|
9
|
-
* `nextB()` runs `c.handler`, whose `nextC()` invokes `finalHandler`.
|
|
10
|
-
*
|
|
11
|
-
* - **Short-circuit.** Any middleware may return a Response without
|
|
12
|
-
* calling `next()`. Downstream middleware and the final handler are
|
|
13
|
-
* skipped; the outer chain receives the short-circuit Response.
|
|
14
|
-
*
|
|
15
|
-
* - **Rewrite.** `next(modifiedReq)` propagates `modifiedReq` to the
|
|
16
|
-
* remainder of the chain. The current middleware still sees the
|
|
17
|
-
* original `req` argument (no mutation).
|
|
18
|
-
*
|
|
19
|
-
* - **Match filter.** Middleware with a `match(req) === false` are
|
|
20
|
-
* skipped at their position in the chain — `next()` transparently
|
|
21
|
-
* advances to the next layer.
|
|
22
|
-
*
|
|
23
|
-
* - **Error propagation.** Throws inside middleware are re-thrown to
|
|
24
|
-
* the caller. Mandu's outer `handleRequest` wraps this in the
|
|
25
|
-
* framework's error boundary (error → 500 via `errorToResponse`),
|
|
26
|
-
* so middleware authors never need their own top-level try/catch
|
|
27
|
-
* unless they want to convert specific errors to specific responses.
|
|
28
|
-
*
|
|
29
|
-
* - **Double-next guard.** Calling `next()` twice inside a single
|
|
30
|
-
* middleware is a programming error (it would re-execute downstream
|
|
31
|
-
* layers with duplicate side effects). The second call throws a
|
|
32
|
-
* `MiddlewareError` with the offending middleware's name so the bug
|
|
33
|
-
* surfaces immediately in dev.
|
|
34
|
-
*
|
|
35
|
-
* @see {@link Middleware} for the interface.
|
|
36
|
-
* @see `docs/architect/middleware-composition.md` for patterns.
|
|
37
|
-
*/
|
|
38
|
-
import type { Middleware } from "./define";
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* The finalized request handler that sits at the bottom of the middleware
|
|
42
|
-
* chain. Typically this is `handleRequest(req, router, registry)` adapted
|
|
43
|
-
* to the `(req) => Promise<Response>` shape.
|
|
44
|
-
*/
|
|
45
|
-
export type FinalHandler = (req: Request) => Promise<Response>;
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* The function produced by {@link compose}. Applies the middleware chain
|
|
49
|
-
* on top of `finalHandler` for the given request.
|
|
50
|
-
*/
|
|
51
|
-
export type ComposedHandler = (
|
|
52
|
-
req: Request,
|
|
53
|
-
finalHandler: FinalHandler
|
|
54
|
-
) => Promise<Response>;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Thrown when a middleware calls `next()` more than once. Identifies the
|
|
58
|
-
* middleware by name so the diagnostic is actionable.
|
|
59
|
-
*/
|
|
60
|
-
export class MiddlewareError extends Error {
|
|
61
|
-
override readonly name = "MiddlewareError";
|
|
62
|
-
constructor(
|
|
63
|
-
public readonly middlewareName: string,
|
|
64
|
-
message: string
|
|
65
|
-
) {
|
|
66
|
-
super(`[${middlewareName}] ${message}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Compose a middleware chain. Zero middleware produces a passthrough:
|
|
72
|
-
* `compose()(req, final) === final(req)`.
|
|
73
|
-
*/
|
|
74
|
-
export function compose(...middlewares: Middleware[]): ComposedHandler {
|
|
75
|
-
// Defensive copy — callers mutating the source array after compose() must
|
|
76
|
-
// not affect the frozen pipeline. Also narrows index type for the inner
|
|
77
|
-
// recursion and makes an empty-array fast path trivial.
|
|
78
|
-
const chain = middlewares.slice();
|
|
79
|
-
|
|
80
|
-
if (chain.length === 0) {
|
|
81
|
-
return (req, finalHandler) => finalHandler(req);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return async function composed(
|
|
85
|
-
req: Request,
|
|
86
|
-
finalHandler: FinalHandler
|
|
87
|
-
): Promise<Response> {
|
|
88
|
-
// Recursive dispatcher. `index` is the next middleware to try; `current`
|
|
89
|
-
// is the Request object that layer will receive. Each invocation either:
|
|
90
|
-
// (a) index === chain.length → delegate to finalHandler(current)
|
|
91
|
-
// (b) chain[index].match(current) === false → skip, recurse to next
|
|
92
|
-
// (c) run chain[index].handler(current, next) where next() = dispatch(i+1, …)
|
|
93
|
-
async function dispatch(index: number, current: Request): Promise<Response> {
|
|
94
|
-
if (index >= chain.length) {
|
|
95
|
-
return finalHandler(current);
|
|
96
|
-
}
|
|
97
|
-
const mw = chain[index]!;
|
|
98
|
-
|
|
99
|
-
// Evaluate match filter. Throws in `match` are framework-bug territory —
|
|
100
|
-
// we surface them to the outer error boundary rather than papering over.
|
|
101
|
-
if (mw.match) {
|
|
102
|
-
let matched: boolean;
|
|
103
|
-
try {
|
|
104
|
-
matched = mw.match(current);
|
|
105
|
-
} catch (err) {
|
|
106
|
-
throw new MiddlewareError(
|
|
107
|
-
mw.name,
|
|
108
|
-
`\`match(req)\` threw: ${err instanceof Error ? err.message : String(err)}`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
if (!matched) {
|
|
112
|
-
return dispatch(index + 1, current);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Single-use `next` guard — second invocation throws.
|
|
117
|
-
let nextCalled = false;
|
|
118
|
-
const next = (override?: Request): Promise<Response> => {
|
|
119
|
-
if (nextCalled) {
|
|
120
|
-
throw new MiddlewareError(
|
|
121
|
-
mw.name,
|
|
122
|
-
"next() was called more than once. Each middleware must call next() at most once."
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
nextCalled = true;
|
|
126
|
-
return dispatch(index + 1, override ?? current);
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
return mw.handler(current, next);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return dispatch(0, req);
|
|
133
|
-
};
|
|
134
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Middleware composition — Phase 18.ε
|
|
3
|
+
*
|
|
4
|
+
* Builds a request pipeline from an array of `Middleware` layers and a
|
|
5
|
+
* final handler. The resulting function has Next.js / SvelteKit semantics:
|
|
6
|
+
*
|
|
7
|
+
* - **Declaration order = outer-to-inner.** `compose(a, b, c)` runs
|
|
8
|
+
* `a.handler(req, nextA)` first; `nextA()` runs `b.handler`, whose
|
|
9
|
+
* `nextB()` runs `c.handler`, whose `nextC()` invokes `finalHandler`.
|
|
10
|
+
*
|
|
11
|
+
* - **Short-circuit.** Any middleware may return a Response without
|
|
12
|
+
* calling `next()`. Downstream middleware and the final handler are
|
|
13
|
+
* skipped; the outer chain receives the short-circuit Response.
|
|
14
|
+
*
|
|
15
|
+
* - **Rewrite.** `next(modifiedReq)` propagates `modifiedReq` to the
|
|
16
|
+
* remainder of the chain. The current middleware still sees the
|
|
17
|
+
* original `req` argument (no mutation).
|
|
18
|
+
*
|
|
19
|
+
* - **Match filter.** Middleware with a `match(req) === false` are
|
|
20
|
+
* skipped at their position in the chain — `next()` transparently
|
|
21
|
+
* advances to the next layer.
|
|
22
|
+
*
|
|
23
|
+
* - **Error propagation.** Throws inside middleware are re-thrown to
|
|
24
|
+
* the caller. Mandu's outer `handleRequest` wraps this in the
|
|
25
|
+
* framework's error boundary (error → 500 via `errorToResponse`),
|
|
26
|
+
* so middleware authors never need their own top-level try/catch
|
|
27
|
+
* unless they want to convert specific errors to specific responses.
|
|
28
|
+
*
|
|
29
|
+
* - **Double-next guard.** Calling `next()` twice inside a single
|
|
30
|
+
* middleware is a programming error (it would re-execute downstream
|
|
31
|
+
* layers with duplicate side effects). The second call throws a
|
|
32
|
+
* `MiddlewareError` with the offending middleware's name so the bug
|
|
33
|
+
* surfaces immediately in dev.
|
|
34
|
+
*
|
|
35
|
+
* @see {@link Middleware} for the interface.
|
|
36
|
+
* @see `docs/architect/middleware-composition.md` for patterns.
|
|
37
|
+
*/
|
|
38
|
+
import type { Middleware } from "./define";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The finalized request handler that sits at the bottom of the middleware
|
|
42
|
+
* chain. Typically this is `handleRequest(req, router, registry)` adapted
|
|
43
|
+
* to the `(req) => Promise<Response>` shape.
|
|
44
|
+
*/
|
|
45
|
+
export type FinalHandler = (req: Request) => Promise<Response>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The function produced by {@link compose}. Applies the middleware chain
|
|
49
|
+
* on top of `finalHandler` for the given request.
|
|
50
|
+
*/
|
|
51
|
+
export type ComposedHandler = (
|
|
52
|
+
req: Request,
|
|
53
|
+
finalHandler: FinalHandler
|
|
54
|
+
) => Promise<Response>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Thrown when a middleware calls `next()` more than once. Identifies the
|
|
58
|
+
* middleware by name so the diagnostic is actionable.
|
|
59
|
+
*/
|
|
60
|
+
export class MiddlewareError extends Error {
|
|
61
|
+
override readonly name = "MiddlewareError";
|
|
62
|
+
constructor(
|
|
63
|
+
public readonly middlewareName: string,
|
|
64
|
+
message: string
|
|
65
|
+
) {
|
|
66
|
+
super(`[${middlewareName}] ${message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compose a middleware chain. Zero middleware produces a passthrough:
|
|
72
|
+
* `compose()(req, final) === final(req)`.
|
|
73
|
+
*/
|
|
74
|
+
export function compose(...middlewares: Middleware[]): ComposedHandler {
|
|
75
|
+
// Defensive copy — callers mutating the source array after compose() must
|
|
76
|
+
// not affect the frozen pipeline. Also narrows index type for the inner
|
|
77
|
+
// recursion and makes an empty-array fast path trivial.
|
|
78
|
+
const chain = middlewares.slice();
|
|
79
|
+
|
|
80
|
+
if (chain.length === 0) {
|
|
81
|
+
return (req, finalHandler) => finalHandler(req);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return async function composed(
|
|
85
|
+
req: Request,
|
|
86
|
+
finalHandler: FinalHandler
|
|
87
|
+
): Promise<Response> {
|
|
88
|
+
// Recursive dispatcher. `index` is the next middleware to try; `current`
|
|
89
|
+
// is the Request object that layer will receive. Each invocation either:
|
|
90
|
+
// (a) index === chain.length → delegate to finalHandler(current)
|
|
91
|
+
// (b) chain[index].match(current) === false → skip, recurse to next
|
|
92
|
+
// (c) run chain[index].handler(current, next) where next() = dispatch(i+1, …)
|
|
93
|
+
async function dispatch(index: number, current: Request): Promise<Response> {
|
|
94
|
+
if (index >= chain.length) {
|
|
95
|
+
return finalHandler(current);
|
|
96
|
+
}
|
|
97
|
+
const mw = chain[index]!;
|
|
98
|
+
|
|
99
|
+
// Evaluate match filter. Throws in `match` are framework-bug territory —
|
|
100
|
+
// we surface them to the outer error boundary rather than papering over.
|
|
101
|
+
if (mw.match) {
|
|
102
|
+
let matched: boolean;
|
|
103
|
+
try {
|
|
104
|
+
matched = mw.match(current);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw new MiddlewareError(
|
|
107
|
+
mw.name,
|
|
108
|
+
`\`match(req)\` threw: ${err instanceof Error ? err.message : String(err)}`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (!matched) {
|
|
112
|
+
return dispatch(index + 1, current);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Single-use `next` guard — second invocation throws.
|
|
117
|
+
let nextCalled = false;
|
|
118
|
+
const next = (override?: Request): Promise<Response> => {
|
|
119
|
+
if (nextCalled) {
|
|
120
|
+
throw new MiddlewareError(
|
|
121
|
+
mw.name,
|
|
122
|
+
"next() was called more than once. Each middleware must call next() at most once."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
nextCalled = true;
|
|
126
|
+
return dispatch(index + 1, override ?? current);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return mw.handler(current, next);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return dispatch(0, req);
|
|
133
|
+
};
|
|
134
|
+
}
|