@rangojs/router 0.0.0-experimental.32 → 0.0.0-experimental.3232cd17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- 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 +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +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 +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- 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 +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- 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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- 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 +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- 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 +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- 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 +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -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/taint.ts +55 -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 +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- 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 +70 -22
- 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 +52 -26
- package/src/index.ts +100 -38
- 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-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +190 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +63 -24
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +2 -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 +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +338 -126
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { TrieNode, TrieLeaf } from "../build/route-trie.js";
|
|
9
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
9
10
|
|
|
10
11
|
export interface TrieMatchResult {
|
|
11
12
|
/** Route name */
|
|
@@ -14,10 +15,6 @@ export interface TrieMatchResult {
|
|
|
14
15
|
sp: string;
|
|
15
16
|
/** Matched route params */
|
|
16
17
|
params: Record<string, string>;
|
|
17
|
-
/** Optional param names (absent params have empty string value) */
|
|
18
|
-
optionalParams?: string[];
|
|
19
|
-
/** Ancestry shortCodes for layout pruning */
|
|
20
|
-
ancestry: string[];
|
|
21
18
|
/** Redirect target if trailing slash requires it */
|
|
22
19
|
redirectTo?: string;
|
|
23
20
|
/** Route has pre-rendered data available */
|
|
@@ -42,14 +39,12 @@ export function tryTrieMatch(
|
|
|
42
39
|
): TrieMatchResult | null {
|
|
43
40
|
if (!trie) return null;
|
|
44
41
|
|
|
45
|
-
// Split pathname into segments, filtering empty strings from leading/trailing slashes
|
|
46
42
|
const pathnameHasTrailingSlash =
|
|
47
43
|
pathname.length > 1 && pathname.endsWith("/");
|
|
48
44
|
const normalizedPath = pathnameHasTrailingSlash
|
|
49
45
|
? pathname.slice(0, -1)
|
|
50
46
|
: pathname;
|
|
51
47
|
|
|
52
|
-
// Handle root path
|
|
53
48
|
if (normalizedPath === "" || normalizedPath === "/") {
|
|
54
49
|
if (trie.r) {
|
|
55
50
|
return validateAndBuild(
|
|
@@ -60,13 +55,24 @@ export function tryTrieMatch(
|
|
|
60
55
|
pathnameHasTrailingSlash,
|
|
61
56
|
);
|
|
62
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
|
+
}
|
|
63
71
|
return null;
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
// Remove leading slash and split
|
|
67
74
|
const segments = normalizedPath.slice(1).split("/");
|
|
68
75
|
|
|
69
|
-
// Try exact match with normalized path (no trailing slash)
|
|
70
76
|
const result = walkTrie(trie, segments, 0, []);
|
|
71
77
|
if (result) {
|
|
72
78
|
return validateAndBuild(
|
|
@@ -88,8 +94,58 @@ interface WalkResult {
|
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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.
|
|
93
149
|
*/
|
|
94
150
|
function walkTrie(
|
|
95
151
|
node: TrieNode,
|
|
@@ -97,25 +153,34 @@ function walkTrie(
|
|
|
97
153
|
index: number,
|
|
98
154
|
paramValues: string[],
|
|
99
155
|
): WalkResult | null {
|
|
100
|
-
// All segments consumed: check for terminal
|
|
101
156
|
if (index === segments.length) {
|
|
102
|
-
if (node.r) {
|
|
157
|
+
if (node.r && leafConstraintsPass(node.r, paramValues, undefined)) {
|
|
103
158
|
return { leaf: node.r, paramValues: [...paramValues] };
|
|
104
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
|
+
}
|
|
105
169
|
return null;
|
|
106
170
|
}
|
|
107
171
|
|
|
108
172
|
const segment = segments[index];
|
|
109
173
|
const staticChild = node.s?.[segment];
|
|
110
174
|
|
|
111
|
-
// Priority 1: Static match
|
|
112
175
|
if (staticChild) {
|
|
113
176
|
const result = walkTrie(staticChild, segments, index + 1, paramValues);
|
|
114
177
|
if (result) return result;
|
|
115
178
|
}
|
|
116
179
|
|
|
117
|
-
// Priority 2: Suffix-param match (e.g., :productId.html)
|
|
118
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`.
|
|
119
184
|
for (const suffix in node.xp) {
|
|
120
185
|
if (segment.endsWith(suffix) && segment.length > suffix.length) {
|
|
121
186
|
const paramValue = segment.slice(0, -suffix.length);
|
|
@@ -132,7 +197,6 @@ function walkTrie(
|
|
|
132
197
|
}
|
|
133
198
|
}
|
|
134
199
|
|
|
135
|
-
// Priority 3: Param match
|
|
136
200
|
if (node.p) {
|
|
137
201
|
paramValues.push(segment);
|
|
138
202
|
const result = walkTrie(node.p.c, segments, index + 1, paramValues);
|
|
@@ -140,14 +204,15 @@ function walkTrie(
|
|
|
140
204
|
if (result) return result;
|
|
141
205
|
}
|
|
142
206
|
|
|
143
|
-
// Priority 4: Wildcard match (consumes rest)
|
|
144
207
|
if (node.w) {
|
|
145
208
|
const rest = joinRemainingSegments(segments, index);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
209
|
+
if (leafConstraintsPass(node.w, paramValues, rest)) {
|
|
210
|
+
return {
|
|
211
|
+
leaf: node.w,
|
|
212
|
+
paramValues: [...paramValues],
|
|
213
|
+
wildcardValue: rest,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
151
216
|
}
|
|
152
217
|
|
|
153
218
|
return null;
|
|
@@ -173,40 +238,22 @@ function validateAndBuild(
|
|
|
173
238
|
originalPathname: string,
|
|
174
239
|
pathnameHasTrailingSlash: boolean,
|
|
175
240
|
): TrieMatchResult | null {
|
|
176
|
-
// Build named params by zipping leaf.pa with positional paramValues
|
|
177
241
|
const params: Record<string, string> = {};
|
|
178
242
|
if (leaf.pa) {
|
|
179
243
|
for (let i = 0; i < leaf.pa.length && i < paramValues.length; i++) {
|
|
180
|
-
params[leaf.pa[i]] = paramValues[i];
|
|
244
|
+
params[leaf.pa[i]] = safeDecodeURIComponent(paramValues[i]);
|
|
181
245
|
}
|
|
182
246
|
}
|
|
183
247
|
|
|
184
|
-
// Add wildcard param (wildcard leaves have pn from TrieNode.w type)
|
|
185
248
|
if (wildcardValue !== undefined && "pn" in leaf) {
|
|
186
|
-
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
249
|
+
params[(leaf as TrieLeaf & { pn: string }).pn] =
|
|
250
|
+
safeDecodeURIComponent(wildcardValue);
|
|
187
251
|
}
|
|
188
252
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
for (const paramName in leaf.cv) {
|
|
192
|
-
const allowed = leaf.cv[paramName]!;
|
|
193
|
-
const value = params[paramName];
|
|
194
|
-
if (value !== undefined && value !== "" && !allowed.includes(value)) {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Fill in empty strings for optional params that weren't matched
|
|
201
|
-
if (leaf.op) {
|
|
202
|
-
for (const name of leaf.op) {
|
|
203
|
-
if (!(name in params)) {
|
|
204
|
-
params[name] = "";
|
|
205
|
-
}
|
|
206
|
-
}
|
|
253
|
+
if (!constraintsSatisfied(leaf, params)) {
|
|
254
|
+
return null;
|
|
207
255
|
}
|
|
208
256
|
|
|
209
|
-
// Trailing slash handling
|
|
210
257
|
const tsMode = leaf.ts as "never" | "always" | "ignore" | undefined;
|
|
211
258
|
let redirectTo: string | undefined;
|
|
212
259
|
|
|
@@ -224,10 +271,8 @@ function validateAndBuild(
|
|
|
224
271
|
routeKey: leaf.n,
|
|
225
272
|
sp: leaf.sp,
|
|
226
273
|
params,
|
|
227
|
-
ancestry: leaf.a,
|
|
228
274
|
};
|
|
229
275
|
|
|
230
|
-
if (leaf.op) result.optionalParams = leaf.op;
|
|
231
276
|
if (redirectTo) result.redirectTo = redirectTo;
|
|
232
277
|
if (leaf.pr) result.pr = true;
|
|
233
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>,
|
|
@@ -96,24 +63,18 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
96
63
|
findNearestNotFoundBoundary: (
|
|
97
64
|
entry: EntryData | null,
|
|
98
65
|
) => ReactNode | NotFoundBoundaryHandler | null;
|
|
66
|
+
notFoundComponent?: ReactNode | ((props: { pathname: string }) => ReactNode);
|
|
99
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;
|
|
100
76
|
}
|
|
101
77
|
|
|
102
|
-
/**
|
|
103
|
-
* Dependencies injected from createRouter closure into extracted intercept resolution functions.
|
|
104
|
-
*/
|
|
105
|
-
export interface InterceptResolutionDeps<TEnv = any> {
|
|
106
|
-
wrapLoaderPromise: SegmentResolutionDeps<TEnv>["wrapLoaderPromise"];
|
|
107
|
-
evaluateInterceptWhen: (
|
|
108
|
-
intercept: InterceptEntry,
|
|
109
|
-
selectorContext: InterceptSelectorContext | null,
|
|
110
|
-
isAction: boolean,
|
|
111
|
-
) => boolean;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Dependencies injected from createRouter closure into extracted match API functions.
|
|
116
|
-
*/
|
|
117
78
|
export interface MatchApiDeps<TEnv = any> {
|
|
118
79
|
findMatch: (pathname: string, ms?: any) => any;
|
|
119
80
|
getMetricsStore: () => any;
|
|
@@ -128,23 +89,13 @@ export interface MatchApiDeps<TEnv = any> {
|
|
|
128
89
|
getRouteMap: () => Record<string, string>;
|
|
129
90
|
}
|
|
130
91
|
|
|
131
|
-
/**
|
|
132
|
-
* Title descriptor types for template support
|
|
133
|
-
*/
|
|
134
92
|
export type TitleDescriptor =
|
|
135
93
|
| string
|
|
136
94
|
| { template: string; default: string } // For layouts - template applied to child titles
|
|
137
|
-
| { absolute: string };
|
|
95
|
+
| { absolute: string };
|
|
138
96
|
|
|
139
|
-
/**
|
|
140
|
-
* Unset descriptor to remove inherited meta
|
|
141
|
-
* Key format matches getMetaKey output: "title", "name:description", "property:og:image"
|
|
142
|
-
*/
|
|
143
97
|
export type UnsetDescriptor = { unset: string };
|
|
144
98
|
|
|
145
|
-
/**
|
|
146
|
-
* Base meta descriptor types (sync values)
|
|
147
|
-
*/
|
|
148
99
|
export type MetaDescriptorBase =
|
|
149
100
|
| { charSet: "utf-8" }
|
|
150
101
|
| { title: TitleDescriptor }
|
|
@@ -156,10 +107,6 @@ export type MetaDescriptorBase =
|
|
|
156
107
|
| UnsetDescriptor
|
|
157
108
|
| { [name: string]: unknown };
|
|
158
109
|
|
|
159
|
-
/**
|
|
160
|
-
* Meta descriptor that can be sync or async.
|
|
161
|
-
* Use Promise<MetaDescriptorBase> for streaming meta that resolves after initial render.
|
|
162
|
-
*/
|
|
163
110
|
export type MetaDescriptor = MetaDescriptorBase | Promise<MetaDescriptorBase>;
|
|
164
111
|
|
|
165
112
|
type LdJsonObject = { [Key in string]: LdJsonValue } & {
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL param encode/decode at the route boundary.
|
|
3
|
+
*
|
|
4
|
+
* Extraction (decode): regex/trie matchers keep param values URL-encoded;
|
|
5
|
+
* `safeDecodeURIComponent` turns them back into raw strings so `ctx.params`
|
|
6
|
+
* matches the contract apps expect (Express/React Router/Fastify/Koa) and
|
|
7
|
+
* round-trips through reverse stay stable. Malformed %-encoding is
|
|
8
|
+
* preserved as-is so a broken URL doesn't crash matching.
|
|
9
|
+
*
|
|
10
|
+
* Reversal (encode): `encodePathSegment` escapes only what RFC 3986
|
|
11
|
+
* requires for a path segment — `/`, `?`, `#`, space, control chars,
|
|
12
|
+
* non-ASCII — and leaves pchar sub-delims (`@ : $ & + , ; =` and friends)
|
|
13
|
+
* readable. `encodeURIComponent` over-encodes for path segments, which
|
|
14
|
+
* makes generated URLs harder for humans to read in the address bar
|
|
15
|
+
* (e.g. mailbox IDs like `ivo@example.com` would become
|
|
16
|
+
* `ivo%40example.com` even though `@` is path-legal).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
export function safeDecodeURIComponent(raw: string): string {
|
|
20
|
+
if (raw === "" || raw.indexOf("%") === -1) return raw;
|
|
21
|
+
try {
|
|
22
|
+
return decodeURIComponent(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const PATH_SAFE_ESCAPES: Record<string, string> = {
|
|
29
|
+
"%3A": ":",
|
|
30
|
+
"%40": "@",
|
|
31
|
+
"%24": "$",
|
|
32
|
+
"%26": "&",
|
|
33
|
+
"%2B": "+",
|
|
34
|
+
"%2C": ",",
|
|
35
|
+
"%3B": ";",
|
|
36
|
+
"%3D": "=",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function encodePathSegment(value: string): string {
|
|
40
|
+
return encodeURIComponent(value).replace(
|
|
41
|
+
/%(?:3A|40|24|26|2B|2C|3B|3D)/gi,
|
|
42
|
+
(match) => PATH_SAFE_ESCAPES[match.toUpperCase()] ?? match,
|
|
43
|
+
);
|
|
44
|
+
}
|