@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
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 +8 -0
- package/README.md +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- package/package.json +68 -14
- 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 +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- 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 +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -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 +122 -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 +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -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 +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- 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 +17 -14
- 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 +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- 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 +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- 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 +117 -23
- 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 +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- 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 +224 -41
- package/src/cache/cache-scope.ts +188 -82
- 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 +1989 -378
- 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 +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +36 -61
- 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 +18 -6
- 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 +31 -23
- package/src/handles/MetaTags.tsx +62 -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 +96 -12
- package/src/index.ts +94 -14
- 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 +32 -37
- package/src/prerender.ts +61 -6
- 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 +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +272 -320
- 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 +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -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 +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -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 +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- 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 +105 -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 +357 -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/run-transition-when.ts +164 -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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -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 +96 -68
- 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 +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- 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-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- 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 +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +109 -118
- package/src/vite/router-discovery.ts +718 -119
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- 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/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 +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- 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,16 +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";
|
|
13
6
|
import { safeDecodeURIComponent } from "./url-params.js";
|
|
7
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
14
8
|
import type {
|
|
15
9
|
CollectedMiddleware,
|
|
16
10
|
MiddlewareCollectableEntry,
|
|
@@ -20,28 +14,29 @@ import type {
|
|
|
20
14
|
ResponseHolder,
|
|
21
15
|
} from "./middleware-types.js";
|
|
22
16
|
import { _getRequestContext } from "../server/request-context.js";
|
|
17
|
+
import {
|
|
18
|
+
EXTERNAL_REDIRECT_MARKER,
|
|
19
|
+
isExternalRedirect,
|
|
20
|
+
markExternalRedirect,
|
|
21
|
+
} from "../redirect-origin.js";
|
|
23
22
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
24
23
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
24
|
+
import { observePhase, PHASES } from "./instrument.js";
|
|
25
25
|
import { stripInternalParams } from "./handler-context.js";
|
|
26
26
|
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
27
27
|
|
|
28
|
-
// Re-export types
|
|
28
|
+
// Re-export types consumed through this module's path.
|
|
29
29
|
export type {
|
|
30
30
|
CookieOptions,
|
|
31
|
-
CollectedMiddleware,
|
|
32
|
-
MiddlewareCollectableEntry,
|
|
33
31
|
MiddlewareContext,
|
|
34
32
|
MiddlewareEntry,
|
|
35
33
|
MiddlewareFn,
|
|
36
|
-
ResponseHolder,
|
|
37
34
|
} from "./middleware-types.js";
|
|
38
|
-
export { parseCookies, serializeCookie } from "./middleware-cookies.js";
|
|
39
35
|
|
|
40
36
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
41
|
-
/** Ignore post-next() durations below this threshold (measurement noise). */
|
|
42
37
|
const POST_METRIC_MIN_DURATION_MS = 0.01;
|
|
43
38
|
|
|
44
|
-
function
|
|
39
|
+
function getMiddlewareMetricLabel<TEnv>(
|
|
45
40
|
entry: MiddlewareEntry<TEnv>,
|
|
46
41
|
ordinal: number,
|
|
47
42
|
): string {
|
|
@@ -49,24 +44,49 @@ function getMiddlewareMetricBase<TEnv>(
|
|
|
49
44
|
const scope = entry.pattern ?? "*";
|
|
50
45
|
|
|
51
46
|
if (handlerName) {
|
|
52
|
-
return
|
|
47
|
+
return `middleware:${handlerName}@${scope}`;
|
|
53
48
|
}
|
|
54
49
|
|
|
55
|
-
return
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function getMiddlewareMetricLabel<TEnv>(
|
|
59
|
-
entry: MiddlewareEntry<TEnv>,
|
|
60
|
-
ordinal: number,
|
|
61
|
-
): string {
|
|
62
|
-
return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
|
|
50
|
+
return `middleware:${scope}#${ordinal + 1}`;
|
|
63
51
|
}
|
|
64
52
|
|
|
65
53
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
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).
|
|
68
88
|
*/
|
|
69
|
-
export function
|
|
89
|
+
export function compileMiddlewarePattern(pattern: string): {
|
|
70
90
|
regex: RegExp;
|
|
71
91
|
paramNames: string[];
|
|
72
92
|
} {
|
|
@@ -74,45 +94,47 @@ export function parsePattern(pattern: string): {
|
|
|
74
94
|
return { regex: /^.*$/, paramNames: [] };
|
|
75
95
|
}
|
|
76
96
|
|
|
97
|
+
const normalizedPattern = pattern.startsWith("/") ? pattern : `/${pattern}`;
|
|
98
|
+
const segments = parseRoutePattern(normalizedPattern);
|
|
77
99
|
const paramNames: string[] = [];
|
|
78
100
|
let regexStr = "^";
|
|
101
|
+
let hasTrailingWildcard = false;
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for (let i = 0; i < parts.length; i++) {
|
|
83
|
-
const part = parts[i];
|
|
103
|
+
for (let i = 0; i < segments.length; i++) {
|
|
104
|
+
const segment = segments[i];
|
|
84
105
|
|
|
85
|
-
if (
|
|
86
|
-
//
|
|
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`.
|
|
87
111
|
regexStr += "(?:/.*)?";
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
}
|
|
93
126
|
} else {
|
|
94
|
-
//
|
|
95
|
-
regexStr += "/" +
|
|
127
|
+
// Static literal
|
|
128
|
+
regexStr += "/" + escapeRegExp(segment.value);
|
|
96
129
|
}
|
|
97
130
|
}
|
|
98
131
|
|
|
99
|
-
//
|
|
100
|
-
|
|
101
|
-
regexStr += "/?$";
|
|
102
|
-
} else {
|
|
103
|
-
regexStr += "$";
|
|
104
|
-
}
|
|
132
|
+
// Without a trailing `*`, match exactly with an optional trailing slash.
|
|
133
|
+
regexStr += hasTrailingWildcard ? "$" : "/?$";
|
|
105
134
|
|
|
106
135
|
return { regex: new RegExp(regexStr), paramNames };
|
|
107
136
|
}
|
|
108
137
|
|
|
109
|
-
/**
|
|
110
|
-
* Escape special regex characters
|
|
111
|
-
*/
|
|
112
|
-
function escapeRegex(str: string): string {
|
|
113
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
138
|
/**
|
|
117
139
|
* Extract params from a pathname using a pattern's regex and param names.
|
|
118
140
|
*
|
|
@@ -186,14 +208,22 @@ export function createMiddlewareContext<TEnv>(
|
|
|
186
208
|
return responseHolder.response;
|
|
187
209
|
};
|
|
188
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();
|
|
189
217
|
return {
|
|
190
218
|
request,
|
|
191
219
|
url,
|
|
192
|
-
originalUrl: new URL(request.url),
|
|
220
|
+
originalUrl: reqCtx?.originalUrl ?? new URL(request.url),
|
|
193
221
|
pathname: url.pathname,
|
|
194
222
|
searchParams: url.searchParams,
|
|
195
223
|
env: env as MiddlewareContext<TEnv>["env"],
|
|
196
224
|
params,
|
|
225
|
+
executionContext: reqCtx?.executionContext,
|
|
226
|
+
waitUntil: reqCtx ? reqCtx.waitUntil.bind(reqCtx) : fireAndForgetWaitUntil,
|
|
197
227
|
// Getter: re-derives from request context on each access so that global
|
|
198
228
|
// middleware sees the matched route name after await next().
|
|
199
229
|
get routeName(): MiddlewareContext<TEnv>["routeName"] {
|
|
@@ -252,9 +282,13 @@ export function createMiddlewareContext<TEnv>(
|
|
|
252
282
|
|
|
253
283
|
reverse:
|
|
254
284
|
reverse ??
|
|
255
|
-
((
|
|
285
|
+
((
|
|
286
|
+
name: string,
|
|
287
|
+
_params?: Record<string, string>,
|
|
288
|
+
_search?: Record<string, unknown>,
|
|
289
|
+
) => {
|
|
256
290
|
throw new Error(
|
|
257
|
-
`ctx.reverse() is not available
|
|
291
|
+
`ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
|
|
258
292
|
);
|
|
259
293
|
}),
|
|
260
294
|
|
|
@@ -288,9 +322,16 @@ export function matchMiddleware<TEnv>(
|
|
|
288
322
|
continue;
|
|
289
323
|
}
|
|
290
324
|
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
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
|
+
}
|
|
294
335
|
matches.push({ entry, params });
|
|
295
336
|
}
|
|
296
337
|
}
|
|
@@ -298,6 +339,88 @@ export function matchMiddleware<TEnv>(
|
|
|
298
339
|
return matches;
|
|
299
340
|
}
|
|
300
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
|
+
|
|
301
424
|
/**
|
|
302
425
|
* Execute middleware chain
|
|
303
426
|
*
|
|
@@ -306,7 +429,7 @@ export function matchMiddleware<TEnv>(
|
|
|
306
429
|
* - `ctx.headers` available before and after `await next()`
|
|
307
430
|
* - `ctx.header()` shorthand for setting a single header
|
|
308
431
|
* - Forgiving: if middleware doesn't return, uses the downstream response
|
|
309
|
-
* - Short-circuit: return Response to stop chain
|
|
432
|
+
* - Short-circuit: return OR throw a Response to stop chain
|
|
310
433
|
* - Error catching: try/catch around `next()` works
|
|
311
434
|
*/
|
|
312
435
|
export async function executeMiddleware<TEnv>(
|
|
@@ -336,47 +459,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
336
459
|
// End of chain - call actual RSC handler
|
|
337
460
|
const response = await finalHandler();
|
|
338
461
|
|
|
339
|
-
// Merge headers set on stub into the real response.
|
|
340
|
-
// Use append for Set-Cookie to preserve multiple cookies.
|
|
341
|
-
const mergedHeaders = new Headers(response.headers);
|
|
342
|
-
stubResponse.headers.forEach((value, name) => {
|
|
343
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
344
|
-
mergedHeaders.append(name, value);
|
|
345
|
-
} else {
|
|
346
|
-
mergedHeaders.set(name, value);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
// Also merge shared RequestContext stub (cookies written via cookies().set()).
|
|
350
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
351
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
352
|
-
const reqCtx = _getRequestContext();
|
|
353
|
-
if (reqCtx) {
|
|
354
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
355
|
-
if (stubCookies.length > 0) {
|
|
356
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
357
|
-
for (const cookie of stubCookies) {
|
|
358
|
-
if (!existing.has(cookie)) {
|
|
359
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
364
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
365
|
-
mergedHeaders.set(name, value);
|
|
366
|
-
}
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
462
|
if (isWebSocketUpgradeResponse(response)) {
|
|
371
463
|
responseHolder.response = response;
|
|
372
464
|
return response;
|
|
373
465
|
}
|
|
374
466
|
|
|
375
|
-
//
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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,
|
|
380
472
|
});
|
|
381
473
|
|
|
382
474
|
return responseHolder.response;
|
|
@@ -434,12 +526,28 @@ export async function executeMiddleware<TEnv>(
|
|
|
434
526
|
return nextPromise;
|
|
435
527
|
};
|
|
436
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.
|
|
437
535
|
let result: Response | void;
|
|
438
536
|
try {
|
|
439
|
-
result = await
|
|
537
|
+
result = await observePhase(PHASES.middleware(metricLabel), () =>
|
|
538
|
+
entry.handler(ctx, wrappedNext),
|
|
539
|
+
);
|
|
440
540
|
} catch (error) {
|
|
441
|
-
|
|
442
|
-
|
|
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
|
+
}
|
|
443
551
|
}
|
|
444
552
|
finishMiddleware();
|
|
445
553
|
|
|
@@ -460,45 +568,18 @@ export async function executeMiddleware<TEnv>(
|
|
|
460
568
|
|
|
461
569
|
// Explicit return takes precedence (middleware short-circuit).
|
|
462
570
|
// Merge stub headers (from ctx.header before this point) and
|
|
463
|
-
// RequestContext stub headers (from
|
|
571
|
+
// RequestContext stub headers (from cookies().set()) into the
|
|
464
572
|
// returned Response so they are not lost.
|
|
465
573
|
if (result instanceof Response) {
|
|
466
574
|
if (isWebSocketUpgradeResponse(result)) {
|
|
467
575
|
responseHolder.response = result;
|
|
468
576
|
return result;
|
|
469
577
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
mergedHeaders.set(name, value);
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
// Also merge shared RequestContext stub (cookies written via setCookie).
|
|
479
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
480
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
481
|
-
const reqCtx = _getRequestContext();
|
|
482
|
-
if (reqCtx) {
|
|
483
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
484
|
-
if (stubCookies.length > 0) {
|
|
485
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
486
|
-
for (const cookie of stubCookies) {
|
|
487
|
-
if (!existing.has(cookie)) {
|
|
488
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
493
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
494
|
-
mergedHeaders.set(name, value);
|
|
495
|
-
}
|
|
496
|
-
});
|
|
497
|
-
}
|
|
498
|
-
const merged = new Response(result.body, {
|
|
499
|
-
status: result.status,
|
|
500
|
-
statusText: result.statusText,
|
|
501
|
-
headers: mergedHeaders,
|
|
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,
|
|
502
583
|
});
|
|
503
584
|
responseHolder.response = merged;
|
|
504
585
|
return merged;
|
|
@@ -548,21 +629,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
548
629
|
// set-cookie on an upgrade is not meaningful.
|
|
549
630
|
const reqCtx = _getRequestContext();
|
|
550
631
|
if (reqCtx && !isWebSocketUpgradeResponse(finalResponse)) {
|
|
551
|
-
|
|
552
|
-
if (stubCookies.length > 0) {
|
|
553
|
-
const existingCookies = new Set(finalResponse.headers.getSetCookie());
|
|
554
|
-
for (const cookie of stubCookies) {
|
|
555
|
-
if (!existingCookies.has(cookie)) {
|
|
556
|
-
finalResponse.headers.append("set-cookie", cookie);
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
// Fill in non-cookie headers that aren't already on the response
|
|
561
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
562
|
-
if (name !== "set-cookie" && !finalResponse.headers.has(name)) {
|
|
563
|
-
finalResponse.headers.set(name, value);
|
|
564
|
-
}
|
|
565
|
-
});
|
|
632
|
+
mergeReqCtxStub(finalResponse.headers, reqCtx);
|
|
566
633
|
}
|
|
567
634
|
|
|
568
635
|
return finalResponse;
|
|
@@ -573,7 +640,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
573
640
|
*
|
|
574
641
|
* Intercepts use a shared stubResponse from the request context. This function:
|
|
575
642
|
* - Runs middleware in sequence with a simple next() chain
|
|
576
|
-
* - 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())
|
|
577
644
|
* - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
|
|
578
645
|
*
|
|
579
646
|
* @param middlewares - Array of middleware functions
|
|
@@ -611,6 +678,7 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
611
678
|
return stubResponse;
|
|
612
679
|
}
|
|
613
680
|
|
|
681
|
+
const ordinal = index;
|
|
614
682
|
const middleware = middlewares[index++];
|
|
615
683
|
const ctx = createMiddlewareContext(
|
|
616
684
|
request,
|
|
@@ -632,7 +700,29 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
632
700
|
return next();
|
|
633
701
|
};
|
|
634
702
|
|
|
635
|
-
|
|
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
|
+
}
|
|
636
726
|
|
|
637
727
|
if (result instanceof Response) {
|
|
638
728
|
earlyResponse = result;
|
|
@@ -656,21 +746,13 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
656
746
|
});
|
|
657
747
|
|
|
658
748
|
if (hasStubHeaders) {
|
|
659
|
-
//
|
|
660
|
-
//
|
|
661
|
-
//
|
|
662
|
-
|
|
663
|
-
stubResponse.headers
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
} else if (!mergedHeaders.has(name)) {
|
|
667
|
-
mergedHeaders.set(name, value);
|
|
668
|
-
}
|
|
669
|
-
});
|
|
670
|
-
return new Response(response.body, {
|
|
671
|
-
status: response.status,
|
|
672
|
-
statusText: response.statusText,
|
|
673
|
-
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,
|
|
674
756
|
});
|
|
675
757
|
}
|
|
676
758
|
return response;
|
|
@@ -711,7 +793,6 @@ export async function executeLoaderMiddleware<TEnv>(
|
|
|
711
793
|
regex: null,
|
|
712
794
|
paramNames: [],
|
|
713
795
|
handler,
|
|
714
|
-
mountPrefix: null,
|
|
715
796
|
} as MiddlewareEntry<TEnv>,
|
|
716
797
|
params,
|
|
717
798
|
}));
|