@rangojs/router 0.0.0-experimental.97 → 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 +71 -21
- 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/i18n/SKILL.md +276 -0
- 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 -25
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +114 -47
- package/skills/route/SKILL.md +42 -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 +316 -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 +11 -11
- 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 -10
- 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 -40
- 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 -41
- 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 +5 -112
- 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 +62 -67
- 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 -48
- 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 -34
- 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,151 +33,60 @@ 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, Record<string, string> for global middleware)
|
|
57
|
-
*/
|
|
58
36
|
export interface MiddlewareContext<
|
|
59
37
|
TEnv = any,
|
|
60
|
-
TParams = Record<string, string>,
|
|
38
|
+
TParams = Record<string, string | undefined>,
|
|
61
39
|
> extends RequestScope<TEnv> {
|
|
62
|
-
/** URL params extracted from route/middleware pattern */
|
|
63
40
|
params: TParams;
|
|
64
41
|
|
|
65
|
-
/**
|
|
66
|
-
* Response headers.
|
|
67
|
-
* Before `next()`, returns headers from the shared response stub.
|
|
68
|
-
* After `next()`, returns headers from the downstream response.
|
|
69
|
-
*/
|
|
70
42
|
readonly headers: Headers;
|
|
71
43
|
|
|
72
|
-
/** Get a context variable (shared with route handlers) */
|
|
73
44
|
get: GetVariableFn;
|
|
74
45
|
|
|
75
|
-
/** Set a context variable (shared with route handlers) */
|
|
76
46
|
set: SetVariableFn;
|
|
77
47
|
|
|
78
|
-
/**
|
|
79
|
-
* Set a response header - can be called before or after `next()`.
|
|
80
|
-
*
|
|
81
|
-
* When called before `next()`, headers are queued and merged into the final response.
|
|
82
|
-
* When called after `next()`, headers are set directly on the response.
|
|
83
|
-
*/
|
|
84
48
|
header(name: string, value: string): void;
|
|
85
49
|
|
|
86
|
-
/**
|
|
87
|
-
* The matched route name, if available and the route has an explicit name.
|
|
88
|
-
* Undefined for global middleware (runs before route matching) or unnamed routes.
|
|
89
|
-
*/
|
|
90
50
|
routeName?: DefaultRouteName;
|
|
91
51
|
|
|
92
|
-
/**
|
|
93
|
-
* Enable performance metrics for this request.
|
|
94
|
-
* When called, granular timing breakdown is logged to console and
|
|
95
|
-
* included in the Server-Timing response header, regardless of the
|
|
96
|
-
* router-level `debugPerformance` option.
|
|
97
|
-
*
|
|
98
|
-
* Call **before** `await next()` so the metrics store exists when
|
|
99
|
-
* downstream phases (route matching, rendering, SSR) record their
|
|
100
|
-
* spans. Calling after `next()` returns still emits `handler:total`
|
|
101
|
-
* but misses all upstream metrics.
|
|
102
|
-
*/
|
|
103
52
|
debugPerformance(): void;
|
|
104
53
|
|
|
105
|
-
/**
|
|
106
|
-
* Current theme (from cookie or default).
|
|
107
|
-
* Only available when theme is enabled in router config.
|
|
108
|
-
*/
|
|
109
54
|
theme?: Theme;
|
|
110
55
|
|
|
111
|
-
/**
|
|
112
|
-
* Set the theme (only available when theme is enabled in router config).
|
|
113
|
-
* Sets a cookie with the new theme value.
|
|
114
|
-
*/
|
|
115
56
|
setTheme?: (theme: Theme) => void;
|
|
116
57
|
|
|
117
|
-
/**
|
|
118
|
-
* Attach location state entries to this response.
|
|
119
|
-
* State is delivered to the client via history.pushState and accessible
|
|
120
|
-
* through the useLocationState() hook.
|
|
121
|
-
*/
|
|
122
58
|
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
123
59
|
|
|
124
|
-
/**
|
|
125
|
-
* Generate URLs from route names.
|
|
126
|
-
* - `name` — global route, from the named-routes definition
|
|
127
|
-
*/
|
|
128
60
|
reverse: ScopedReverseFunction<
|
|
129
61
|
Record<string, string>,
|
|
130
62
|
DefaultReverseRouteMap
|
|
131
63
|
>;
|
|
132
64
|
}
|
|
133
65
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
* @template TParams - URL params type (typed for route middleware)
|
|
139
|
-
*
|
|
140
|
-
* When using middleware with global augmentation (RSCRouter.Env), explicitly
|
|
141
|
-
* annotate your middleware functions, or the types will be inferred from context:
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```typescript
|
|
145
|
-
* // With explicit annotation (recommended for reusable middleware)
|
|
146
|
-
* const authMiddleware: MiddlewareFn<AppEnv> = async (ctx, next) => {...}
|
|
147
|
-
*
|
|
148
|
-
* // Types inferred from router.use() call
|
|
149
|
-
* router.use((ctx, next) => {...}) // ctx is typed from router's TEnv
|
|
150
|
-
* ```
|
|
151
|
-
*/
|
|
152
|
-
export type MiddlewareFn<TEnv = any, TParams = Record<string, string>> = (
|
|
66
|
+
export type MiddlewareFn<
|
|
67
|
+
TEnv = any,
|
|
68
|
+
TParams = Record<string, string | undefined>,
|
|
69
|
+
> = (
|
|
153
70
|
ctx: MiddlewareContext<TEnv, TParams>,
|
|
154
71
|
next: () => Promise<Response>,
|
|
155
72
|
) => Response | void | Promise<Response | void>;
|
|
156
73
|
|
|
157
|
-
/**
|
|
158
|
-
* Stored middleware entry with pattern matching info
|
|
159
|
-
* @internal - uses any for internal flexibility
|
|
160
|
-
*/
|
|
161
74
|
export interface MiddlewareEntry<TEnv = any> {
|
|
162
|
-
/** Original pattern string */
|
|
163
75
|
pattern: string | null;
|
|
164
|
-
|
|
165
|
-
/** Compiled regex for matching */
|
|
166
76
|
regex: RegExp | null;
|
|
167
|
-
|
|
168
|
-
/** Param names extracted from pattern */
|
|
169
77
|
paramNames: string[];
|
|
170
|
-
|
|
171
|
-
/** The middleware function */
|
|
172
78
|
handler: MiddlewareFn<TEnv>;
|
|
173
|
-
|
|
174
|
-
/** Mount prefix this middleware is scoped to (null = global) */
|
|
175
|
-
mountPrefix: string | null;
|
|
176
79
|
}
|
|
177
80
|
|
|
178
|
-
/**
|
|
179
|
-
* Mutable response holder - tracks the current response through the middleware chain.
|
|
180
|
-
*/
|
|
181
81
|
export interface ResponseHolder {
|
|
182
82
|
response: Response | null;
|
|
183
83
|
}
|
|
184
84
|
|
|
185
|
-
/**
|
|
186
|
-
* Entry type for middleware collection
|
|
187
|
-
* Matches the shape of EntryData used in router.ts
|
|
188
|
-
*/
|
|
189
85
|
export interface MiddlewareCollectableEntry {
|
|
190
86
|
middleware?: MiddlewareFn<any, any>[];
|
|
191
87
|
layout?: MiddlewareCollectableEntry[];
|
|
192
88
|
}
|
|
193
89
|
|
|
194
|
-
/**
|
|
195
|
-
* Collected route middleware with params
|
|
196
|
-
*/
|
|
197
90
|
export interface CollectedMiddleware {
|
|
198
91
|
handler: MiddlewareFn<any, any>;
|
|
199
92
|
params: Record<string, string>;
|
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 {
|