@rangojs/router 0.0.0-experimental.98 → 0.0.0-experimental.98914650
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/dist/bin/rango.js +157 -63
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +1584 -639
- package/package.json +60 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +3 -1
- package/skills/hooks/SKILL.md +235 -28
- package/skills/host-router/SKILL.md +122 -22
- package/skills/intercept/SKILL.md +29 -5
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +173 -17
- package/skills/loader/SKILL.md +170 -23
- package/skills/middleware/SKILL.md +16 -10
- package/skills/migrate-nextjs/SKILL.md +38 -16
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +11 -7
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +250 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +22 -5
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +78 -42
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +310 -26
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +37 -143
- 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 +30 -59
- package/src/browser/navigation-client.ts +96 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +32 -82
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +60 -127
- package/src/browser/prefetch/cache.ts +82 -72
- package/src/browser/prefetch/fetch.ts +108 -33
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +41 -48
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +3 -6
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +20 -5
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +70 -34
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +168 -44
- package/src/browser/types.ts +36 -21
- 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 +3 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +89 -11
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +122 -22
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2255 -238
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +25 -61
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +63 -9
- package/src/index.ts +64 -9
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +9 -0
- package/src/reverse.ts +65 -41
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +244 -281
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +40 -17
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +15 -15
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +4 -42
- package/src/router/intercept-resolution.ts +14 -19
- package/src/router/lazy-includes.ts +9 -46
- package/src/router/loader-resolution.ts +91 -46
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +57 -58
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +150 -271
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +31 -80
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +118 -133
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +20 -58
- package/src/router/prerender-match.ts +99 -63
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +50 -56
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +68 -35
- package/src/router/router-options.ts +55 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +44 -63
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +203 -285
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +87 -47
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +80 -41
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +83 -78
- package/src/rsc/helpers.ts +93 -5
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +12 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +76 -62
- package/src/rsc/rsc-rendering.ts +41 -60
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +62 -67
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +10 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +199 -142
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +150 -51
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +165 -87
- package/src/ssr/index.tsx +14 -14
- 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 +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +97 -22
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +18 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +19 -5
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +20 -19
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +1 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +95 -82
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/prerender-collection.ts +26 -34
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/state.ts +39 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/plugin-types.ts +185 -10
- package/src/vite/plugins/cjs-to-esm.ts +3 -18
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +12 -11
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +4 -75
- package/src/vite/plugins/expose-id-utils.ts +3 -54
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +6 -74
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +57 -67
- package/src/vite/plugins/performance-tracks.ts +9 -16
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +26 -49
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +2 -32
- package/src/vite/plugins/version-plugin.ts +32 -23
- package/src/vite/plugins/virtual-entries.ts +35 -17
- package/src/vite/rango.ts +148 -115
- package/src/vite/router-discovery.ts +220 -68
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +95 -43
- 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/router/middleware-cookies.ts +0 -55
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Middleware Types
|
|
3
|
-
*
|
|
4
|
-
* Type definitions and interfaces for the middleware system.
|
|
5
|
-
* Separated from execution logic for cleaner imports.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import type { ContextVar } from "../context-var.js";
|
|
9
2
|
import type {
|
|
10
3
|
DefaultReverseRouteMap,
|
|
@@ -16,17 +9,11 @@ import type { Theme } from "../theme/types.js";
|
|
|
16
9
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
17
10
|
import type { RequestScope } from "../types/request-scope.js";
|
|
18
11
|
|
|
19
|
-
/**
|
|
20
|
-
* Get variable function type
|
|
21
|
-
*/
|
|
22
12
|
type GetVariableFn = {
|
|
23
13
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
24
14
|
<K extends keyof DefaultVars>(key: K): DefaultVars[K];
|
|
25
15
|
};
|
|
26
16
|
|
|
27
|
-
/**
|
|
28
|
-
* Set variable function type
|
|
29
|
-
*/
|
|
30
17
|
type SetVariableFn = {
|
|
31
18
|
<T>(contextVar: ContextVar<T>, value: T, options?: { cache?: boolean }): void;
|
|
32
19
|
<K extends keyof DefaultVars>(
|
|
@@ -36,9 +23,6 @@ type SetVariableFn = {
|
|
|
36
23
|
): void;
|
|
37
24
|
};
|
|
38
25
|
|
|
39
|
-
/**
|
|
40
|
-
* Cookie options for setting cookies
|
|
41
|
-
*/
|
|
42
26
|
export interface CookieOptions {
|
|
43
27
|
domain?: string;
|
|
44
28
|
path?: string;
|
|
@@ -49,109 +33,36 @@ export interface CookieOptions {
|
|
|
49
33
|
sameSite?: "strict" | "lax" | "none";
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
/**
|
|
53
|
-
* Context passed to middleware
|
|
54
|
-
*
|
|
55
|
-
* @template TEnv - Environment type (bindings, variables) - defaults to any for internal flexibility
|
|
56
|
-
* @template TParams - URL params type (typed for route middleware,
|
|
57
|
-
* `Record<string, string | undefined>` for global middleware — absent
|
|
58
|
-
* optional segments are omitted from the params record at runtime, so
|
|
59
|
-
* the index signature must include `undefined`)
|
|
60
|
-
*/
|
|
61
36
|
export interface MiddlewareContext<
|
|
62
37
|
TEnv = any,
|
|
63
38
|
TParams = Record<string, string | undefined>,
|
|
64
39
|
> extends RequestScope<TEnv> {
|
|
65
|
-
/** URL params extracted from route/middleware pattern */
|
|
66
40
|
params: TParams;
|
|
67
41
|
|
|
68
|
-
/**
|
|
69
|
-
* Response headers.
|
|
70
|
-
* Before `next()`, returns headers from the shared response stub.
|
|
71
|
-
* After `next()`, returns headers from the downstream response.
|
|
72
|
-
*/
|
|
73
42
|
readonly headers: Headers;
|
|
74
43
|
|
|
75
|
-
/** Get a context variable (shared with route handlers) */
|
|
76
44
|
get: GetVariableFn;
|
|
77
45
|
|
|
78
|
-
/** Set a context variable (shared with route handlers) */
|
|
79
46
|
set: SetVariableFn;
|
|
80
47
|
|
|
81
|
-
/**
|
|
82
|
-
* Set a response header - can be called before or after `next()`.
|
|
83
|
-
*
|
|
84
|
-
* When called before `next()`, headers are queued and merged into the final response.
|
|
85
|
-
* When called after `next()`, headers are set directly on the response.
|
|
86
|
-
*/
|
|
87
48
|
header(name: string, value: string): void;
|
|
88
49
|
|
|
89
|
-
/**
|
|
90
|
-
* The matched route name, if available and the route has an explicit name.
|
|
91
|
-
* Undefined for global middleware (runs before route matching) or unnamed routes.
|
|
92
|
-
*/
|
|
93
50
|
routeName?: DefaultRouteName;
|
|
94
51
|
|
|
95
|
-
/**
|
|
96
|
-
* Enable performance metrics for this request.
|
|
97
|
-
* When called, granular timing breakdown is logged to console and
|
|
98
|
-
* included in the Server-Timing response header, regardless of the
|
|
99
|
-
* router-level `debugPerformance` option.
|
|
100
|
-
*
|
|
101
|
-
* Call **before** `await next()` so the metrics store exists when
|
|
102
|
-
* downstream phases (route matching, rendering, SSR) record their
|
|
103
|
-
* spans. Calling after `next()` returns still emits `handler:total`
|
|
104
|
-
* but misses all upstream metrics.
|
|
105
|
-
*/
|
|
106
52
|
debugPerformance(): void;
|
|
107
53
|
|
|
108
|
-
/**
|
|
109
|
-
* Current theme (from cookie or default).
|
|
110
|
-
* Only available when theme is enabled in router config.
|
|
111
|
-
*/
|
|
112
54
|
theme?: Theme;
|
|
113
55
|
|
|
114
|
-
/**
|
|
115
|
-
* Set the theme (only available when theme is enabled in router config).
|
|
116
|
-
* Sets a cookie with the new theme value.
|
|
117
|
-
*/
|
|
118
56
|
setTheme?: (theme: Theme) => void;
|
|
119
57
|
|
|
120
|
-
/**
|
|
121
|
-
* Attach location state entries to this response.
|
|
122
|
-
* State is delivered to the client via history.pushState and accessible
|
|
123
|
-
* through the useLocationState() hook.
|
|
124
|
-
*/
|
|
125
58
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
126
59
|
|
|
127
|
-
/**
|
|
128
|
-
* Generate URLs from route names.
|
|
129
|
-
* - `name` — global route, from the named-routes definition
|
|
130
|
-
*/
|
|
131
60
|
reverse: ScopedReverseFunction<
|
|
132
61
|
Record<string, string>,
|
|
133
62
|
DefaultReverseRouteMap
|
|
134
63
|
>;
|
|
135
64
|
}
|
|
136
65
|
|
|
137
|
-
/**
|
|
138
|
-
* Middleware function signature
|
|
139
|
-
*
|
|
140
|
-
* @template TEnv - Environment type - defaults to any for internal flexibility
|
|
141
|
-
* @template TParams - URL params type (typed for route middleware)
|
|
142
|
-
*
|
|
143
|
-
* When using middleware with global augmentation (RSCRouter.Env), explicitly
|
|
144
|
-
* annotate your middleware functions, or the types will be inferred from context:
|
|
145
|
-
*
|
|
146
|
-
* @example
|
|
147
|
-
* ```typescript
|
|
148
|
-
* // With explicit annotation (recommended for reusable middleware)
|
|
149
|
-
* const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
|
|
150
|
-
*
|
|
151
|
-
* // Types inferred from router.use() call
|
|
152
|
-
* router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
|
|
153
|
-
* ```
|
|
154
|
-
*/
|
|
155
66
|
export type MiddlewareFn<
|
|
156
67
|
TEnv = any,
|
|
157
68
|
TParams = Record<string, string | undefined>,
|
|
@@ -160,50 +71,23 @@ export type MiddlewareFn<
|
|
|
160
71
|
next: () => Promise<Response>,
|
|
161
72
|
) => Response | void | Promise<Response | void>;
|
|
162
73
|
|
|
163
|
-
/**
|
|
164
|
-
* Stored middleware entry with pattern matching info
|
|
165
|
-
* @internal - uses any for internal flexibility
|
|
166
|
-
*/
|
|
167
74
|
export interface MiddlewareEntry<TEnv = any> {
|
|
168
|
-
/** Original pattern string */
|
|
169
75
|
pattern: string | null;
|
|
170
|
-
|
|
171
|
-
/** Compiled regex for matching */
|
|
172
76
|
regex: RegExp | null;
|
|
173
|
-
|
|
174
|
-
/** Param names extracted from pattern */
|
|
175
77
|
paramNames: string[];
|
|
176
|
-
|
|
177
|
-
/** The middleware function */
|
|
178
78
|
handler: MiddlewareFn<TEnv>;
|
|
179
|
-
|
|
180
|
-
/** Mount prefix this middleware is scoped to (null = global) */
|
|
181
|
-
mountPrefix: string | null;
|
|
182
79
|
}
|
|
183
80
|
|
|
184
|
-
/**
|
|
185
|
-
* Mutable response holder - tracks the current response through the middleware chain.
|
|
186
|
-
*/
|
|
187
81
|
export interface ResponseHolder {
|
|
188
82
|
response: Response | null;
|
|
189
83
|
}
|
|
190
84
|
|
|
191
|
-
/**
|
|
192
|
-
* Entry type for middleware collection
|
|
193
|
-
* Matches the shape of EntryData used in router.ts
|
|
194
|
-
*/
|
|
195
85
|
export interface MiddlewareCollectableEntry {
|
|
196
86
|
middleware?: MiddlewareFn<any, any>[];
|
|
197
87
|
layout?: MiddlewareCollectableEntry[];
|
|
198
88
|
}
|
|
199
89
|
|
|
200
|
-
/**
|
|
201
|
-
* Collected route middleware with params
|
|
202
|
-
*/
|
|
203
90
|
export interface CollectedMiddleware {
|
|
204
91
|
handler: MiddlewareFn<any, any>;
|
|
205
|
-
// Internal shape only. The user-facing `MiddlewareContext.params` is
|
|
206
|
-
// typed `Record<string, string | undefined>` to reflect that absent
|
|
207
|
-
// optional segments are omitted from the params record at runtime.
|
|
208
92
|
params: Record<string, string>;
|
|
209
93
|
}
|
package/src/router/middleware.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
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";
|
|
13
4
|
import { safeDecodeURIComponent } from "./url-params.js";
|
|
@@ -21,28 +12,28 @@ import type {
|
|
|
21
12
|
ResponseHolder,
|
|
22
13
|
} from "./middleware-types.js";
|
|
23
14
|
import { _getRequestContext } from "../server/request-context.js";
|
|
15
|
+
import {
|
|
16
|
+
EXTERNAL_REDIRECT_MARKER,
|
|
17
|
+
isExternalRedirect,
|
|
18
|
+
markExternalRedirect,
|
|
19
|
+
} from "../redirect-origin.js";
|
|
24
20
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
25
21
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
26
22
|
import { stripInternalParams } from "./handler-context.js";
|
|
27
23
|
import { isWebSocketUpgradeResponse } from "../response-utils.js";
|
|
28
24
|
|
|
29
|
-
// Re-export types
|
|
25
|
+
// Re-export types consumed through this module's path.
|
|
30
26
|
export type {
|
|
31
27
|
CookieOptions,
|
|
32
|
-
CollectedMiddleware,
|
|
33
|
-
MiddlewareCollectableEntry,
|
|
34
28
|
MiddlewareContext,
|
|
35
29
|
MiddlewareEntry,
|
|
36
30
|
MiddlewareFn,
|
|
37
|
-
ResponseHolder,
|
|
38
31
|
} from "./middleware-types.js";
|
|
39
|
-
export { parseCookies, serializeCookie } from "./middleware-cookies.js";
|
|
40
32
|
|
|
41
33
|
const MIDDLEWARE_METRIC_DEPTH = 1;
|
|
42
|
-
/** Ignore post-next() durations below this threshold (measurement noise). */
|
|
43
34
|
const POST_METRIC_MIN_DURATION_MS = 0.01;
|
|
44
35
|
|
|
45
|
-
function
|
|
36
|
+
function getMiddlewareMetricLabel<TEnv>(
|
|
46
37
|
entry: MiddlewareEntry<TEnv>,
|
|
47
38
|
ordinal: number,
|
|
48
39
|
): string {
|
|
@@ -50,23 +41,12 @@ function getMiddlewareMetricBase<TEnv>(
|
|
|
50
41
|
const scope = entry.pattern ?? "*";
|
|
51
42
|
|
|
52
43
|
if (handlerName) {
|
|
53
|
-
return
|
|
44
|
+
return `middleware:${handlerName}@${scope}`;
|
|
54
45
|
}
|
|
55
46
|
|
|
56
|
-
return
|
|
47
|
+
return `middleware:${scope}#${ordinal + 1}`;
|
|
57
48
|
}
|
|
58
49
|
|
|
59
|
-
function getMiddlewareMetricLabel<TEnv>(
|
|
60
|
-
entry: MiddlewareEntry<TEnv>,
|
|
61
|
-
ordinal: number,
|
|
62
|
-
): string {
|
|
63
|
-
return `middleware:${getMiddlewareMetricBase(entry, ordinal)}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Parse a route pattern into regex and param names
|
|
68
|
-
* Supports: *, /path, /path/*, /path/:param, /path/:param/*
|
|
69
|
-
*/
|
|
70
50
|
export function parsePattern(pattern: string): {
|
|
71
51
|
regex: RegExp;
|
|
72
52
|
paramNames: string[];
|
|
@@ -261,9 +241,13 @@ export function createMiddlewareContext<TEnv>(
|
|
|
261
241
|
|
|
262
242
|
reverse:
|
|
263
243
|
reverse ??
|
|
264
|
-
((
|
|
244
|
+
((
|
|
245
|
+
name: string,
|
|
246
|
+
_params?: Record<string, string>,
|
|
247
|
+
_search?: Record<string, unknown>,
|
|
248
|
+
) => {
|
|
265
249
|
throw new Error(
|
|
266
|
-
`ctx.reverse() is not available
|
|
250
|
+
`ctx.reverse(${JSON.stringify(name)}) is not available: no route map is bound to this middleware context.`,
|
|
267
251
|
);
|
|
268
252
|
}),
|
|
269
253
|
|
|
@@ -307,6 +291,88 @@ export function matchMiddleware<TEnv>(
|
|
|
307
291
|
return matches;
|
|
308
292
|
}
|
|
309
293
|
|
|
294
|
+
// Set-Cookie is appended; for other headers stubOverridesNonCookie=true
|
|
295
|
+
// overwrites (chain ran to completion), false fills only missing slots (an
|
|
296
|
+
// explicit short-circuit Response's own headers win).
|
|
297
|
+
function mergeStubHeaders(
|
|
298
|
+
target: Headers,
|
|
299
|
+
stub: Headers,
|
|
300
|
+
stubOverridesNonCookie: boolean,
|
|
301
|
+
): void {
|
|
302
|
+
stub.forEach((value, name) => {
|
|
303
|
+
// The reserved external-redirect marker is internal and never a trust
|
|
304
|
+
// signal; never copy a stub value (e.g. a stray ctx.header() call) onto a
|
|
305
|
+
// browser-facing response. The opt-in is the out-of-band brand.
|
|
306
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
307
|
+
if (name.toLowerCase() === "set-cookie") {
|
|
308
|
+
target.append(name, value);
|
|
309
|
+
} else if (stubOverridesNonCookie || !target.has(name)) {
|
|
310
|
+
target.set(name, value);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Set-Cookie is deduped so a nested inner executeMiddleware that already merged
|
|
316
|
+
// the same reqCtx cookies does not duplicate them; other headers fill if missing.
|
|
317
|
+
function mergeReqCtxStub(
|
|
318
|
+
target: Headers,
|
|
319
|
+
reqCtx: ReturnType<typeof _getRequestContext>,
|
|
320
|
+
): void {
|
|
321
|
+
if (!reqCtx) return;
|
|
322
|
+
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
323
|
+
if (stubCookies.length > 0) {
|
|
324
|
+
const existing = new Set(target.getSetCookie());
|
|
325
|
+
for (const cookie of stubCookies) {
|
|
326
|
+
if (!existing.has(cookie)) {
|
|
327
|
+
target.append("set-cookie", cookie);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
reqCtx.res.headers.forEach((value, name) => {
|
|
332
|
+
// Never propagate the reserved external-redirect marker (see mergeStubHeaders).
|
|
333
|
+
if (name.toLowerCase() === EXTERNAL_REDIRECT_MARKER) return;
|
|
334
|
+
if (name !== "set-cookie" && !target.has(name)) {
|
|
335
|
+
target.set(name, value);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Clone `base` with stub headers merged into a fresh Headers (the clone keeps
|
|
341
|
+
// the body mutable for post-next() modifications). Set-Cookie is always
|
|
342
|
+
// appended; other headers obey stubOverridesNonCookie (see mergeStubHeaders).
|
|
343
|
+
// mergeReqCtx folds in RequestContext stub cookies/headers; the intercept
|
|
344
|
+
// short-circuit path passes false (its reqCtx headers are not merged here),
|
|
345
|
+
// which is the one deliberate divergence between the call sites.
|
|
346
|
+
function mergeResponse(
|
|
347
|
+
base: Response,
|
|
348
|
+
stub: Headers,
|
|
349
|
+
opts: { stubOverridesNonCookie: boolean; mergeReqCtx: boolean },
|
|
350
|
+
): Response {
|
|
351
|
+
const mergedHeaders = new Headers(base.headers);
|
|
352
|
+
// The reserved external-redirect marker is never a trust signal and must never
|
|
353
|
+
// reach the browser. The guard strips it on 3xx redirects; strip it here too so
|
|
354
|
+
// a forged value cannot ride a non-3xx middleware response (which the 3xx-only
|
|
355
|
+
// guard would not touch) to the client. The opt-in is the out-of-band brand.
|
|
356
|
+
mergedHeaders.delete(EXTERNAL_REDIRECT_MARKER);
|
|
357
|
+
mergeStubHeaders(mergedHeaders, stub, opts.stubOverridesNonCookie);
|
|
358
|
+
if (opts.mergeReqCtx) {
|
|
359
|
+
mergeReqCtxStub(mergedHeaders, _getRequestContext());
|
|
360
|
+
}
|
|
361
|
+
const merged = new Response(base.body, {
|
|
362
|
+
status: base.status,
|
|
363
|
+
statusText: base.statusText,
|
|
364
|
+
headers: mergedHeaders,
|
|
365
|
+
});
|
|
366
|
+
// Transfer the out-of-band external-redirect brand across this rebuild: a
|
|
367
|
+
// middleware short-circuit `return redirect(url, { external: true })` reaches
|
|
368
|
+
// the open-redirect guard only after this merge, and the brand lives on the
|
|
369
|
+
// Response object, not in its headers.
|
|
370
|
+
if (isExternalRedirect(base)) {
|
|
371
|
+
markExternalRedirect(merged);
|
|
372
|
+
}
|
|
373
|
+
return merged;
|
|
374
|
+
}
|
|
375
|
+
|
|
310
376
|
/**
|
|
311
377
|
* Execute middleware chain
|
|
312
378
|
*
|
|
@@ -315,7 +381,7 @@ export function matchMiddleware<TEnv>(
|
|
|
315
381
|
* - `ctx.headers` available before and after `await next()`
|
|
316
382
|
* - `ctx.header()` shorthand for setting a single header
|
|
317
383
|
* - Forgiving: if middleware doesn't return, uses the downstream response
|
|
318
|
-
* - Short-circuit: return Response to stop chain
|
|
384
|
+
* - Short-circuit: return OR throw a Response to stop chain
|
|
319
385
|
* - Error catching: try/catch around `next()` works
|
|
320
386
|
*/
|
|
321
387
|
export async function executeMiddleware<TEnv>(
|
|
@@ -345,47 +411,16 @@ export async function executeMiddleware<TEnv>(
|
|
|
345
411
|
// End of chain - call actual RSC handler
|
|
346
412
|
const response = await finalHandler();
|
|
347
413
|
|
|
348
|
-
// Merge headers set on stub into the real response.
|
|
349
|
-
// Use append for Set-Cookie to preserve multiple cookies.
|
|
350
|
-
const mergedHeaders = new Headers(response.headers);
|
|
351
|
-
stubResponse.headers.forEach((value, name) => {
|
|
352
|
-
if (name.toLowerCase() === "set-cookie") {
|
|
353
|
-
mergedHeaders.append(name, value);
|
|
354
|
-
} else {
|
|
355
|
-
mergedHeaders.set(name, value);
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
// Also merge shared RequestContext stub (cookies written via cookies().set()).
|
|
359
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
360
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
361
|
-
const reqCtx = _getRequestContext();
|
|
362
|
-
if (reqCtx) {
|
|
363
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
364
|
-
if (stubCookies.length > 0) {
|
|
365
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
366
|
-
for (const cookie of stubCookies) {
|
|
367
|
-
if (!existing.has(cookie)) {
|
|
368
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
373
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
374
|
-
mergedHeaders.set(name, value);
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
}
|
|
378
|
-
|
|
379
414
|
if (isWebSocketUpgradeResponse(response)) {
|
|
380
415
|
responseHolder.response = response;
|
|
381
416
|
return response;
|
|
382
417
|
}
|
|
383
418
|
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
419
|
+
// Chain ran to completion: stub headers overwrite (stubOverridesNonCookie)
|
|
420
|
+
// and reqCtx stub headers are merged in.
|
|
421
|
+
responseHolder.response = mergeResponse(response, stubResponse.headers, {
|
|
422
|
+
stubOverridesNonCookie: true,
|
|
423
|
+
mergeReqCtx: true,
|
|
389
424
|
});
|
|
390
425
|
|
|
391
426
|
return responseHolder.response;
|
|
@@ -477,45 +512,18 @@ export async function executeMiddleware<TEnv>(
|
|
|
477
512
|
|
|
478
513
|
// Explicit return takes precedence (middleware short-circuit).
|
|
479
514
|
// Merge stub headers (from ctx.header before this point) and
|
|
480
|
-
// RequestContext stub headers (from
|
|
515
|
+
// RequestContext stub headers (from cookies().set()) into the
|
|
481
516
|
// returned Response so they are not lost.
|
|
482
517
|
if (result instanceof Response) {
|
|
483
518
|
if (isWebSocketUpgradeResponse(result)) {
|
|
484
519
|
responseHolder.response = result;
|
|
485
520
|
return result;
|
|
486
521
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
mergedHeaders.set(name, value);
|
|
493
|
-
}
|
|
494
|
-
});
|
|
495
|
-
// Also merge shared RequestContext stub (cookies written via setCookie).
|
|
496
|
-
// Dedup Set-Cookie: an inner executeMiddleware (route-level middleware)
|
|
497
|
-
// may have already merged the same reqCtx cookies into the response.
|
|
498
|
-
const reqCtx = _getRequestContext();
|
|
499
|
-
if (reqCtx) {
|
|
500
|
-
const stubCookies = reqCtx.res.headers.getSetCookie();
|
|
501
|
-
if (stubCookies.length > 0) {
|
|
502
|
-
const existing = new Set(mergedHeaders.getSetCookie());
|
|
503
|
-
for (const cookie of stubCookies) {
|
|
504
|
-
if (!existing.has(cookie)) {
|
|
505
|
-
mergedHeaders.append("set-cookie", cookie);
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
510
|
-
if (name !== "set-cookie" && !mergedHeaders.has(name)) {
|
|
511
|
-
mergedHeaders.set(name, value);
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
const merged = new Response(result.body, {
|
|
516
|
-
status: result.status,
|
|
517
|
-
statusText: result.statusText,
|
|
518
|
-
headers: mergedHeaders,
|
|
522
|
+
// Explicit short-circuit: the returned Response's own headers win
|
|
523
|
+
// (stubOverridesNonCookie=false); reqCtx stub headers still merge in.
|
|
524
|
+
const merged = mergeResponse(result, stubResponse.headers, {
|
|
525
|
+
stubOverridesNonCookie: false,
|
|
526
|
+
mergeReqCtx: true,
|
|
519
527
|
});
|
|
520
528
|
responseHolder.response = merged;
|
|
521
529
|
return merged;
|
|
@@ -565,21 +573,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
565
573
|
// set-cookie on an upgrade is not meaningful.
|
|
566
574
|
const reqCtx = _getRequestContext();
|
|
567
575
|
if (reqCtx && !isWebSocketUpgradeResponse(finalResponse)) {
|
|
568
|
-
|
|
569
|
-
if (stubCookies.length > 0) {
|
|
570
|
-
const existingCookies = new Set(finalResponse.headers.getSetCookie());
|
|
571
|
-
for (const cookie of stubCookies) {
|
|
572
|
-
if (!existingCookies.has(cookie)) {
|
|
573
|
-
finalResponse.headers.append("set-cookie", cookie);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
// Fill in non-cookie headers that aren't already on the response
|
|
578
|
-
reqCtx.res.headers.forEach((value, name) => {
|
|
579
|
-
if (name !== "set-cookie" && !finalResponse.headers.has(name)) {
|
|
580
|
-
finalResponse.headers.set(name, value);
|
|
581
|
-
}
|
|
582
|
-
});
|
|
576
|
+
mergeReqCtxStub(finalResponse.headers, reqCtx);
|
|
583
577
|
}
|
|
584
578
|
|
|
585
579
|
return finalResponse;
|
|
@@ -590,7 +584,7 @@ export async function executeMiddleware<TEnv>(
|
|
|
590
584
|
*
|
|
591
585
|
* Intercepts use a shared stubResponse from the request context. This function:
|
|
592
586
|
* - Runs middleware in sequence with a simple next() chain
|
|
593
|
-
* - Returns Response if any middleware short-circuits (returns Response or redirects BEFORE next())
|
|
587
|
+
* - Returns Response if any middleware short-circuits (returns OR throws a Response, or redirects, BEFORE next())
|
|
594
588
|
* - Returns null if all middleware calls next() - headers set after next() remain on stubResponse
|
|
595
589
|
*
|
|
596
590
|
* @param middlewares - Array of middleware functions
|
|
@@ -684,21 +678,13 @@ export async function executeInterceptMiddleware<TEnv>(
|
|
|
684
678
|
});
|
|
685
679
|
|
|
686
680
|
if (hasStubHeaders) {
|
|
687
|
-
//
|
|
688
|
-
//
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
stubResponse.headers
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
} else if (!mergedHeaders.has(name)) {
|
|
695
|
-
mergedHeaders.set(name, value);
|
|
696
|
-
}
|
|
697
|
-
});
|
|
698
|
-
return new Response(response.body, {
|
|
699
|
-
status: response.status,
|
|
700
|
-
statusText: response.statusText,
|
|
701
|
-
headers: mergedHeaders,
|
|
681
|
+
// Only fill in missing headers — the returned Response's explicit headers
|
|
682
|
+
// take precedence (stubOverridesNonCookie=false), matching executeMiddleware.
|
|
683
|
+
// mergeReqCtx=false: the intercept path deliberately does NOT merge reqCtx
|
|
684
|
+
// stub headers here (pinned by intercept-middleware-headers.test.ts).
|
|
685
|
+
return mergeResponse(response, stubResponse.headers, {
|
|
686
|
+
stubOverridesNonCookie: false,
|
|
687
|
+
mergeReqCtx: false,
|
|
702
688
|
});
|
|
703
689
|
}
|
|
704
690
|
return response;
|
|
@@ -739,7 +725,6 @@ export async function executeLoaderMiddleware<TEnv>(
|
|
|
739
725
|
regex: null,
|
|
740
726
|
paramNames: [],
|
|
741
727
|
handler,
|
|
742
|
-
mountPrefix: null,
|
|
743
728
|
} as MiddlewareEntry<TEnv>,
|
|
744
729
|
params,
|
|
745
730
|
}));
|
|
@@ -1,58 +1,26 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Navigation Snapshot
|
|
3
|
-
*
|
|
4
|
-
* Pure data type representing the navigation-specific state for partial requests.
|
|
5
|
-
* Consolidates the header parsing, previous-route matching, intercept-context
|
|
6
|
-
* detection, and segment ID filtering that previously lived inline in
|
|
7
|
-
* createMatchContextForPartial (match-api.ts).
|
|
8
|
-
*
|
|
9
|
-
* resolveNavigation() is the factory: given a request + URL + current route key,
|
|
10
|
-
* it returns a NavigationSnapshot (or null if no previous URL).
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
1
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
14
2
|
|
|
15
|
-
/**
|
|
16
|
-
* Snapshot of navigation state for a partial (navigation/action) request.
|
|
17
|
-
*
|
|
18
|
-
* Contains the "where are we coming from?" data: previous route, intercept
|
|
19
|
-
* source, client segment state, and derived flags.
|
|
20
|
-
*/
|
|
21
3
|
export interface NavigationSnapshot {
|
|
22
|
-
/** Previous page URL (from X-RSC-Router-Client-Path or Referer) */
|
|
23
4
|
prevUrl: URL;
|
|
24
|
-
/** Params from the previous route match */
|
|
25
5
|
prevParams: Record<string, string>;
|
|
26
|
-
/** Previous route match result (null if prev URL doesn't match any route) */
|
|
27
6
|
prevMatch: RouteMatchResult | null;
|
|
28
7
|
|
|
29
|
-
/** URL used as intercept context source */
|
|
30
8
|
interceptContextUrl: URL;
|
|
31
|
-
/** Route match for the intercept context URL */
|
|
32
9
|
interceptContextMatch: RouteMatchResult | null;
|
|
33
10
|
|
|
34
|
-
/** Raw segment IDs the client currently has */
|
|
35
11
|
clientSegmentIds: string[];
|
|
36
|
-
/** Set version for O(1) lookup */
|
|
37
12
|
clientSegmentSet: Set<string>;
|
|
38
|
-
/** Segment IDs filtered to remove parallel (.@) and loader (D\d+.) entries */
|
|
39
13
|
filteredSegmentIds: string[];
|
|
40
14
|
|
|
41
|
-
/** Whether client considers its cache stale */
|
|
42
15
|
stale: boolean;
|
|
43
16
|
|
|
44
|
-
/** Whether the intercept context route is the same as the current route */
|
|
45
17
|
isSameRouteNavigation: boolean;
|
|
46
18
|
|
|
47
|
-
/** Effective "from" URL (intercept source URL when present, else prevUrl) */
|
|
48
19
|
effectiveFromUrl: URL;
|
|
49
|
-
/** Effective "from" match (intercept source match when present, else prevMatch) */
|
|
50
20
|
effectiveFromMatch: RouteMatchResult | null;
|
|
51
21
|
|
|
52
|
-
/** Whether an intercept source header was present */
|
|
53
22
|
hasInterceptSource: boolean;
|
|
54
23
|
|
|
55
|
-
/** Whether an HMR request header was present */
|
|
56
24
|
isHmr: boolean;
|
|
57
25
|
}
|
|
58
26
|
|
|
@@ -60,23 +28,12 @@ export interface ResolveNavigationDeps {
|
|
|
60
28
|
findMatch: (pathname: string) => RouteMatchResult | null;
|
|
61
29
|
}
|
|
62
30
|
|
|
63
|
-
/**
|
|
64
|
-
* Resolve navigation state from a partial request.
|
|
65
|
-
*
|
|
66
|
-
* Returns null if no previous URL is available (required for partial navigation).
|
|
67
|
-
*
|
|
68
|
-
* @param request - The incoming HTTP request
|
|
69
|
-
* @param url - Parsed URL of the request
|
|
70
|
-
* @param currentRouteKey - Route key of the current (target) route match
|
|
71
|
-
* @param deps - Dependencies (findMatch)
|
|
72
|
-
*/
|
|
73
31
|
export function resolveNavigation(
|
|
74
32
|
request: Request,
|
|
75
33
|
url: URL,
|
|
76
34
|
currentRouteKey: string,
|
|
77
35
|
deps: ResolveNavigationDeps,
|
|
78
36
|
): NavigationSnapshot | null {
|
|
79
|
-
// Parse client state from RSC request params/headers
|
|
80
37
|
const clientSegmentIds =
|
|
81
38
|
url.searchParams.get("_rsc_segments")?.split(",").filter(Boolean) || [];
|
|
82
39
|
const stale = url.searchParams.get("_rsc_stale") === "true";
|
|
@@ -92,7 +49,6 @@ export function resolveNavigation(
|
|
|
92
49
|
return null;
|
|
93
50
|
}
|
|
94
51
|
|
|
95
|
-
// Parse previous URL
|
|
96
52
|
let prevUrl: URL;
|
|
97
53
|
try {
|
|
98
54
|
prevUrl = new URL(previousUrl, url.origin);
|
|
@@ -100,7 +56,6 @@ export function resolveNavigation(
|
|
|
100
56
|
return null;
|
|
101
57
|
}
|
|
102
58
|
|
|
103
|
-
// Parse intercept context URL
|
|
104
59
|
let interceptContextUrl: URL;
|
|
105
60
|
try {
|
|
106
61
|
interceptContextUrl = interceptSourceUrl
|
|
@@ -110,14 +65,12 @@ export function resolveNavigation(
|
|
|
110
65
|
interceptContextUrl = prevUrl;
|
|
111
66
|
}
|
|
112
67
|
|
|
113
|
-
// Match previous and intercept context routes
|
|
114
68
|
const prevMatch = deps.findMatch(prevUrl.pathname);
|
|
115
69
|
const prevParams = prevMatch?.params || {};
|
|
116
70
|
const interceptContextMatch = interceptSourceUrl
|
|
117
71
|
? deps.findMatch(interceptContextUrl.pathname)
|
|
118
72
|
: prevMatch;
|
|
119
73
|
|
|
120
|
-
// Derived state
|
|
121
74
|
const isSameRouteNavigation = !!(
|
|
122
75
|
interceptContextMatch && interceptContextMatch.routeKey === currentRouteKey
|
|
123
76
|
);
|
|
@@ -128,7 +81,6 @@ export function resolveNavigation(
|
|
|
128
81
|
? interceptContextMatch
|
|
129
82
|
: prevMatch;
|
|
130
83
|
|
|
131
|
-
// Filter segment IDs: remove parallel (.@) and loader (D\d+.) entries
|
|
132
84
|
const filteredSegmentIds = clientSegmentIds.filter((id) => {
|
|
133
85
|
if (id.includes(".@")) return false;
|
|
134
86
|
if (/D\d+\./.test(id)) return false;
|
|
@@ -155,9 +107,6 @@ export function resolveNavigation(
|
|
|
155
107
|
};
|
|
156
108
|
}
|
|
157
109
|
|
|
158
|
-
/**
|
|
159
|
-
* Test helper: create a NavigationSnapshot with sensible defaults and overrides.
|
|
160
|
-
*/
|
|
161
110
|
export function createNavigationSnapshot(
|
|
162
111
|
overrides?: Partial<NavigationSnapshot>,
|
|
163
112
|
): NavigationSnapshot {
|