@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
|
@@ -15,10 +15,6 @@ export interface TrieMatchResult {
|
|
|
15
15
|
sp: string;
|
|
16
16
|
/** Matched route params */
|
|
17
17
|
params: Record<string, string>;
|
|
18
|
-
/** Optional param names (absent params have empty string value) */
|
|
19
|
-
optionalParams?: string[];
|
|
20
|
-
/** Ancestry shortCodes for layout pruning */
|
|
21
|
-
ancestry: string[];
|
|
22
18
|
/** Redirect target if trailing slash requires it */
|
|
23
19
|
redirectTo?: string;
|
|
24
20
|
/** Route has pre-rendered data available */
|
|
@@ -43,14 +39,12 @@ export function tryTrieMatch(
|
|
|
43
39
|
): TrieMatchResult | null {
|
|
44
40
|
if (!trie) return null;
|
|
45
41
|
|
|
46
|
-
// Split pathname into segments, filtering empty strings from leading/trailing slashes
|
|
47
42
|
const pathnameHasTrailingSlash =
|
|
48
43
|
pathname.length > 1 && pathname.endsWith("/");
|
|
49
44
|
const normalizedPath = pathnameHasTrailingSlash
|
|
50
45
|
? pathname.slice(0, -1)
|
|
51
46
|
: pathname;
|
|
52
47
|
|
|
53
|
-
// Handle root path
|
|
54
48
|
if (normalizedPath === "" || normalizedPath === "/") {
|
|
55
49
|
if (trie.r) {
|
|
56
50
|
return validateAndBuild(
|
|
@@ -61,13 +55,24 @@ export function tryTrieMatch(
|
|
|
61
55
|
pathnameHasTrailingSlash,
|
|
62
56
|
);
|
|
63
57
|
}
|
|
58
|
+
// A root-level wildcard ("/*") matches "/" with an empty remainder, the
|
|
59
|
+
// same value the regex matcher produces for the bare prefix. Without this
|
|
60
|
+
// the trie misses, the regex fallback runs, and its no-config branch emits
|
|
61
|
+
// a corrupt slice-off redirect. The static terminal still wins above.
|
|
62
|
+
if (trie.w) {
|
|
63
|
+
return validateAndBuild(
|
|
64
|
+
trie.w,
|
|
65
|
+
[],
|
|
66
|
+
"",
|
|
67
|
+
pathname,
|
|
68
|
+
pathnameHasTrailingSlash,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
64
71
|
return null;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
// Remove leading slash and split
|
|
68
74
|
const segments = normalizedPath.slice(1).split("/");
|
|
69
75
|
|
|
70
|
-
// Try exact match with normalized path (no trailing slash)
|
|
71
76
|
const result = walkTrie(trie, segments, 0, []);
|
|
72
77
|
if (result) {
|
|
73
78
|
return validateAndBuild(
|
|
@@ -89,8 +94,58 @@ interface WalkResult {
|
|
|
89
94
|
}
|
|
90
95
|
|
|
91
96
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
97
|
+
* Check a leaf's constraints (leaf.cv) against already-resolved named params.
|
|
98
|
+
* Empty/undefined values are exempt (optional params that were not bound).
|
|
99
|
+
*/
|
|
100
|
+
function constraintsSatisfied(
|
|
101
|
+
leaf: TrieLeaf,
|
|
102
|
+
params: Record<string, string>,
|
|
103
|
+
): boolean {
|
|
104
|
+
if (!leaf.cv) return true;
|
|
105
|
+
for (const paramName in leaf.cv) {
|
|
106
|
+
const allowed = leaf.cv[paramName]!;
|
|
107
|
+
const value = params[paramName];
|
|
108
|
+
if (value !== undefined && value !== "" && !allowed.includes(value)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Constraint check for a candidate terminal DURING the walk. Builds the named
|
|
117
|
+
* params from positional walk values (decoded the same way validateAndBuild
|
|
118
|
+
* does) and validates leaf.cv. Returning false lets walkTrie unwind to a
|
|
119
|
+
* lower-priority sibling instead of committing to a leaf that would only be
|
|
120
|
+
* rejected post-walk — that post-walk rejection is what forced the regex
|
|
121
|
+
* fallback (and its false "trie gap" R3 warning) for perfectly valid configs.
|
|
122
|
+
*/
|
|
123
|
+
function leafConstraintsPass(
|
|
124
|
+
leaf: TrieLeaf,
|
|
125
|
+
paramValues: string[],
|
|
126
|
+
wildcardValue: string | undefined,
|
|
127
|
+
): boolean {
|
|
128
|
+
if (!leaf.cv) return true;
|
|
129
|
+
const params: Record<string, string> = {};
|
|
130
|
+
if (leaf.pa) {
|
|
131
|
+
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
132
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
136
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
137
|
+
safeDecodeURIComponent(wildcardValue);
|
|
138
|
+
}
|
|
139
|
+
return constraintsSatisfied(leaf, params);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Walk the trie by segments with priority: static > suffix-param > param >
|
|
144
|
+
* wildcard (Priority 1-4 below; matches the canonical M4 ordering in
|
|
145
|
+
* docs/internal/matching-and-lazy-discovery.md).
|
|
146
|
+
* Uses backtracking to try all possible matches. Per-leaf constraints are
|
|
147
|
+
* enforced at each candidate terminal so a constraint miss backtracks to a
|
|
148
|
+
* lower-priority sibling rather than aborting the whole match.
|
|
94
149
|
*/
|
|
95
150
|
function walkTrie(
|
|
96
151
|
node: TrieNode,
|
|
@@ -98,25 +153,34 @@ function walkTrie(
|
|
|
98
153
|
index: number,
|
|
99
154
|
paramValues: string[],
|
|
100
155
|
): WalkResult | null {
|
|
101
|
-
// All segments consumed: check for terminal
|
|
102
156
|
if (index === segments.length) {
|
|
103
|
-
if (node.r) {
|
|
157
|
+
if (node.r && leafConstraintsPass(node.r, paramValues, undefined)) {
|
|
104
158
|
return { leaf: node.r, paramValues: [...paramValues] };
|
|
105
159
|
}
|
|
160
|
+
// A wildcard at this node matches the bare prefix with an empty remainder
|
|
161
|
+
// (e.g. "/files" against "/files/*"), mirroring the regex matcher's `*=""`.
|
|
162
|
+
// walkTrie otherwise only reaches node.w in the index<length branch below,
|
|
163
|
+
// so without this a request to the wildcard's own prefix misses the trie
|
|
164
|
+
// and the regex fallback emits a corrupt redirect. A static terminal
|
|
165
|
+
// (node.r) still wins.
|
|
166
|
+
if (node.w && leafConstraintsPass(node.w, paramValues, "")) {
|
|
167
|
+
return { leaf: node.w, paramValues: [...paramValues], wildcardValue: "" };
|
|
168
|
+
}
|
|
106
169
|
return null;
|
|
107
170
|
}
|
|
108
171
|
|
|
109
172
|
const segment = segments[index];
|
|
110
173
|
const staticChild = node.s?.[segment];
|
|
111
174
|
|
|
112
|
-
// Priority 1: Static match
|
|
113
175
|
if (staticChild) {
|
|
114
176
|
const result = walkTrie(staticChild, segments, index + 1, paramValues);
|
|
115
177
|
if (result) return result;
|
|
116
178
|
}
|
|
117
179
|
|
|
118
|
-
// Priority 2: Suffix-param match (e.g., :productId.html)
|
|
119
180
|
if (node.xp) {
|
|
181
|
+
// node.xp keys are pre-sorted longest-suffix-first at build time
|
|
182
|
+
// (route-trie.ts sortSuffixParams), so the first match is the most specific
|
|
183
|
+
// suffix: `/app.min.js` matches `:file.min.js` before `:file.js`.
|
|
120
184
|
for (const suffix in node.xp) {
|
|
121
185
|
if (segment.endsWith(suffix) && segment.length > suffix.length) {
|
|
122
186
|
const paramValue = segment.slice(0, -suffix.length);
|
|
@@ -133,7 +197,6 @@ function walkTrie(
|
|
|
133
197
|
}
|
|
134
198
|
}
|
|
135
199
|
|
|
136
|
-
// Priority 3: Param match
|
|
137
200
|
if (node.p) {
|
|
138
201
|
paramValues.push(segment);
|
|
139
202
|
const result = walkTrie(node.p.c, segments, index + 1, paramValues);
|
|
@@ -141,14 +204,15 @@ function walkTrie(
|
|
|
141
204
|
if (result) return result;
|
|
142
205
|
}
|
|
143
206
|
|
|
144
|
-
// Priority 4: Wildcard match (consumes rest)
|
|
145
207
|
if (node.w) {
|
|
146
208
|
const rest = joinRemainingSegments(segments, index);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
209
|
+
if (leafConstraintsPass(node.w, paramValues, rest)) {
|
|
210
|
+
return {
|
|
211
|
+
leaf: node.w,
|
|
212
|
+
paramValues: [...paramValues],
|
|
213
|
+
wildcardValue: rest,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
152
216
|
}
|
|
153
217
|
|
|
154
218
|
return null;
|
|
@@ -174,10 +238,6 @@ function validateAndBuild(
|
|
|
174
238
|
originalPathname: string,
|
|
175
239
|
pathnameHasTrailingSlash: boolean,
|
|
176
240
|
): TrieMatchResult | null {
|
|
177
|
-
// Build named params by zipping leaf.pa with positional paramValues.
|
|
178
|
-
// Params are URL-decoded at this boundary so ctx.params holds the values
|
|
179
|
-
// apps expect (matching Express/React Router) and round-trip cleanly
|
|
180
|
-
// through ctx.reverse.
|
|
181
241
|
const params: Record<string, string> = {};
|
|
182
242
|
if (leaf.pa) {
|
|
183
243
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
@@ -185,34 +245,15 @@ function validateAndBuild(
|
|
|
185
245
|
}
|
|
186
246
|
}
|
|
187
247
|
|
|
188
|
-
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
189
248
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
190
249
|
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
191
250
|
safeDecodeURIComponent(wildcardValue);
|
|
192
251
|
}
|
|
193
252
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (leaf.cv) {
|
|
197
|
-
for (const paramName in leaf.cv) {
|
|
198
|
-
const allowed = leaf.cv[paramName]!;
|
|
199
|
-
const value = params[paramName];
|
|
200
|
-
if (value !== undefined && value !== "" && !allowed.includes(value)) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Fill in empty strings for optional params that weren't matched
|
|
207
|
-
if (leaf.op) {
|
|
208
|
-
for (const name of leaf.op) {
|
|
209
|
-
if (!(name in params)) {
|
|
210
|
-
params[name] = "";
|
|
211
|
-
}
|
|
212
|
-
}
|
|
253
|
+
if (!constraintsSatisfied(leaf, params)) {
|
|
254
|
+
return null;
|
|
213
255
|
}
|
|
214
256
|
|
|
215
|
-
// Trailing slash handling
|
|
216
257
|
const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
|
|
217
258
|
let redirectTo: string | undefined;
|
|
218
259
|
|
|
@@ -230,10 +271,8 @@ function validateAndBuild(
|
|
|
230
271
|
routeKey: leaf.n,
|
|
231
272
|
sp: leaf.sp,
|
|
232
273
|
params,
|
|
233
|
-
ancestry: leaf.a,
|
|
234
274
|
};
|
|
235
275
|
|
|
236
|
-
if (leaf.op) result.optionalParams = leaf.op;
|
|
237
276
|
if (redirectTo) result.redirectTo = redirectTo;
|
|
238
277
|
if (leaf.pr) result.pr = true;
|
|
239
278
|
if (leaf.pt) result.pt = true;
|
package/src/router/types.ts
CHANGED
|
@@ -22,27 +22,11 @@ import type {
|
|
|
22
22
|
ShouldRevalidateFn,
|
|
23
23
|
} from "../types";
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* Result of resolving loaders with revalidation
|
|
27
|
-
* Contains both segments to render and all matched segment IDs
|
|
28
|
-
*/
|
|
29
|
-
export interface LoaderRevalidationResult {
|
|
30
|
-
segments: ResolvedSegment[];
|
|
31
|
-
matchedIds: string[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Result of resolving segments with revalidation
|
|
36
|
-
* Contains both segments to render and all matched segment IDs
|
|
37
|
-
*/
|
|
38
25
|
export interface SegmentRevalidationResult {
|
|
39
26
|
segments: ResolvedSegment[];
|
|
40
27
|
matchedIds: string[];
|
|
41
28
|
}
|
|
42
29
|
|
|
43
|
-
/**
|
|
44
|
-
* Action context type for revalidation
|
|
45
|
-
*/
|
|
46
30
|
export type ActionContext = {
|
|
47
31
|
actionId?: string;
|
|
48
32
|
actionUrl?: URL;
|
|
@@ -50,23 +34,6 @@ export type ActionContext = {
|
|
|
50
34
|
formData?: FormData;
|
|
51
35
|
};
|
|
52
36
|
|
|
53
|
-
/**
|
|
54
|
-
* Dependencies passed to segment resolution functions
|
|
55
|
-
* These are created within createRouter and passed to extracted utilities
|
|
56
|
-
*/
|
|
57
|
-
export interface RouterDependencies<TEnv> {
|
|
58
|
-
findNearestErrorBoundary: (
|
|
59
|
-
entry: EntryData | null,
|
|
60
|
-
) => ReactNode | ErrorBoundaryHandler | null;
|
|
61
|
-
findNearestNotFoundBoundary: (
|
|
62
|
-
entry: EntryData | null,
|
|
63
|
-
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Dependencies injected from createRouter closure into extracted segment resolution functions.
|
|
68
|
-
* These are the closure-bound helpers that cannot be imported directly.
|
|
69
|
-
*/
|
|
70
37
|
export interface SegmentResolutionDeps<TEnv = any> {
|
|
71
38
|
wrapLoaderPromise: <T>(
|
|
72
39
|
promise: Promise<T>,
|
|
@@ -98,23 +65,16 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
98
65
|
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
99
66
|
notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
100
67
|
callOnError: (error: unknown, phase: ErrorPhase, context: any) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Router-level default for the per-segment `transition({ viewTransition })`
|
|
70
|
+
* flag, from createRouter({ viewTransition }). Resolved into each segment's
|
|
71
|
+
* transition config during resolution (only `false` is stamped) so the render
|
|
72
|
+
* gate reads the boundary decision off the segment on both server and client.
|
|
73
|
+
* Undefined is treated as "auto" (wrap).
|
|
74
|
+
*/
|
|
75
|
+
viewTransitionDefault?: "auto" | false;
|
|
101
76
|
}
|
|
102
77
|
|
|
103
|
-
/**
|
|
104
|
-
* Dependencies injected from createRouter closure into extracted intercept resolution functions.
|
|
105
|
-
*/
|
|
106
|
-
export interface InterceptResolutionDeps<TEnv = any> {
|
|
107
|
-
wrapLoaderPromise: SegmentResolutionDeps<TEnv>["wrapLoaderPromise"];
|
|
108
|
-
evaluateInterceptWhen: (
|
|
109
|
-
intercept: InterceptEntry,
|
|
110
|
-
selectorContext: InterceptSelectorContext | null,
|
|
111
|
-
isAction: boolean,
|
|
112
|
-
) => boolean;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Dependencies injected from createRouter closure into extracted match API functions.
|
|
117
|
-
*/
|
|
118
78
|
export interface MatchApiDeps<TEnv = any> {
|
|
119
79
|
findMatch: (pathname: string, ms?: any) => any;
|
|
120
80
|
getMetricsStore: () => any;
|
|
@@ -129,23 +89,13 @@ export interface MatchApiDeps<TEnv = any> {
|
|
|
129
89
|
getRouteMap: () => Record<string, string>;
|
|
130
90
|
}
|
|
131
91
|
|
|
132
|
-
/**
|
|
133
|
-
* Title descriptor types for template support
|
|
134
|
-
*/
|
|
135
92
|
export type TitleDescriptor =
|
|
136
93
|
| string
|
|
137
94
|
| { template: string; default: string } // For layouts - template applied to child titles
|
|
138
|
-
| { absolute: string };
|
|
95
|
+
| { absolute: string };
|
|
139
96
|
|
|
140
|
-
/**
|
|
141
|
-
* Unset descriptor to remove inherited meta
|
|
142
|
-
* Key format matches getMetaKey output: "title", "name:description", "property:og:image"
|
|
143
|
-
*/
|
|
144
97
|
export type UnsetDescriptor = { unset: string };
|
|
145
98
|
|
|
146
|
-
/**
|
|
147
|
-
* Base meta descriptor types (sync values)
|
|
148
|
-
*/
|
|
149
99
|
export type MetaDescriptorBase =
|
|
150
100
|
| { charSet: "utf-8" }
|
|
151
101
|
| { title: TitleDescriptor }
|
|
@@ -157,10 +107,6 @@ export type MetaDescriptorBase =
|
|
|
157
107
|
| UnsetDescriptor
|
|
158
108
|
| { [name: string]: unknown };
|
|
159
109
|
|
|
160
|
-
/**
|
|
161
|
-
* Meta descriptor that can be sync or async.
|
|
162
|
-
* Use Promise<MetaDescriptorBase> for streaming meta that resolves after initial render.
|
|
163
|
-
*/
|
|
164
110
|
export type MetaDescriptor = MetaDescriptorBase | Promise<MetaDescriptorBase>;
|
|
165
111
|
|
|
166
112
|
type LdJsonObject = { [Key in string]: LdJsonValue } & {
|
package/src/router/url-params.ts
CHANGED
|
@@ -25,11 +25,6 @@ export function safeDecodeURIComponent(raw: string): string {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
// encodeURIComponent over-encodes for path segments. After running it,
|
|
29
|
-
// un-encode the pchar sub-delims + (`:` / `@`) so the resulting URL
|
|
30
|
-
// keeps human-readable characters that are legal in a path segment.
|
|
31
|
-
// Everything dangerous — `/ ? # %` and space/control/non-ASCII — stays
|
|
32
|
-
// encoded.
|
|
33
28
|
const PATH_SAFE_ESCAPES: Record<string, string> = {
|
|
34
29
|
"%3A": ":",
|
|
35
30
|
"%40": "@",
|
package/src/router.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { createCacheScope } from "./cache/cache-scope.js";
|
|
3
|
-
import {
|
|
4
|
-
setCacheProfiles,
|
|
5
|
-
resolveCacheProfiles,
|
|
6
|
-
} from "./cache/profile-registry.js";
|
|
3
|
+
import { resolveCacheProfiles } from "./cache/profile-registry.js";
|
|
7
4
|
import { isCachedFunction } from "./cache/taint.js";
|
|
8
5
|
import { assertClientComponent } from "./component-utils.js";
|
|
9
6
|
import { DefaultDocument } from "./components/DefaultDocument.js";
|
|
@@ -21,10 +18,11 @@ import type { AllUseItems } from "./route-types.js";
|
|
|
21
18
|
import type { UrlPatterns } from "./urls.js";
|
|
22
19
|
import type { UrlBuilder } from "./urls/pattern-types.js";
|
|
23
20
|
import { urls } from "./urls.js";
|
|
21
|
+
import { buildPrecomputedByPrefix } from "./build/prefix-tree-utils.js";
|
|
24
22
|
import {
|
|
25
23
|
type EntryData,
|
|
26
24
|
getContext,
|
|
27
|
-
|
|
25
|
+
RangoContext,
|
|
28
26
|
type MetricsStore,
|
|
29
27
|
} from "./server/context";
|
|
30
28
|
import { createHandleStore, type HandleStore } from "./server/handle-store.js";
|
|
@@ -56,6 +54,7 @@ import { buildDebugManifest } from "./router/debug-manifest.js";
|
|
|
56
54
|
|
|
57
55
|
import type { SegmentResolutionDeps, MatchApiDeps } from "./router/types.js";
|
|
58
56
|
import { createHandlerContext } from "./router/handler-context.js";
|
|
57
|
+
import { normalizeBasename } from "./router/basename.js";
|
|
59
58
|
import {
|
|
60
59
|
setupLoaderAccess,
|
|
61
60
|
setupLoaderAccessSilent,
|
|
@@ -70,6 +69,7 @@ import {
|
|
|
70
69
|
} from "./router/middleware.js";
|
|
71
70
|
import {
|
|
72
71
|
extractStaticPrefix,
|
|
72
|
+
joinPrefix,
|
|
73
73
|
traverseBack,
|
|
74
74
|
} from "./router/pattern-matching.js";
|
|
75
75
|
import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
|
|
@@ -90,13 +90,10 @@ import {
|
|
|
90
90
|
RouterRegistry,
|
|
91
91
|
nextRouterAutoId,
|
|
92
92
|
} from "./router/router-registry.js";
|
|
93
|
+
import type { RangoOptions, RootLayoutProps } from "./router/router-options.js";
|
|
93
94
|
import type {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} from "./router/router-options.js";
|
|
97
|
-
import type {
|
|
98
|
-
RSCRouter,
|
|
99
|
-
RSCRouterInternal,
|
|
95
|
+
Rango,
|
|
96
|
+
RangoInternal,
|
|
100
97
|
RouterRequestInput,
|
|
101
98
|
} from "./router/router-interfaces.js";
|
|
102
99
|
|
|
@@ -111,26 +108,27 @@ import {
|
|
|
111
108
|
matchForPrerender as _matchForPrerender,
|
|
112
109
|
renderStaticSegment as _renderStaticSegment,
|
|
113
110
|
} from "./router/prerender-match.js";
|
|
111
|
+
import { resolveStateCookieName } from "./router/state-cookie-name.js";
|
|
114
112
|
|
|
115
113
|
// Re-export public types and values from extracted modules
|
|
116
114
|
export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
117
115
|
export type {
|
|
118
|
-
|
|
116
|
+
RangoOptions,
|
|
119
117
|
RootLayoutProps,
|
|
120
118
|
SSRStreamMode,
|
|
121
119
|
SSROptions,
|
|
122
120
|
ResolveStreamingContext,
|
|
123
121
|
} from "./router/router-options.js";
|
|
124
122
|
export type {
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
Rango,
|
|
124
|
+
RangoInternal,
|
|
127
125
|
RouterRequestInput,
|
|
128
126
|
} from "./router/router-interfaces.js";
|
|
129
127
|
export { toInternal } from "./router/router-interfaces.js";
|
|
130
128
|
|
|
131
129
|
export function createRouter<TEnv = any>(
|
|
132
|
-
options:
|
|
133
|
-
):
|
|
130
|
+
options: RangoOptions<TEnv> = {},
|
|
131
|
+
): Rango<TEnv, {}> {
|
|
134
132
|
const {
|
|
135
133
|
id: userProvidedId,
|
|
136
134
|
$$id: injectedId,
|
|
@@ -150,6 +148,7 @@ export function createRouter<TEnv = any>(
|
|
|
150
148
|
nonce,
|
|
151
149
|
version,
|
|
152
150
|
prefetchCacheTTL: prefetchCacheTTLOption,
|
|
151
|
+
stateCookiePrefix: stateCookiePrefixOption,
|
|
153
152
|
warmup: warmupOption,
|
|
154
153
|
allowDebugManifest: allowDebugManifestOption = false,
|
|
155
154
|
telemetry: telemetrySink,
|
|
@@ -158,23 +157,31 @@ export function createRouter<TEnv = any>(
|
|
|
158
157
|
timeouts: timeoutsOption,
|
|
159
158
|
onTimeout,
|
|
160
159
|
originCheck: originCheckOption,
|
|
160
|
+
viewTransition: viewTransitionOption = "auto",
|
|
161
|
+
debugCacheSignal: debugCacheSignalOption = false,
|
|
161
162
|
} = options;
|
|
162
163
|
|
|
164
|
+
// Debug cache signal gate (DEVELOPMENT/TEST ONLY). Enabled by the
|
|
165
|
+
// debugCacheSignal option OR the RANGO_TEST_SIGNALS=1 env flag. When off,
|
|
166
|
+
// no X-Rango-Cache header is emitted and output is byte-identical.
|
|
167
|
+
const cacheSignalEnabled =
|
|
168
|
+
debugCacheSignalOption ||
|
|
169
|
+
(typeof process !== "undefined" &&
|
|
170
|
+
(process as { env?: Record<string, string | undefined> }).env
|
|
171
|
+
?.RANGO_TEST_SIGNALS === "1");
|
|
172
|
+
|
|
163
173
|
// Normalize basename: ensure leading slash, strip trailing slash.
|
|
164
|
-
// A bare "/" is equivalent to no basename.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
? "/" + basenameOption.replace(/^\/+|\/+$/g, "")
|
|
168
|
-
: undefined;
|
|
174
|
+
// A bare "/" is equivalent to no basename. Shared with the testing
|
|
175
|
+
// primitives via normalizeBasename so they can never drift.
|
|
176
|
+
const basename = normalizeBasename(basenameOption);
|
|
169
177
|
|
|
170
178
|
// Resolve telemetry sink (no-op when not configured)
|
|
171
179
|
const telemetry = resolveSink(telemetrySink);
|
|
172
180
|
|
|
173
|
-
// Resolve cache profiles: merge user config with guaranteed default
|
|
174
|
-
// This resolved map is
|
|
175
|
-
//
|
|
181
|
+
// Resolve cache profiles: merge user config with the guaranteed default
|
|
182
|
+
// profile. This resolved map is threaded onto each request context; the
|
|
183
|
+
// "use cache: <profile>" runtime path reads it request-scoped.
|
|
176
184
|
const resolvedCacheProfiles = resolveCacheProfiles(cacheProfilesOption);
|
|
177
|
-
setCacheProfiles(resolvedCacheProfiles);
|
|
178
185
|
|
|
179
186
|
// Source file: prefer Vite-injected path (zero cost), fall back to
|
|
180
187
|
// stack trace parsing for non-Vite environments (e.g. tests).
|
|
@@ -209,6 +216,14 @@ export function createRouter<TEnv = any>(
|
|
|
209
216
|
const routerId =
|
|
210
217
|
userProvidedId ?? injectedId ?? `router_${nextRouterAutoId()}`;
|
|
211
218
|
|
|
219
|
+
// Resolve the rango state cookie name once, here, so the two cookie writers
|
|
220
|
+
// (the client document.cookie writer and the server Set-Cookie writer)
|
|
221
|
+
// consume one pre-composed name and cannot drift.
|
|
222
|
+
const resolvedStateCookieName = resolveStateCookieName(
|
|
223
|
+
stateCookiePrefixOption,
|
|
224
|
+
routerId,
|
|
225
|
+
);
|
|
226
|
+
|
|
212
227
|
// Resolve prefetch cache TTL (default: 300 seconds / 5 minutes)
|
|
213
228
|
// Clamp to a non-negative integer for valid Cache-Control max-age.
|
|
214
229
|
const rawTTL =
|
|
@@ -255,9 +270,14 @@ export function createRouter<TEnv = any>(
|
|
|
255
270
|
invokeOnError(onError, error, phase, context, "Router");
|
|
256
271
|
}
|
|
257
272
|
|
|
258
|
-
// Validate document is a client component
|
|
273
|
+
// Validate document is a client component. Under a test runner the "use
|
|
274
|
+
// client" transform has not run, so a real exported document has no marker;
|
|
275
|
+
// allowServerInTest lets the router construct in a bare unit test (for
|
|
276
|
+
// dispatch / assertGeneratedRoutesMatch) while a real build still throws.
|
|
259
277
|
if (documentOption !== undefined) {
|
|
260
|
-
assertClientComponent(documentOption, "document"
|
|
278
|
+
assertClientComponent(documentOption, "document", {
|
|
279
|
+
allowServerInTest: true,
|
|
280
|
+
});
|
|
261
281
|
}
|
|
262
282
|
|
|
263
283
|
// Use default document if none provided (keeps internal name as rootLayout)
|
|
@@ -331,7 +351,6 @@ export function createRouter<TEnv = any>(
|
|
|
331
351
|
regex,
|
|
332
352
|
paramNames,
|
|
333
353
|
handler,
|
|
334
|
-
mountPrefix,
|
|
335
354
|
});
|
|
336
355
|
}
|
|
337
356
|
|
|
@@ -365,9 +384,11 @@ export function createRouter<TEnv = any>(
|
|
|
365
384
|
getRouterPrecomputedEntries(routerId) ?? getPrecomputedEntries();
|
|
366
385
|
if (current !== precomputedSource) {
|
|
367
386
|
precomputedSource = current;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
387
|
+
// buildPrecomputedByPrefix drops any staticPrefix owned by more than one
|
|
388
|
+
// leaf include instead of collapsing it last-wins (which would mis-assign
|
|
389
|
+
// one include's routes to another's entry and 500 a valid sibling route).
|
|
390
|
+
// Such shared-prefix includes resolve via the handler path instead.
|
|
391
|
+
precomputedByPrefix = current ? buildPrecomputedByPrefix(current) : null;
|
|
371
392
|
}
|
|
372
393
|
return precomputedByPrefix;
|
|
373
394
|
}
|
|
@@ -537,6 +558,7 @@ export function createRouter<TEnv = any>(
|
|
|
537
558
|
findNearestNotFoundBoundary,
|
|
538
559
|
notFoundComponent: notFound,
|
|
539
560
|
callOnError,
|
|
561
|
+
viewTransitionDefault: viewTransitionOption,
|
|
540
562
|
};
|
|
541
563
|
|
|
542
564
|
// Match API dependencies
|
|
@@ -664,6 +686,7 @@ export function createRouter<TEnv = any>(
|
|
|
664
686
|
findMatch,
|
|
665
687
|
findInterceptForRoute,
|
|
666
688
|
telemetry: telemetrySink,
|
|
689
|
+
cacheSignalEnabled,
|
|
667
690
|
});
|
|
668
691
|
|
|
669
692
|
const { match, matchPartial, matchError, previewMatch } = matchHandlers;
|
|
@@ -673,7 +696,7 @@ export function createRouter<TEnv = any>(
|
|
|
673
696
|
* The type system tracks accumulated routes through the builder chain
|
|
674
697
|
* Initial TRoutes is {} (empty) to avoid poisoning accumulated types with Record<string, string>
|
|
675
698
|
*/
|
|
676
|
-
const router:
|
|
699
|
+
const router: RangoInternal<TEnv, {}> = {
|
|
677
700
|
__brand: RSC_ROUTER_BRAND,
|
|
678
701
|
id: routerId,
|
|
679
702
|
basename,
|
|
@@ -721,7 +744,7 @@ export function createRouter<TEnv = any>(
|
|
|
721
744
|
};
|
|
722
745
|
|
|
723
746
|
let handlerResult: AllUseItems[] = [];
|
|
724
|
-
|
|
747
|
+
RangoContext.run(
|
|
725
748
|
{
|
|
726
749
|
manifest,
|
|
727
750
|
patterns: routePatterns,
|
|
@@ -833,10 +856,13 @@ export function createRouter<TEnv = any>(
|
|
|
833
856
|
|
|
834
857
|
// Create placeholder RouteEntry for each lazy include
|
|
835
858
|
for (const lazyInclude of lazyIncludes) {
|
|
836
|
-
// Compute the full URL prefix (combining parent prefix if any)
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
859
|
+
// Compute the full URL prefix (combining parent prefix if any). Use the
|
|
860
|
+
// slash-collapsing join so a trailing-slash parent prefix does not
|
|
861
|
+
// produce a double-slash staticPrefix the trie's sp can never match.
|
|
862
|
+
const fullPrefix = joinPrefix(
|
|
863
|
+
lazyInclude.context.urlPrefix,
|
|
864
|
+
lazyInclude.prefix,
|
|
865
|
+
);
|
|
840
866
|
|
|
841
867
|
const lazyEntry: RouteEntry<TEnv> & { _lazyPrefix?: string } = {
|
|
842
868
|
prefix: "",
|
|
@@ -932,6 +958,10 @@ export function createRouter<TEnv = any>(
|
|
|
932
958
|
prefetchCacheControl,
|
|
933
959
|
prefetchCacheTTL,
|
|
934
960
|
|
|
961
|
+
// Expose the resolved rango state cookie name for the server-side writer
|
|
962
|
+
// (invalidateClientCache) and for shipping to the client in metadata.
|
|
963
|
+
resolvedStateCookieName,
|
|
964
|
+
|
|
935
965
|
// Expose warmup enabled flag for handler and client
|
|
936
966
|
warmupEnabled,
|
|
937
967
|
|
|
@@ -999,6 +1029,13 @@ export function createRouter<TEnv = any>(
|
|
|
999
1029
|
// Expose basename for runtime manifest generation
|
|
1000
1030
|
__basename: basename,
|
|
1001
1031
|
|
|
1032
|
+
// Expose router-level boundary defaults for build-time clientChunks
|
|
1033
|
+
// discovery (so a "use client" default boundary lands in app-fallback).
|
|
1034
|
+
// These are createRouter options, never pushed onto EntryData.
|
|
1035
|
+
__defaultErrorBoundary: defaultErrorBoundary,
|
|
1036
|
+
__defaultNotFoundBoundary: defaultNotFoundBoundary,
|
|
1037
|
+
__notFound: notFound,
|
|
1038
|
+
|
|
1002
1039
|
// RSC request handler (lazily created on first call)
|
|
1003
1040
|
fetch: (() => {
|
|
1004
1041
|
// Handler is created on first call and reused
|
|
@@ -1016,8 +1053,10 @@ export function createRouter<TEnv = any>(
|
|
|
1016
1053
|
if (!handler) {
|
|
1017
1054
|
// Lazy import deferred to first request to avoid dev mode issues
|
|
1018
1055
|
const { createRSCHandler } = await import("./rsc/handler.js");
|
|
1019
|
-
// Cast:
|
|
1020
|
-
//
|
|
1056
|
+
// Cast: createRSCHandler receives `router as any`, which erases TEnv
|
|
1057
|
+
// and infers its handler as RouterRequestInput<unknown>. Re-narrow the
|
|
1058
|
+
// returned handler to RouterRequestInput<TEnv> so the call below stays
|
|
1059
|
+
// typed. (The handler already accepts (request, RouterRequestInput).)
|
|
1021
1060
|
handler = createRSCHandler({
|
|
1022
1061
|
router: router as any,
|
|
1023
1062
|
cache,
|
|
@@ -1045,9 +1084,9 @@ export function createRouter<TEnv = any>(
|
|
|
1045
1084
|
|
|
1046
1085
|
// If urls option was provided, auto-register them
|
|
1047
1086
|
if (typeof urlsOption === "function") {
|
|
1048
|
-
return router.routes(urlsOption) as
|
|
1087
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1049
1088
|
} else if (urlsOption) {
|
|
1050
|
-
return router.routes(urlsOption) as
|
|
1089
|
+
return router.routes(urlsOption) as Rango<TEnv, {}>;
|
|
1051
1090
|
}
|
|
1052
1091
|
|
|
1053
1092
|
return router;
|
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* RSC rendering) so they can be standalone modules without closure coupling.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
9
|
+
import type { RangoInternal } from "../router/router-interfaces.js";
|
|
10
10
|
import type { ErrorPhase } from "../types.js";
|
|
11
11
|
import type { InvokeOnErrorContext } from "../router/error-handling.js";
|
|
12
12
|
import type { RSCDependencies, LoadSSRModule } from "./types.js";
|
|
13
13
|
import type { SSRStreamMode } from "../router/router-options.js";
|
|
14
14
|
|
|
15
15
|
export interface HandlerContext<TEnv = unknown> {
|
|
16
|
-
router:
|
|
16
|
+
router: RangoInternal<TEnv, any>;
|
|
17
17
|
version: string;
|
|
18
18
|
renderToReadableStream: RSCDependencies["renderToReadableStream"];
|
|
19
19
|
decodeReply: RSCDependencies["decodeReply"];
|
|
@@ -31,6 +31,7 @@ export interface HandlerContext<TEnv = unknown> {
|
|
|
31
31
|
createRedirectFlightResponse: (
|
|
32
32
|
redirectUrl: string,
|
|
33
33
|
locationState?: Record<string, unknown>,
|
|
34
|
+
external?: boolean,
|
|
34
35
|
) => Response;
|
|
35
36
|
|
|
36
37
|
/**
|