@rangojs/router 0.0.0-experimental.1b930379 → 0.0.0-experimental.1c0bdfad
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/AGENTS.md +12 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +441 -134
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3453 -1240
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +76 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +318 -15
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- 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 +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +279 -53
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +153 -109
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +101 -5
- package/skills/router-setup/SKILL.md +116 -8
- package/skills/scripts/SKILL.md +179 -0
- 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 +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -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 +123 -0
- package/skills/typesafety/SKILL.md +332 -29
- package/skills/use-cache/SKILL.md +46 -14
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +197 -150
- 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 +111 -31
- package/src/browser/navigation-client.ts +201 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +230 -35
- package/src/browser/prefetch/fetch.ts +338 -39
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +111 -16
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -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-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- 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 +80 -5
- package/src/browser/rsc-router.tsx +218 -76
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -61
- package/src/browser/types.ts +91 -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 +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +272 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +214 -48
- package/src/cache/cache-scope.ts +236 -89
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2224 -171
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- 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 +93 -12
- package/src/index.ts +133 -15
- package/src/internal-debug.ts +11 -10
- 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/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -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 +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +53 -12
- 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 +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- 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 +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +270 -172
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +115 -35
- package/src/router/router-options.ts +172 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +264 -77
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +474 -385
- 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 +5 -1
- package/src/router/segment-wrappers.ts +8 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +163 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +658 -511
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +77 -11
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +105 -72
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +263 -112
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +32 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +26 -46
- package/src/server/request-context.ts +398 -172
- package/src/ssr/index.tsx +25 -16
- 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 +701 -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 +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +371 -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 +343 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- 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 +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- 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 +214 -132
- 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 +57 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- 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 +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +238 -295
- package/src/vite/router-discovery.ts +940 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -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 +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- 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/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
package/src/router/middleware.ts
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
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 { escapeRegExp } from "../regex-escape.js";
|
|
5
|
+
import { parsePattern as parseRoutePattern } from "./pattern-matching.js";
|
|
6
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
7
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
13
8
|
import type {
|
|
14
9
|
CollectedMiddleware,
|
|
15
10
|
MiddlewareCollectableEntry,
|
|
@@ -19,26 +14,29 @@ import type {
|
|
|
19
14
|
ResponseHolder,
|
|
20
15
|
} from "./middleware-types.js";
|
|
21
16
|
import { _getRequestContext } from "../server/request-context.js";
|
|
17
|
+
import {
|
|
18
|
+
EXTERNAL_REDIRECT_MARKER,
|
|
19
|
+
isExternalRedirect,
|
|
20
|
+
markExternalRedirect,
|
|
21
|
+
} from "../redirect-origin.js";
|
|
22
22
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
23
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
24
|
+
import { observePhase, PHASES } from "./instrument.js";
|
|
25
|
+
import { stripInternalParams } from "./handler-context.js";
|
|
26
|
+
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
24
27
|
|
|
25
|
-
// Re-export types
|
|
28
|
+
// Re-export types consumed through this module's path.
|
|
26
29
|
export type {
|
|
27
30
|
CookieOptions,
|
|
28
|
-
CollectedMiddleware,
|
|
29
|
-
MiddlewareCollectableEntry,
|
|
30
31
|
MiddlewareContext,
|
|
31
32
|
MiddlewareEntry,
|
|
32
33
|
MiddlewareFn,
|
|
33
|
-
ResponseHolder,
|
|
34
34
|
} from "./middleware-types.js";
|
|
35
|
-
export { parseCookies, serializeCookie } from "./middleware-cookies.js";
|
|
36
35
|
|
|
37
36
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
38
|
-
/** Ignore post-next() durations below this threshold (measurement noise). */
|
|
39
37
|
const POST_METRIC_MIN_DURATION_MS = 0.01;
|
|
40
38
|
|
|
41
|
-
function
|
|
39
|
+
function getMiddlewareMetricLabel<TEnv>(
|
|
42
40
|
entry: MiddlewareEntry<TEnv>,
|
|
43
41
|
ordinal: number,
|
|
44
42
|
): string {
|
|
@@ -46,24 +44,49 @@ function getMiddlewareMetricBase<TEnv>(
|
|
|
46
44
|
const scope = entry.pattern ?? "*";
|
|
47
45
|
|
|
48
46
|
if (handlerName) {
|
|
49
|
-
return
|
|
47
|
+
return `middleware:${handlerName}@${scope}`;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function getMiddlewareMetricLabel<TEnv>(
|
|
56
|
-
entry: MiddlewareEntry<TEnv>,
|
|
57
|
-
ordinal: number,
|
|
58
|
-
): string {
|
|
59
|
-
return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
|
|
50
|
+
return `middleware:${scope}#${ordinal + 1}`;
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
54
|
+
* Compile a middleware scope pattern to a regex + param names.
|
|
55
|
+
*
|
|
56
|
+
* Middleware scopes reuse the route pattern parser (`parsePattern` from
|
|
57
|
+
* pattern-matching.ts), so they support the same param forms as routes —
|
|
58
|
+
* optional (`:x?`), constrained (`:x(en|gb)`), and suffix (`:x.html`) — in
|
|
59
|
+
* addition to the trailing-`*` wildcard middleware relies on. Before this
|
|
60
|
+
* unification the middleware-side parser handled only static, bare `:param`,
|
|
61
|
+
* and trailing `*`, so e.g. `router.use("/:locale(en|gb)/*", mw)` silently
|
|
62
|
+
* named the param "locale(en|gb)" and never enforced the constraint.
|
|
63
|
+
*
|
|
64
|
+
* Middleware matching semantics deliberately differ from route matching, so we
|
|
65
|
+
* emit the regex here rather than route through `compilePattern`:
|
|
66
|
+
* - `*` alone matches every path (`/^.*$/`).
|
|
67
|
+
* - A trailing `*` segment is an OPTIONAL subtree match (`(?:/.*)?`): `/admin/*`
|
|
68
|
+
* matches `/admin`, `/admin/`, and `/admin/users`. It contributes no param
|
|
69
|
+
* name (unlike route wildcards, which capture `*`).
|
|
70
|
+
* - A NON-trailing `*` is also OPTIONAL (`(?:/.*)?`), matching zero-or-more
|
|
71
|
+
* intermediate segments: `/a/<star>/b` matches both `/a/b` and `/a/x/b`. This
|
|
72
|
+
* mirrors the pre-unification parser, which compiled every `*` part as
|
|
73
|
+
* optional regardless of position.
|
|
74
|
+
* - A pattern without a trailing `*` tolerates a trailing slash (`/?$`).
|
|
75
|
+
* - Constraints are baked into the regex as an alternation so `matchMiddleware`
|
|
76
|
+
* (a bare `regex.test`) enforces them without extra validation. Constraint
|
|
77
|
+
* values are matched against the raw (still URL-encoded) path segment, which
|
|
78
|
+
* matches the pre-unification middleware behavior (it never decoded for
|
|
79
|
+
* matching); the constraint string is regex-escaped so values like `en.gb`
|
|
80
|
+
* are treated literally.
|
|
81
|
+
*
|
|
82
|
+
* The route segment parser only recognizes `/`-prefixed segments, but the
|
|
83
|
+
* pre-unification middleware parser split on `/` and dropped empty parts, so a
|
|
84
|
+
* leading slash was irrelevant: `use("admin/*")` and `use("/admin/*")` scoped
|
|
85
|
+
* identically. Normalize a non-`*` pattern to have a leading slash before
|
|
86
|
+
* parsing so that behavior is preserved (without it, `parseRoutePattern("admin/*")`
|
|
87
|
+
* drops the static `admin` and the scope explodes to every path).
|
|
65
88
|
*/
|
|
66
|
-
export function
|
|
89
|
+
export function compileMiddlewarePattern(pattern: string): {
|
|
67
90
|
regex: RegExp;
|
|
68
91
|
paramNames: string[];
|
|
69
92
|
} {
|
|
@@ -71,47 +94,54 @@ export function parsePattern(pattern: string): {
|
|
|
71
94
|
return { regex: /^.*$/, paramNames: [] };
|
|
72
95
|
}
|
|
73
96
|
|
|
97
|
+
const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
98
|
+
const segments = parseRoutePattern(normalizedPattern);
|
|
74
99
|
const paramNames: string[] = [];
|
|
75
100
|
let regexStr = "^";
|
|
101
|
+
let hasTrailingWildcard = false;
|
|
76
102
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (let i = 0; i < parts.length; i++) {
|
|
80
|
-
const part = parts[i];
|
|
103
|
+
for (let i = 0; i < segments.length; i++) {
|
|
104
|
+
const segment = segments[i];
|
|
81
105
|
|
|
82
|
-
if (
|
|
83
|
-
//
|
|
106
|
+
if (segment.type === "wildcard") {
|
|
107
|
+
// Optional subtree match (parity with the original middleware parser,
|
|
108
|
+
// which compiled every `*` as `(?:/.*)?`). A trailing `*` matches the
|
|
109
|
+
// subtree; a non-trailing `*` matches zero-or-more intermediate segments,
|
|
110
|
+
// so `/a/<star>/b` still matches `/a/b`.
|
|
84
111
|
regexStr += "(?:/.*)?";
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
if (i === segments.length - 1) {
|
|
113
|
+
hasTrailingWildcard = true;
|
|
114
|
+
}
|
|
115
|
+
} else if (segment.type === "param") {
|
|
116
|
+
paramNames.push(segment.value);
|
|
117
|
+
const suffixPattern = segment.suffix ? escapeRegExp(segment.suffix) : "";
|
|
118
|
+
const valuePattern = segment.constraint
|
|
119
|
+
? `(${segment.constraint.map(escapeRegExp).join("|")})`
|
|
120
|
+
: "([^/]+)";
|
|
121
|
+
if (segment.optional) {
|
|
122
|
+
regexStr += `(?:/${valuePattern}${suffixPattern})?`;
|
|
123
|
+
} else {
|
|
124
|
+
regexStr += `/${valuePattern}${suffixPattern}`;
|
|
125
|
+
}
|
|
90
126
|
} else {
|
|
91
|
-
//
|
|
92
|
-
regexStr += "/" +
|
|
127
|
+
// Static literal
|
|
128
|
+
regexStr += "/" + escapeRegExp(segment.value);
|
|
93
129
|
}
|
|
94
130
|
}
|
|
95
131
|
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
regexStr += "/?$";
|
|
99
|
-
} else {
|
|
100
|
-
regexStr += "$";
|
|
101
|
-
}
|
|
132
|
+
// Without a trailing `*`, match exactly with an optional trailing slash.
|
|
133
|
+
regexStr += hasTrailingWildcard ? "$" : "/?$";
|
|
102
134
|
|
|
103
135
|
return { regex: new RegExp(regexStr), paramNames };
|
|
104
136
|
}
|
|
105
137
|
|
|
106
138
|
/**
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Extract params from a pathname using a pattern's regex and param names
|
|
139
|
+
* Extract params from a pathname using a pattern's regex and param names.
|
|
140
|
+
*
|
|
141
|
+
* Values are URL-decoded so apps see the raw string (e.g. "ivo@example.com")
|
|
142
|
+
* instead of the percent-encoded form ("ivo%40example.com"). This matches the
|
|
143
|
+
* contract assumed by ctx.reverse (which re-encodes) and aligns with
|
|
144
|
+
* Express/React Router/Fastify/Koa.
|
|
115
145
|
*/
|
|
116
146
|
export function extractParams(
|
|
117
147
|
pathname: string,
|
|
@@ -123,7 +153,7 @@ export function extractParams(
|
|
|
123
153
|
|
|
124
154
|
const params: Record<string, string> = {};
|
|
125
155
|
for (let i = 0; i < paramNames.length; i++) {
|
|
126
|
-
params[paramNames[i]] = match[i + 1] || "";
|
|
156
|
+
params[paramNames[i]] = safeDecodeURIComponent(match[i + 1] || "");
|
|
127
157
|
}
|
|
128
158
|
return params;
|
|
129
159
|
}
|
|
@@ -147,7 +177,7 @@ export function createMiddlewareContext<TEnv>(
|
|
|
147
177
|
search?: Record<string, unknown>,
|
|
148
178
|
) => string,
|
|
149
179
|
): MiddlewareContext<TEnv> {
|
|
150
|
-
const url = new URL(request.url);
|
|
180
|
+
const url = stripInternalParams(new URL(request.url));
|
|
151
181
|
|
|
152
182
|
// Track the initial response to detect pre/post-next() phase.
|
|
153
183
|
// Before next(): responseHolder.response === initialResponse (the stub).
|
|
@@ -178,14 +208,22 @@ export function createMiddlewareContext<TEnv>(
|
|
|
178
208
|
return responseHolder.response;
|
|
179
209
|
};
|
|
180
210
|
|
|
211
|
+
// Capture reqCtx once: the request-scoped platform fields
|
|
212
|
+
// (originalUrl, executionContext, waitUntil) are immutable per request,
|
|
213
|
+
// so snapshotting beats re-reading ALS on every access. The lazy getters
|
|
214
|
+
// below (routeName, theme, setTheme) stay lazy because those can change
|
|
215
|
+
// during `await next()`.
|
|
216
|
+
const reqCtx = _getRequestContext();
|
|
181
217
|
return {
|
|
182
218
|
request,
|
|
183
219
|
url,
|
|
184
|
-
originalUrl: new URL(request.url),
|
|
220
|
+
originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
|
|
185
221
|
pathname: url.pathname,
|
|
186
222
|
searchParams: url.searchParams,
|
|
187
223
|
env: env as MiddlewareContext<TEnv>["env"],
|
|
188
224
|
params,
|
|
225
|
+
executionContext: reqCtx?.executionContext,
|
|
226
|
+
waitUntil: reqCtx ? reqCtx.waitUntil.bind(reqCtx) : fireAndForgetWaitUntil,
|
|
189
227
|
// Getter: re-derives from request context on each access so that global
|
|
190
228
|
// middleware sees the matched route name after await next().
|
|
191
229
|
get routeName(): MiddlewareContext<TEnv>["routeName"] {
|
|
@@ -203,12 +241,9 @@ export function createMiddlewareContext<TEnv>(
|
|
|
203
241
|
get: ((keyOrVar: any) =>
|
|
204
242
|
contextGet(variables, keyOrVar)) as MiddlewareContext<TEnv>["get"],
|
|
205
243
|
|
|
206
|
-
set: ((keyOrVar: any, value: unknown) => {
|
|
207
|
-
contextSet(variables, keyOrVar, value);
|
|
244
|
+
set: ((keyOrVar: any, value: unknown, options?: any) => {
|
|
245
|
+
contextSet(variables, keyOrVar, value, options);
|
|
208
246
|
}) as MiddlewareContext<TEnv>["set"],
|
|
209
|
-
|
|
210
|
-
var: variables as MiddlewareContext<TEnv>["var"],
|
|
211
|
-
|
|
212
247
|
header(name: string, value: string): void {
|
|
213
248
|
// Before next(): delegate to shared RequestContext stub
|
|
214
249
|
if (isPreNext()) {
|
|
@@ -247,9 +282,13 @@ export function createMiddlewareContext<TEnv>(
|
|
|
247
282
|
|
|
248
283
|
reverse:
|
|
249
284
|
reverse ??
|
|
250
|
-
((
|
|
285
|
+
((
|
|
286
|
+
name: string,
|
|
287
|
+
_params?: Record<string, string>,
|
|
288
|
+
_search?: Record<string, unknown>,
|
|
289
|
+
) => {
|
|
251
290
|
throw new Error(
|
|
252
|
-
`ctx.reverse() is not available
|
|
291
|
+
`ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
|
|
253
292
|
);
|
|
254
293
|
}),
|
|
255
294
|
|
|
@@ -283,9 +322,16 @@ export function matchMiddleware<TEnv>(
|
|
|
283
322
|
continue;
|
|
284
323
|
}
|
|
285
324
|
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
325
|
+
// Run the scope regex ONCE per entry. The old code ran test() then, on a
|
|
326
|
+
// hit, extractParams' match() — a second full pass over the same string for
|
|
327
|
+
// every matching entry on the per-request hot path. The regexes carry no
|
|
328
|
+
// `g` flag, so there is no lastIndex statefulness across this single match.
|
|
329
|
+
const m = pathname.match(entry.regex);
|
|
330
|
+
if (m) {
|
|
331
|
+
const params: Record<string, string> = {};
|
|
332
|
+
for (let i = 0; i < entry.paramNames.length; i++) {
|
|
333
|
+
params[entry.paramNames[i]] = safeDecodeURIComponent(m[i + 1] || "");
|
|
334
|
+
}
|
|
289
335
|
matches.push({ entry, params });
|
|
290
336
|
}
|
|
291
337
|
}
|
|
@@ -293,6 +339,88 @@ export function matchMiddleware<TEnv>(
|
|
|
293
339
|
return matches;
|
|
294
340
|
}
|
|
295
341
|
|
|
342
|
+
// Set-Cookie is appended; for other headers stubOverridesNonCookie=true
|
|
343
|
+
// overwrites (chain ran to completion), false fills only missing slots (an
|
|
344
|
+
// explicit short-circuit Response's own headers win).
|
|
345
|
+
function mergeStubHeaders(
|
|
346
|
+
target: Headers,
|
|
347
|
+
stub: Headers,
|
|
348
|
+
stubOverridesNonCookie: boolean,
|
|
349
|
+
): void {
|
|
350
|
+
stub.forEach((value, name) => {
|
|
351
|
+
// The reserved external-redirect marker is internal and never a trust
|
|
352
|
+
// signal; never copy a stub value (e.g. a stray ctx.header() call) onto a
|
|
353
|
+
// browser-facing response. The opt-in is the out-of-band brand.
|
|
354
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
355
|
+
if (name.toLowerCase() === "set-cookie") {
|
|
356
|
+
target.append(name, value);
|
|
357
|
+
} else if (stubOverridesNonCookie || !target.has(name)) {
|
|
358
|
+
target.set(name, value);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Set-Cookie is deduped so a nested inner executeMiddleware that already merged
|
|
364
|
+
// the same reqCtx cookies does not duplicate them; other headers fill if missing.
|
|
365
|
+
function mergeReqCtxStub(
|
|
366
|
+
target: Headers,
|
|
367
|
+
reqCtx: ReturnType<typeof _getRequestContext>,
|
|
368
|
+
): void {
|
|
369
|
+
if (!reqCtx) return;
|
|
370
|
+
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
371
|
+
if (stubCookies.length > 0) {
|
|
372
|
+
const existing = new Set(target.getSetCookie());
|
|
373
|
+
for (const cookie of stubCookies) {
|
|
374
|
+
if (!existing.has(cookie)) {
|
|
375
|
+
target.append("set-cookie", cookie);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
reqCtx.res.headers.forEach((value, name) => {
|
|
380
|
+
// Never propagate the reserved external-redirect marker (see mergeStubHeaders).
|
|
381
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
382
|
+
if (name !== "set-cookie" && !target.has(name)) {
|
|
383
|
+
target.set(name, value);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Clone `base` with stub headers merged into a fresh Headers (the clone keeps
|
|
389
|
+
// the body mutable for post-next() modifications). Set-Cookie is always
|
|
390
|
+
// appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
|
|
391
|
+
// mergeReqCtx folds in RequestContext stub cookies/headers; the intercept
|
|
392
|
+
// short-circuit path passes false (its reqCtx headers are not merged here),
|
|
393
|
+
// which is the one deliberate divergence between the call sites.
|
|
394
|
+
function mergeResponse(
|
|
395
|
+
base: Response,
|
|
396
|
+
stub: Headers,
|
|
397
|
+
opts: { stubOverridesNonCookie: boolean; mergeReqCtx: boolean },
|
|
398
|
+
): Response {
|
|
399
|
+
const mergedHeaders = new Headers(base.headers);
|
|
400
|
+
// The reserved external-redirect marker is never a trust signal and must never
|
|
401
|
+
// reach the browser. The guard strips it on 3xx redirects; strip it here too so
|
|
402
|
+
// a forged value cannot ride a non-3xx middleware response (which the 3xx-only
|
|
403
|
+
// guard would not touch) to the client. The opt-in is the out-of-band brand.
|
|
404
|
+
mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
|
|
405
|
+
mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
|
|
406
|
+
if (opts.mergeReqCtx) {
|
|
407
|
+
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
408
|
+
}
|
|
409
|
+
const merged = new Response(base.body, {
|
|
410
|
+
status: base.status,
|
|
411
|
+
statusText: base.statusText,
|
|
412
|
+
headers: mergedHeaders,
|
|
413
|
+
});
|
|
414
|
+
// Transfer the out-of-band external-redirect brand across this rebuild: a
|
|
415
|
+
// middleware short-circuit `return redirect(url, { external: true })` reaches
|
|
416
|
+
// the open-redirect guard only after this merge, and the brand lives on the
|
|
417
|
+
// Response object, not in its headers.
|
|
418
|
+
if (isExternalRedirect(base)) {
|
|
419
|
+
markExternalRedirect(merged);
|
|
420
|
+
}
|
|
421
|
+
return merged;
|
|
422
|
+
}
|
|
423
|
+
|
|
296
424
|
/**
|
|
297
425
|
* Execute middleware chain
|
|
298
426
|
*
|
|
@@ -301,7 +429,7 @@ export function matchMiddleware<TEnv>(
|
|
|
301
429
|
* - `ctx.headers` available before and after `await next()`
|
|
302
430
|
* - `ctx.header()` shorthand for setting a single header
|
|
303
431
|
* - Forgiving: if middleware doesn't return, uses the downstream response
|
|
304
|
-
* - Short-circuit: return Response to stop chain
|
|
432
|
+
* - Short-circuit: return OR throw a Response to stop chain
|
|
305
433
|
* - Error catching: try/catch around `next()` works
|
|
306
434
|
*/
|
|
307
435
|
export async function executeMiddleware<TEnv>(
|
|
@@ -331,42 +459,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
331
459
|
// End of chain - call actual RSC handler
|
|
332
460
|
const response = await finalHandler();
|
|
333
461
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
stubResponse.headers.forEach((value, name) => {
|
|
338
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
339
|
-
mergedHeaders.append(name, value);
|
|
340
|
-
} else {
|
|
341
|
-
mergedHeaders.set(name, value);
|
|
342
|
-
}
|
|
343
|
-
});
|
|
344
|
-
// Also merge shared RequestContext stub (cookies written via cookies().set()).
|
|
345
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
346
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
347
|
-
const reqCtx = _getRequestContext();
|
|
348
|
-
if (reqCtx) {
|
|
349
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
350
|
-
if (stubCookies.length > 0) {
|
|
351
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
352
|
-
for (const cookie of stubCookies) {
|
|
353
|
-
if (!existing.has(cookie)) {
|
|
354
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
359
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
360
|
-
mergedHeaders.set(name, value);
|
|
361
|
-
}
|
|
362
|
-
});
|
|
462
|
+
if (isWebSocketUpgradeResponse(response)) {
|
|
463
|
+
responseHolder.response = response;
|
|
464
|
+
return response;
|
|
363
465
|
}
|
|
364
466
|
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
467
|
+
// Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
|
|
468
|
+
// and reqCtx stub headers are merged in.
|
|
469
|
+
responseHolder.response = mergeResponse(response, stubResponse.headers, {
|
|
470
|
+
stubOverridesNonCookie: true,
|
|
471
|
+
mergeReqCtx: true,
|
|
370
472
|
});
|
|
371
473
|
|
|
372
474
|
return responseHolder.response;
|
|
@@ -424,12 +526,28 @@ export async function executeMiddleware<TEnv>(
|
|
|
424
526
|
return nextPromise;
|
|
425
527
|
};
|
|
426
528
|
|
|
529
|
+
// Wrap the middleware (including its downstream next() chain) in its span
|
|
530
|
+
// via the unified phase API. metric:false — the middleware's perf metric is
|
|
531
|
+
// its exclusive pre/post own-time, recorded directly above and below, finer
|
|
532
|
+
// than a single wrap. Spans nest by async context, so this onions
|
|
533
|
+
// middleware-over-middleware and the core handler underneath. Pass-through
|
|
534
|
+
// when neither surface is active.
|
|
427
535
|
let result: Response | void;
|
|
428
536
|
try {
|
|
429
|
-
result = await
|
|
537
|
+
result = await observePhase(PHASES.middleware(metricLabel), () =>
|
|
538
|
+
entry.handler(ctx, wrappedNext),
|
|
539
|
+
);
|
|
430
540
|
} catch (error) {
|
|
431
|
-
|
|
432
|
-
|
|
541
|
+
// Thrown Response is short-circuit control flow, not an error.
|
|
542
|
+
// Fall through to the `if (result instanceof Response)` branch below
|
|
543
|
+
// so stub headers and request-context cookies merge as they do for
|
|
544
|
+
// an explicit `return new Response(...)`. Real errors propagate.
|
|
545
|
+
if (error instanceof Response) {
|
|
546
|
+
result = error;
|
|
547
|
+
} else {
|
|
548
|
+
finishMiddleware();
|
|
549
|
+
throw error;
|
|
550
|
+
}
|
|
433
551
|
}
|
|
434
552
|
finishMiddleware();
|
|
435
553
|
|
|
@@ -450,41 +568,18 @@ export async function executeMiddleware<TEnv>(
|
|
|
450
568
|
|
|
451
569
|
// Explicit return takes precedence (middleware short-circuit).
|
|
452
570
|
// Merge stub headers (from ctx.header before this point) and
|
|
453
|
-
// RequestContext stub headers (from
|
|
571
|
+
// RequestContext stub headers (from cookies().set()) into the
|
|
454
572
|
// returned Response so they are not lost.
|
|
455
573
|
if (result instanceof Response) {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
mergedHeaders.append(name, value);
|
|
460
|
-
} else if (!mergedHeaders.has(name)) {
|
|
461
|
-
mergedHeaders.set(name, value);
|
|
462
|
-
}
|
|
463
|
-
});
|
|
464
|
-
// Also merge shared RequestContext stub (cookies written via setCookie).
|
|
465
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
466
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
467
|
-
const reqCtx = _getRequestContext();
|
|
468
|
-
if (reqCtx) {
|
|
469
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
470
|
-
if (stubCookies.length > 0) {
|
|
471
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
472
|
-
for (const cookie of stubCookies) {
|
|
473
|
-
if (!existing.has(cookie)) {
|
|
474
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
479
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
480
|
-
mergedHeaders.set(name, value);
|
|
481
|
-
}
|
|
482
|
-
});
|
|
574
|
+
if (isWebSocketUpgradeResponse(result)) {
|
|
575
|
+
responseHolder.response = result;
|
|
576
|
+
return result;
|
|
483
577
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
578
|
+
// Explicit short-circuit: the returned Response's own headers win
|
|
579
|
+
// (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
|
|
580
|
+
const merged = mergeResponse(result, stubResponse.headers, {
|
|
581
|
+
stubOverridesNonCookie: false,
|
|
582
|
+
mergeReqCtx: true,
|
|
488
583
|
});
|
|
489
584
|
responseHolder.response = merged;
|
|
490
585
|
return merged;
|
|
@@ -529,23 +624,12 @@ export async function executeMiddleware<TEnv>(
|
|
|
529
624
|
// last merge point (e.g. cookies().set() called after await next()).
|
|
530
625
|
// The reqCtx stub may have already been partially merged during finalHandler
|
|
531
626
|
// or early-return paths; only append *new* Set-Cookie entries to avoid dupes.
|
|
627
|
+
//
|
|
628
|
+
// Skip for upgrade responses: upgrade headers are semantically immutable and
|
|
629
|
+
// set-cookie on an upgrade is not meaningful.
|
|
532
630
|
const reqCtx = _getRequestContext();
|
|
533
|
-
if (reqCtx) {
|
|
534
|
-
|
|
535
|
-
if (stubCookies.length > 0) {
|
|
536
|
-
const existingCookies = new Set(finalResponse.headers.getSetCookie());
|
|
537
|
-
for (const cookie of stubCookies) {
|
|
538
|
-
if (!existingCookies.has(cookie)) {
|
|
539
|
-
finalResponse.headers.append("set-cookie", cookie);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
// Fill in non-cookie headers that aren't already on the response
|
|
544
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
545
|
-
if (name !== "set-cookie" && !finalResponse.headers.has(name)) {
|
|
546
|
-
finalResponse.headers.set(name, value);
|
|
547
|
-
}
|
|
548
|
-
});
|
|
631
|
+
if (reqCtx && !isWebSocketUpgradeResponse(finalResponse)) {
|
|
632
|
+
mergeReqCtxStub(finalResponse.headers, reqCtx);
|
|
549
633
|
}
|
|
550
634
|
|
|
551
635
|
return finalResponse;
|
|
@@ -556,7 +640,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
556
640
|
*
|
|
557
641
|
* Intercepts use a shared stubResponse from the request context. This function:
|
|
558
642
|
* - Runs middleware in sequence with a simple next() chain
|
|
559
|
-
* - Returns Response if any middleware short-circuits (returns Response or redirects BEFORE next())
|
|
643
|
+
* - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
|
|
560
644
|
* - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
|
|
561
645
|
*
|
|
562
646
|
* @param middlewares - Array of middleware functions
|
|
@@ -594,6 +678,7 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
594
678
|
return stubResponse;
|
|
595
679
|
}
|
|
596
680
|
|
|
681
|
+
const ordinal = index;
|
|
597
682
|
const middleware = middlewares[index++];
|
|
598
683
|
const ctx = createMiddlewareContext(
|
|
599
684
|
request,
|
|
@@ -615,7 +700,29 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
615
700
|
return next();
|
|
616
701
|
};
|
|
617
702
|
|
|
618
|
-
|
|
703
|
+
// Span-wrap each intercept middleware as rango.middleware (metric:false —
|
|
704
|
+
// intercept runs inside the render phase already metered by render:total, so
|
|
705
|
+
// it contributes a span but no separate perf metric). Bare MiddlewareFns
|
|
706
|
+
// have no pattern, so the label is scoped to "*" like a pattern-less entry.
|
|
707
|
+
const label = getMiddlewareMetricLabel(
|
|
708
|
+
{ handler: middleware, pattern: null } as MiddlewareEntry<TEnv>,
|
|
709
|
+
ordinal,
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
let result: Response | void;
|
|
713
|
+
try {
|
|
714
|
+
result = await observePhase(PHASES.middleware(label), () =>
|
|
715
|
+
middleware(ctx, guardedNext),
|
|
716
|
+
);
|
|
717
|
+
} catch (error) {
|
|
718
|
+
// Thrown Response is short-circuit control flow, parity with the
|
|
719
|
+
// explicit-return path below. Real errors propagate.
|
|
720
|
+
if (error instanceof Response) {
|
|
721
|
+
result = error;
|
|
722
|
+
} else {
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
619
726
|
|
|
620
727
|
if (result instanceof Response) {
|
|
621
728
|
earlyResponse = result;
|
|
@@ -639,21 +746,13 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
639
746
|
});
|
|
640
747
|
|
|
641
748
|
if (hasStubHeaders) {
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
//
|
|
645
|
-
|
|
646
|
-
stubResponse.headers
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
} else if (!mergedHeaders.has(name)) {
|
|
650
|
-
mergedHeaders.set(name, value);
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
return new Response(response.body, {
|
|
654
|
-
status: response.status,
|
|
655
|
-
statusText: response.statusText,
|
|
656
|
-
headers: mergedHeaders,
|
|
749
|
+
// Only fill in missing headers — the returned Response's explicit headers
|
|
750
|
+
// take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
|
|
751
|
+
// mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
|
|
752
|
+
// stub headers here (pinned by intercept-middleware-headers.test.ts).
|
|
753
|
+
return mergeResponse(response, stubResponse.headers, {
|
|
754
|
+
stubOverridesNonCookie: false,
|
|
755
|
+
mergeReqCtx: false,
|
|
657
756
|
});
|
|
658
757
|
}
|
|
659
758
|
return response;
|
|
@@ -694,7 +793,6 @@ export async function executeLoaderMiddleware<TEnv>(
|
|
|
694
793
|
regex: null,
|
|
695
794
|
paramNames: [],
|
|
696
795
|
handler,
|
|
697
|
-
mountPrefix: null,
|
|
698
796
|
} as MiddlewareEntry<TEnv>,
|
|
699
797
|
params,
|
|
700
798
|
}));
|