@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.ac99d918
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 +211 -43
- package/dist/bin/rango.js +279 -102
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3313 -1160
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +62 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +63 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +124 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +223 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +66 -5
- package/skills/router-setup/SKILL.md +38 -3
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +81 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +66 -14
- package/src/browser/navigation-client.ts +172 -109
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +79 -93
- package/src/browser/prefetch/cache.ts +180 -62
- package/src/browser/prefetch/fetch.ts +252 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +72 -10
- package/src/browser/react/NavigationProvider.tsx +83 -31
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +111 -24
- package/src/browser/scroll-restoration.ts +29 -19
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -62
- package/src/browser/types.ts +60 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +6 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +333 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2256 -241
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/browser.ts +0 -1
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +69 -10
- package/src/index.ts +112 -14
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +413 -275
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +107 -32
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +50 -8
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +46 -30
- package/src/router/intercept-resolution.ts +23 -23
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +207 -30
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +161 -262
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +169 -140
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +119 -65
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +329 -305
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +128 -42
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -409
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +18 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +28 -4
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +53 -56
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -69
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +22 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +208 -201
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +211 -52
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +289 -124
- package/src/ssr/index.tsx +22 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +124 -70
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +36 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +236 -6
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +65 -207
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +46 -17
- package/src/vite/rango.ts +207 -125
- package/src/vite/router-discovery.ts +931 -133
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +97 -44
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions and interfaces for the middleware system.
|
|
5
|
-
* Separated from execution logic for cleaner imports.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import type { ContextVar } from "../context-var.js";
|
|
9
2
|
import type {
|
|
10
3
|
DefaultReverseRouteMap,
|
|
@@ -14,18 +7,13 @@ import type {
|
|
|
14
7
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
15
8
|
import type { Theme } from "../theme/types.js";
|
|
16
9
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
10
|
+
import type { RequestScope } from "../types/request-scope.js";
|
|
17
11
|
|
|
18
|
-
/**
|
|
19
|
-
* Get variable function type
|
|
20
|
-
*/
|
|
21
12
|
type GetVariableFn = {
|
|
22
13
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
23
14
|
<K extends keyof DefaultVars>(key: K): DefaultVars[K];
|
|
24
15
|
};
|
|
25
16
|
|
|
26
|
-
/**
|
|
27
|
-
* Set variable function type
|
|
28
|
-
*/
|
|
29
17
|
type SetVariableFn = {
|
|
30
18
|
<T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
|
|
31
19
|
<K extends keyof DefaultVars>(
|
|
@@ -35,9 +23,6 @@ type SetVariableFn = {
|
|
|
35
23
|
): void;
|
|
36
24
|
};
|
|
37
25
|
|
|
38
|
-
/**
|
|
39
|
-
* Cookie options for setting cookies
|
|
40
|
-
*/
|
|
41
26
|
export interface CookieOptions {
|
|
42
27
|
domain?: string;
|
|
43
28
|
path?: string;
|
|
@@ -48,178 +33,60 @@ export interface CookieOptions {
|
|
|
48
33
|
sameSite?: "strict" | "lax" | "none";
|
|
49
34
|
}
|
|
50
35
|
|
|
51
|
-
/**
|
|
52
|
-
* Context passed to middleware
|
|
53
|
-
*
|
|
54
|
-
* @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
|
|
55
|
-
* @template TParams - URL params type (typed for route middleware, Record<string, string> for global middleware)
|
|
56
|
-
*/
|
|
57
36
|
export interface MiddlewareContext<
|
|
58
37
|
TEnv = any,
|
|
59
|
-
TParams = Record<string, string>,
|
|
60
|
-
> {
|
|
61
|
-
/** Original request */
|
|
62
|
-
request: Request;
|
|
63
|
-
|
|
64
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
65
|
-
url: URL;
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The original request URL with all parameters intact, including
|
|
69
|
-
* internal `_rsc*` transport params.
|
|
70
|
-
*/
|
|
71
|
-
originalUrl: URL;
|
|
72
|
-
|
|
73
|
-
/** URL pathname */
|
|
74
|
-
pathname: string;
|
|
75
|
-
|
|
76
|
-
/** URL search params */
|
|
77
|
-
searchParams: URLSearchParams;
|
|
78
|
-
|
|
79
|
-
/** Platform bindings (Cloudflare, etc.) */
|
|
80
|
-
env: TEnv;
|
|
81
|
-
|
|
82
|
-
/** URL params extracted from route/middleware pattern */
|
|
38
|
+
TParams = Record<string, string | undefined>,
|
|
39
|
+
> extends RequestScope<TEnv> {
|
|
83
40
|
params: TParams;
|
|
84
41
|
|
|
85
|
-
/**
|
|
86
|
-
* Response headers.
|
|
87
|
-
* Before `next()`, returns headers from the shared response stub.
|
|
88
|
-
* After `next()`, returns headers from the downstream response.
|
|
89
|
-
*/
|
|
90
42
|
readonly headers: Headers;
|
|
91
43
|
|
|
92
|
-
/** Get a context variable (shared with route handlers) */
|
|
93
44
|
get: GetVariableFn;
|
|
94
45
|
|
|
95
|
-
/** Set a context variable (shared with route handlers) */
|
|
96
46
|
set: SetVariableFn;
|
|
97
47
|
|
|
98
|
-
/**
|
|
99
|
-
* Middleware-injected variables.
|
|
100
|
-
* Same shared dictionary as `ctx.get()`/`ctx.set()`.
|
|
101
|
-
*/
|
|
102
|
-
var: DefaultVars;
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Set a response header - can be called before or after `next()`.
|
|
106
|
-
*
|
|
107
|
-
* When called before `next()`, headers are queued and merged into the final response.
|
|
108
|
-
* When called after `next()`, headers are set directly on the response.
|
|
109
|
-
*/
|
|
110
48
|
header(name: string, value: string): void;
|
|
111
49
|
|
|
112
|
-
/**
|
|
113
|
-
* The matched route name, if available and the route has an explicit name.
|
|
114
|
-
* Undefined for global middleware (runs before route matching) or unnamed routes.
|
|
115
|
-
*/
|
|
116
50
|
routeName?: DefaultRouteName;
|
|
117
51
|
|
|
118
|
-
/**
|
|
119
|
-
* Enable performance metrics for this request.
|
|
120
|
-
* When called, granular timing breakdown is logged to console and
|
|
121
|
-
* included in the Server-Timing response header, regardless of the
|
|
122
|
-
* router-level `debugPerformance` option.
|
|
123
|
-
*
|
|
124
|
-
* Call **before** `await next()` so the metrics store exists when
|
|
125
|
-
* downstream phases (route matching, rendering, SSR) record their
|
|
126
|
-
* spans. Calling after `next()` returns still emits `handler:total`
|
|
127
|
-
* but misses all upstream metrics.
|
|
128
|
-
*/
|
|
129
52
|
debugPerformance(): void;
|
|
130
53
|
|
|
131
|
-
/**
|
|
132
|
-
* Current theme (from cookie or default).
|
|
133
|
-
* Only available when theme is enabled in router config.
|
|
134
|
-
*/
|
|
135
54
|
theme?: Theme;
|
|
136
55
|
|
|
137
|
-
/**
|
|
138
|
-
* Set the theme (only available when theme is enabled in router config).
|
|
139
|
-
* Sets a cookie with the new theme value.
|
|
140
|
-
*/
|
|
141
56
|
setTheme?: (theme: Theme) => void;
|
|
142
57
|
|
|
143
|
-
/**
|
|
144
|
-
* Attach location state entries to this response.
|
|
145
|
-
* State is delivered to the client via history.pushState and accessible
|
|
146
|
-
* through the useLocationState() hook.
|
|
147
|
-
*/
|
|
148
58
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
149
59
|
|
|
150
|
-
/**
|
|
151
|
-
* Generate URLs from route names.
|
|
152
|
-
* - `name` — global route, from the named-routes definition
|
|
153
|
-
*/
|
|
154
60
|
reverse: ScopedReverseFunction<
|
|
155
61
|
Record<string, string>,
|
|
156
62
|
DefaultReverseRouteMap
|
|
157
63
|
>;
|
|
158
64
|
}
|
|
159
65
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
* @template TParams - URL params type (typed for route middleware)
|
|
165
|
-
*
|
|
166
|
-
* When using middleware with global augmentation (RSCRouter.Env), explicitly
|
|
167
|
-
* annotate your middleware functions, or the types will be inferred from context:
|
|
168
|
-
*
|
|
169
|
-
* @example
|
|
170
|
-
* ```typescript
|
|
171
|
-
* // With explicit annotation (recommended for reusable middleware)
|
|
172
|
-
* const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
|
|
173
|
-
*
|
|
174
|
-
* // Types inferred from router.use() call
|
|
175
|
-
* router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
|
|
176
|
-
* ```
|
|
177
|
-
*/
|
|
178
|
-
export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
|
|
66
|
+
export type MiddlewareFn<
|
|
67
|
+
TEnv = any,
|
|
68
|
+
TParams = Record<string, string | undefined>,
|
|
69
|
+
> = (
|
|
179
70
|
ctx: MiddlewareContext<TEnv, TParams>,
|
|
180
71
|
next: () => Promise<Response>,
|
|
181
72
|
) => Response | void | Promise<Response | void>;
|
|
182
73
|
|
|
183
|
-
/**
|
|
184
|
-
* Stored middleware entry with pattern matching info
|
|
185
|
-
* @internal - uses any for internal flexibility
|
|
186
|
-
*/
|
|
187
74
|
export interface MiddlewareEntry<TEnv = any> {
|
|
188
|
-
/** Original pattern string */
|
|
189
75
|
pattern: string | null;
|
|
190
|
-
|
|
191
|
-
/** Compiled regex for matching */
|
|
192
76
|
regex: RegExp | null;
|
|
193
|
-
|
|
194
|
-
/** Param names extracted from pattern */
|
|
195
77
|
paramNames: string[];
|
|
196
|
-
|
|
197
|
-
/** The middleware function */
|
|
198
78
|
handler: MiddlewareFn<TEnv>;
|
|
199
|
-
|
|
200
|
-
/** Mount prefix this middleware is scoped to (null = global) */
|
|
201
|
-
mountPrefix: string | null;
|
|
202
79
|
}
|
|
203
80
|
|
|
204
|
-
/**
|
|
205
|
-
* Mutable response holder - tracks the current response through the middleware chain.
|
|
206
|
-
*/
|
|
207
81
|
export interface ResponseHolder {
|
|
208
82
|
response: Response | null;
|
|
209
83
|
}
|
|
210
84
|
|
|
211
|
-
/**
|
|
212
|
-
* Entry type for middleware collection
|
|
213
|
-
* Matches the shape of EntryData used in router.ts
|
|
214
|
-
*/
|
|
215
85
|
export interface MiddlewareCollectableEntry {
|
|
216
86
|
middleware?: MiddlewareFn<any, any>[];
|
|
217
87
|
layout?: MiddlewareCollectableEntry[];
|
|
218
88
|
}
|
|
219
89
|
|
|
220
|
-
/**
|
|
221
|
-
* Collected route middleware with params
|
|
222
|
-
*/
|
|
223
90
|
export interface CollectedMiddleware {
|
|
224
91
|
handler: MiddlewareFn<any, any>;
|
|
225
92
|
params: Record<string, string>;
|
package/src/router/middleware.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
/// <reference types="vite/types/importMeta.d.ts" />
|
|
2
|
-
/**
|
|
3
|
-
* Middleware Execution
|
|
4
|
-
*
|
|
5
|
-
* True middleware that wraps the entire RSC handler.
|
|
6
|
-
* - `await next()` returns actual Response
|
|
7
|
-
* - Can modify response headers
|
|
8
|
-
* - Can catch errors from RSC rendering
|
|
9
|
-
* - Forgiving API: if middleware doesn't return, original response is used
|
|
10
|
-
*/
|
|
11
2
|
|
|
12
3
|
import { contextGet, contextSet } from "../context-var.js";
|
|
4
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
5
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
13
6
|
import type {
|
|
14
7
|
CollectedMiddleware,
|
|
15
8
|
MiddlewareCollectableEntry,
|
|
@@ -19,27 +12,28 @@ import type {
|
|
|
19
12
|
ResponseHolder,
|
|
20
13
|
} from "./middleware-types.js";
|
|
21
14
|
import { _getRequestContext } from "../server/request-context.js";
|
|
15
|
+
import {
|
|
16
|
+
EXTERNAL_REDIRECT_MARKER,
|
|
17
|
+
isExternalRedirect,
|
|
18
|
+
markExternalRedirect,
|
|
19
|
+
} from "../redirect-origin.js";
|
|
22
20
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
21
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
24
22
|
import { stripInternalParams } from "./handler-context.js";
|
|
23
|
+
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
25
24
|
|
|
26
|
-
// Re-export types
|
|
25
|
+
// Re-export types consumed through this module's path.
|
|
27
26
|
export type {
|
|
28
27
|
CookieOptions,
|
|
29
|
-
CollectedMiddleware,
|
|
30
|
-
MiddlewareCollectableEntry,
|
|
31
28
|
MiddlewareContext,
|
|
32
29
|
MiddlewareEntry,
|
|
33
30
|
MiddlewareFn,
|
|
34
|
-
ResponseHolder,
|
|
35
31
|
} from "./middleware-types.js";
|
|
36
|
-
export { parseCookies, serializeCookie } from "./middleware-cookies.js";
|
|
37
32
|
|
|
38
33
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
39
|
-
/** Ignore post-next() durations below this threshold (measurement noise). */
|
|
40
34
|
const POST_METRIC_MIN_DURATION_MS = 0.01;
|
|
41
35
|
|
|
42
|
-
function
|
|
36
|
+
function getMiddlewareMetricLabel<TEnv>(
|
|
43
37
|
entry: MiddlewareEntry<TEnv>,
|
|
44
38
|
ordinal: number,
|
|
45
39
|
): string {
|
|
@@ -47,23 +41,12 @@ function getMiddlewareMetricBase<TEnv>(
|
|
|
47
41
|
const scope = entry.pattern ?? "*";
|
|
48
42
|
|
|
49
43
|
if (handlerName) {
|
|
50
|
-
return
|
|
44
|
+
return `middleware:${handlerName}@${scope}`;
|
|
51
45
|
}
|
|
52
46
|
|
|
53
|
-
return
|
|
47
|
+
return `middleware:${scope}#${ordinal + 1}`;
|
|
54
48
|
}
|
|
55
49
|
|
|
56
|
-
function getMiddlewareMetricLabel<TEnv>(
|
|
57
|
-
entry: MiddlewareEntry<TEnv>,
|
|
58
|
-
ordinal: number,
|
|
59
|
-
): string {
|
|
60
|
-
return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Parse a route pattern into regex and param names
|
|
65
|
-
* Supports: *, /path, /path/*, /path/:param, /path/:param/*
|
|
66
|
-
*/
|
|
67
50
|
export function parsePattern(pattern: string): {
|
|
68
51
|
regex: RegExp;
|
|
69
52
|
paramNames: string[];
|
|
@@ -112,7 +95,12 @@ function escapeRegex(str: string): string {
|
|
|
112
95
|
}
|
|
113
96
|
|
|
114
97
|
/**
|
|
115
|
-
* Extract params from a pathname using a pattern's regex and param names
|
|
98
|
+
* Extract params from a pathname using a pattern's regex and param names.
|
|
99
|
+
*
|
|
100
|
+
* Values are URL-decoded so apps see the raw string (e.g. "ivo@example.com")
|
|
101
|
+
* instead of the percent-encoded form ("ivo%40example.com"). This matches the
|
|
102
|
+
* contract assumed by ctx.reverse (which re-encodes) and aligns with
|
|
103
|
+
* Express/React Router/Fastify/Koa.
|
|
116
104
|
*/
|
|
117
105
|
export function extractParams(
|
|
118
106
|
pathname: string,
|
|
@@ -124,7 +112,7 @@ export function extractParams(
|
|
|
124
112
|
|
|
125
113
|
const params: Record<string, string> = {};
|
|
126
114
|
for (let i = 0; i < paramNames.length; i++) {
|
|
127
|
-
params[paramNames[i]] = match[i + 1] || "";
|
|
115
|
+
params[paramNames[i]] = safeDecodeURIComponent(match[i + 1] || "");
|
|
128
116
|
}
|
|
129
117
|
return params;
|
|
130
118
|
}
|
|
@@ -179,14 +167,22 @@ export function createMiddlewareContext<TEnv>(
|
|
|
179
167
|
return responseHolder.response;
|
|
180
168
|
};
|
|
181
169
|
|
|
170
|
+
// Capture reqCtx once: the request-scoped platform fields
|
|
171
|
+
// (originalUrl, executionContext, waitUntil) are immutable per request,
|
|
172
|
+
// so snapshotting beats re-reading ALS on every access. The lazy getters
|
|
173
|
+
// below (routeName, theme, setTheme) stay lazy because those can change
|
|
174
|
+
// during `await next()`.
|
|
175
|
+
const reqCtx = _getRequestContext();
|
|
182
176
|
return {
|
|
183
177
|
request,
|
|
184
178
|
url,
|
|
185
|
-
originalUrl: new URL(request.url),
|
|
179
|
+
originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
|
|
186
180
|
pathname: url.pathname,
|
|
187
181
|
searchParams: url.searchParams,
|
|
188
182
|
env: env as MiddlewareContext<TEnv>["env"],
|
|
189
183
|
params,
|
|
184
|
+
executionContext: reqCtx?.executionContext,
|
|
185
|
+
waitUntil: reqCtx ? reqCtx.waitUntil.bind(reqCtx) : fireAndForgetWaitUntil,
|
|
190
186
|
// Getter: re-derives from request context on each access so that global
|
|
191
187
|
// middleware sees the matched route name after await next().
|
|
192
188
|
get routeName(): MiddlewareContext<TEnv>["routeName"] {
|
|
@@ -207,9 +203,6 @@ export function createMiddlewareContext<TEnv>(
|
|
|
207
203
|
set: ((keyOrVar: any, value: unknown, options?: any) => {
|
|
208
204
|
contextSet(variables, keyOrVar, value, options);
|
|
209
205
|
}) as MiddlewareContext<TEnv>["set"],
|
|
210
|
-
|
|
211
|
-
var: variables as MiddlewareContext<TEnv>["var"],
|
|
212
|
-
|
|
213
206
|
header(name: string, value: string): void {
|
|
214
207
|
// Before next(): delegate to shared RequestContext stub
|
|
215
208
|
if (isPreNext()) {
|
|
@@ -248,9 +241,13 @@ export function createMiddlewareContext<TEnv>(
|
|
|
248
241
|
|
|
249
242
|
reverse:
|
|
250
243
|
reverse ??
|
|
251
|
-
((
|
|
244
|
+
((
|
|
245
|
+
name: string,
|
|
246
|
+
_params?: Record<string, string>,
|
|
247
|
+
_search?: Record<string, unknown>,
|
|
248
|
+
) => {
|
|
252
249
|
throw new Error(
|
|
253
|
-
`ctx.reverse() is not available
|
|
250
|
+
`ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
|
|
254
251
|
);
|
|
255
252
|
}),
|
|
256
253
|
|
|
@@ -294,6 +291,88 @@ export function matchMiddleware<TEnv>(
|
|
|
294
291
|
return matches;
|
|
295
292
|
}
|
|
296
293
|
|
|
294
|
+
// Set-Cookie is appended; for other headers stubOverridesNonCookie=true
|
|
295
|
+
// overwrites (chain ran to completion), false fills only missing slots (an
|
|
296
|
+
// explicit short-circuit Response's own headers win).
|
|
297
|
+
function mergeStubHeaders(
|
|
298
|
+
target: Headers,
|
|
299
|
+
stub: Headers,
|
|
300
|
+
stubOverridesNonCookie: boolean,
|
|
301
|
+
): void {
|
|
302
|
+
stub.forEach((value, name) => {
|
|
303
|
+
// The reserved external-redirect marker is internal and never a trust
|
|
304
|
+
// signal; never copy a stub value (e.g. a stray ctx.header() call) onto a
|
|
305
|
+
// browser-facing response. The opt-in is the out-of-band brand.
|
|
306
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
307
|
+
if (name.toLowerCase() === "set-cookie") {
|
|
308
|
+
target.append(name, value);
|
|
309
|
+
} else if (stubOverridesNonCookie || !target.has(name)) {
|
|
310
|
+
target.set(name, value);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Set-Cookie is deduped so a nested inner executeMiddleware that already merged
|
|
316
|
+
// the same reqCtx cookies does not duplicate them; other headers fill if missing.
|
|
317
|
+
function mergeReqCtxStub(
|
|
318
|
+
target: Headers,
|
|
319
|
+
reqCtx: ReturnType<typeof _getRequestContext>,
|
|
320
|
+
): void {
|
|
321
|
+
if (!reqCtx) return;
|
|
322
|
+
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
323
|
+
if (stubCookies.length > 0) {
|
|
324
|
+
const existing = new Set(target.getSetCookie());
|
|
325
|
+
for (const cookie of stubCookies) {
|
|
326
|
+
if (!existing.has(cookie)) {
|
|
327
|
+
target.append("set-cookie", cookie);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
reqCtx.res.headers.forEach((value, name) => {
|
|
332
|
+
// Never propagate the reserved external-redirect marker (see mergeStubHeaders).
|
|
333
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
334
|
+
if (name !== "set-cookie" && !target.has(name)) {
|
|
335
|
+
target.set(name, value);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Clone `base` with stub headers merged into a fresh Headers (the clone keeps
|
|
341
|
+
// the body mutable for post-next() modifications). Set-Cookie is always
|
|
342
|
+
// appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
|
|
343
|
+
// mergeReqCtx folds in RequestContext stub cookies/headers; the intercept
|
|
344
|
+
// short-circuit path passes false (its reqCtx headers are not merged here),
|
|
345
|
+
// which is the one deliberate divergence between the call sites.
|
|
346
|
+
function mergeResponse(
|
|
347
|
+
base: Response,
|
|
348
|
+
stub: Headers,
|
|
349
|
+
opts: { stubOverridesNonCookie: boolean; mergeReqCtx: boolean },
|
|
350
|
+
): Response {
|
|
351
|
+
const mergedHeaders = new Headers(base.headers);
|
|
352
|
+
// The reserved external-redirect marker is never a trust signal and must never
|
|
353
|
+
// reach the browser. The guard strips it on 3xx redirects; strip it here too so
|
|
354
|
+
// a forged value cannot ride a non-3xx middleware response (which the 3xx-only
|
|
355
|
+
// guard would not touch) to the client. The opt-in is the out-of-band brand.
|
|
356
|
+
mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
|
|
357
|
+
mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
|
|
358
|
+
if (opts.mergeReqCtx) {
|
|
359
|
+
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
360
|
+
}
|
|
361
|
+
const merged = new Response(base.body, {
|
|
362
|
+
status: base.status,
|
|
363
|
+
statusText: base.statusText,
|
|
364
|
+
headers: mergedHeaders,
|
|
365
|
+
});
|
|
366
|
+
// Transfer the out-of-band external-redirect brand across this rebuild: a
|
|
367
|
+
// middleware short-circuit `return redirect(url, { external: true })` reaches
|
|
368
|
+
// the open-redirect guard only after this merge, and the brand lives on the
|
|
369
|
+
// Response object, not in its headers.
|
|
370
|
+
if (isExternalRedirect(base)) {
|
|
371
|
+
markExternalRedirect(merged);
|
|
372
|
+
}
|
|
373
|
+
return merged;
|
|
374
|
+
}
|
|
375
|
+
|
|
297
376
|
/**
|
|
298
377
|
* Execute middleware chain
|
|
299
378
|
*
|
|
@@ -302,7 +381,7 @@ export function matchMiddleware<TEnv>(
|
|
|
302
381
|
* - `ctx.headers` available before and after `await next()`
|
|
303
382
|
* - `ctx.header()` shorthand for setting a single header
|
|
304
383
|
* - Forgiving: if middleware doesn't return, uses the downstream response
|
|
305
|
-
* - Short-circuit: return Response to stop chain
|
|
384
|
+
* - Short-circuit: return OR throw a Response to stop chain
|
|
306
385
|
* - Error catching: try/catch around `next()` works
|
|
307
386
|
*/
|
|
308
387
|
export async function executeMiddleware<TEnv>(
|
|
@@ -332,42 +411,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
332
411
|
// End of chain - call actual RSC handler
|
|
333
412
|
const response = await finalHandler();
|
|
334
413
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
stubResponse.headers.forEach((value, name) => {
|
|
339
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
340
|
-
mergedHeaders.append(name, value);
|
|
341
|
-
} else {
|
|
342
|
-
mergedHeaders.set(name, value);
|
|
343
|
-
}
|
|
344
|
-
});
|
|
345
|
-
// Also merge shared RequestContext stub (cookies written via cookies().set()).
|
|
346
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
347
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
348
|
-
const reqCtx = _getRequestContext();
|
|
349
|
-
if (reqCtx) {
|
|
350
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
351
|
-
if (stubCookies.length > 0) {
|
|
352
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
353
|
-
for (const cookie of stubCookies) {
|
|
354
|
-
if (!existing.has(cookie)) {
|
|
355
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
360
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
361
|
-
mergedHeaders.set(name, value);
|
|
362
|
-
}
|
|
363
|
-
});
|
|
414
|
+
if (isWebSocketUpgradeResponse(response)) {
|
|
415
|
+
responseHolder.response = response;
|
|
416
|
+
return response;
|
|
364
417
|
}
|
|
365
418
|
|
|
366
|
-
//
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
419
|
+
// Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
|
|
420
|
+
// and reqCtx stub headers are merged in.
|
|
421
|
+
responseHolder.response = mergeResponse(response, stubResponse.headers, {
|
|
422
|
+
stubOverridesNonCookie: true,
|
|
423
|
+
mergeReqCtx: true,
|
|
371
424
|
});
|
|
372
425
|
|
|
373
426
|
return responseHolder.response;
|
|
@@ -429,8 +482,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
429
482
|
try {
|
|
430
483
|
result = await entry.handler(ctx, wrappedNext);
|
|
431
484
|
} catch (error) {
|
|
432
|
-
|
|
433
|
-
|
|
485
|
+
// Thrown Response is short-circuit control flow, not an error.
|
|
486
|
+
// Fall through to the `if (result instanceof Response)` branch below
|
|
487
|
+
// so stub headers and request-context cookies merge as they do for
|
|
488
|
+
// an explicit `return new Response(...)`. Real errors propagate.
|
|
489
|
+
if (error instanceof Response) {
|
|
490
|
+
result = error;
|
|
491
|
+
} else {
|
|
492
|
+
finishMiddleware();
|
|
493
|
+
throw error;
|
|
494
|
+
}
|
|
434
495
|
}
|
|
435
496
|
finishMiddleware();
|
|
436
497
|
|
|
@@ -451,41 +512,18 @@ export async function executeMiddleware<TEnv>(
|
|
|
451
512
|
|
|
452
513
|
// Explicit return takes precedence (middleware short-circuit).
|
|
453
514
|
// Merge stub headers (from ctx.header before this point) and
|
|
454
|
-
// RequestContext stub headers (from
|
|
515
|
+
// RequestContext stub headers (from cookies().set()) into the
|
|
455
516
|
// returned Response so they are not lost.
|
|
456
517
|
if (result instanceof Response) {
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
mergedHeaders.append(name, value);
|
|
461
|
-
} else if (!mergedHeaders.has(name)) {
|
|
462
|
-
mergedHeaders.set(name, value);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
// Also merge shared RequestContext stub (cookies written via setCookie).
|
|
466
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
467
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
468
|
-
const reqCtx = _getRequestContext();
|
|
469
|
-
if (reqCtx) {
|
|
470
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
471
|
-
if (stubCookies.length > 0) {
|
|
472
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
473
|
-
for (const cookie of stubCookies) {
|
|
474
|
-
if (!existing.has(cookie)) {
|
|
475
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
480
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
481
|
-
mergedHeaders.set(name, value);
|
|
482
|
-
}
|
|
483
|
-
});
|
|
518
|
+
if (isWebSocketUpgradeResponse(result)) {
|
|
519
|
+
responseHolder.response = result;
|
|
520
|
+
return result;
|
|
484
521
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
522
|
+
// Explicit short-circuit: the returned Response's own headers win
|
|
523
|
+
// (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
|
|
524
|
+
const merged = mergeResponse(result, stubResponse.headers, {
|
|
525
|
+
stubOverridesNonCookie: false,
|
|
526
|
+
mergeReqCtx: true,
|
|
489
527
|
});
|
|
490
528
|
responseHolder.response = merged;
|
|
491
529
|
return merged;
|
|
@@ -530,23 +568,12 @@ export async function executeMiddleware<TEnv>(
|
|
|
530
568
|
// last merge point (e.g. cookies().set() called after await next()).
|
|
531
569
|
// The reqCtx stub may have already been partially merged during finalHandler
|
|
532
570
|
// or early-return paths; only append *new* Set-Cookie entries to avoid dupes.
|
|
571
|
+
//
|
|
572
|
+
// Skip for upgrade responses: upgrade headers are semantically immutable and
|
|
573
|
+
// set-cookie on an upgrade is not meaningful.
|
|
533
574
|
const reqCtx = _getRequestContext();
|
|
534
|
-
if (reqCtx) {
|
|
535
|
-
|
|
536
|
-
if (stubCookies.length > 0) {
|
|
537
|
-
const existingCookies = new Set(finalResponse.headers.getSetCookie());
|
|
538
|
-
for (const cookie of stubCookies) {
|
|
539
|
-
if (!existingCookies.has(cookie)) {
|
|
540
|
-
finalResponse.headers.append("set-cookie", cookie);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
// Fill in non-cookie headers that aren't already on the response
|
|
545
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
546
|
-
if (name !== "set-cookie" && !finalResponse.headers.has(name)) {
|
|
547
|
-
finalResponse.headers.set(name, value);
|
|
548
|
-
}
|
|
549
|
-
});
|
|
575
|
+
if (reqCtx && !isWebSocketUpgradeResponse(finalResponse)) {
|
|
576
|
+
mergeReqCtxStub(finalResponse.headers, reqCtx);
|
|
550
577
|
}
|
|
551
578
|
|
|
552
579
|
return finalResponse;
|
|
@@ -557,7 +584,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
557
584
|
*
|
|
558
585
|
* Intercepts use a shared stubResponse from the request context. This function:
|
|
559
586
|
* - Runs middleware in sequence with a simple next() chain
|
|
560
|
-
* - Returns Response if any middleware short-circuits (returns Response or redirects BEFORE next())
|
|
587
|
+
* - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
|
|
561
588
|
* - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
|
|
562
589
|
*
|
|
563
590
|
* @param middlewares - Array of middleware functions
|
|
@@ -616,7 +643,18 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
616
643
|
return next();
|
|
617
644
|
};
|
|
618
645
|
|
|
619
|
-
|
|
646
|
+
let result: Response | void;
|
|
647
|
+
try {
|
|
648
|
+
result = await middleware(ctx, guardedNext);
|
|
649
|
+
} catch (error) {
|
|
650
|
+
// Thrown Response is short-circuit control flow, parity with the
|
|
651
|
+
// explicit-return path below. Real errors propagate.
|
|
652
|
+
if (error instanceof Response) {
|
|
653
|
+
result = error;
|
|
654
|
+
} else {
|
|
655
|
+
throw error;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
620
658
|
|
|
621
659
|
if (result instanceof Response) {
|
|
622
660
|
earlyResponse = result;
|
|
@@ -640,21 +678,13 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
640
678
|
});
|
|
641
679
|
|
|
642
680
|
if (hasStubHeaders) {
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
|
|
647
|
-
stubResponse.headers
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
} else if (!mergedHeaders.has(name)) {
|
|
651
|
-
mergedHeaders.set(name, value);
|
|
652
|
-
}
|
|
653
|
-
});
|
|
654
|
-
return new Response(response.body, {
|
|
655
|
-
status: response.status,
|
|
656
|
-
statusText: response.statusText,
|
|
657
|
-
headers: mergedHeaders,
|
|
681
|
+
// Only fill in missing headers — the returned Response's explicit headers
|
|
682
|
+
// take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
|
|
683
|
+
// mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
|
|
684
|
+
// stub headers here (pinned by intercept-middleware-headers.test.ts).
|
|
685
|
+
return mergeResponse(response, stubResponse.headers, {
|
|
686
|
+
stubOverridesNonCookie: false,
|
|
687
|
+
mergeReqCtx: false,
|
|
658
688
|
});
|
|
659
689
|
}
|
|
660
690
|
return response;
|
|
@@ -695,7 +725,6 @@ export async function executeLoaderMiddleware<TEnv>(
|
|
|
695
725
|
regex: null,
|
|
696
726
|
paramNames: [],
|
|
697
727
|
handler,
|
|
698
|
-
mountPrefix: null,
|
|
699
728
|
} as MiddlewareEntry<TEnv>,
|
|
700
729
|
params,
|
|
701
730
|
}));
|