@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +117 -23
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +36 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +96 -12
- package/src/index.ts +94 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +109 -118
- package/src/vite/router-discovery.ts +718 -119
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -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 {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared route-param comparison helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Shallow equality for two route-param records. Same-reference is a fast path;
|
|
7
|
+
* otherwise compares key count then each value.
|
|
8
|
+
*/
|
|
9
|
+
export function paramsEqual(
|
|
10
|
+
a: Record<string, string>,
|
|
11
|
+
b: Record<string, string>,
|
|
12
|
+
): boolean {
|
|
13
|
+
if (a === b) return true;
|
|
14
|
+
|
|
15
|
+
const keysA = Object.keys(a);
|
|
16
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
17
|
+
|
|
18
|
+
for (const key of keysA) {
|
|
19
|
+
if (a[key] !== b[key]) return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { RouteEntry, TrailingSlashMode } from "../types";
|
|
8
8
|
import type { EntryData } from "../server/context";
|
|
9
9
|
import { debugLog, isRouterDebugEnabled } from "./logging.js";
|
|
10
|
+
import { escapeRegExp } from "../regex-escape.js";
|
|
10
11
|
import { safeDecodeURIComponent } from "./url-params.js";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -33,13 +34,6 @@ export interface ParsedSegment {
|
|
|
33
34
|
*/
|
|
34
35
|
export function parsePattern(pattern: string): ParsedSegment[] {
|
|
35
36
|
const segments: ParsedSegment[] = [];
|
|
36
|
-
// Match: /segment where segment can be:
|
|
37
|
-
// - static text
|
|
38
|
-
// - :param
|
|
39
|
-
// - :param?
|
|
40
|
-
// - :param(a|b)
|
|
41
|
-
// - :param(a|b)?
|
|
42
|
-
// - *
|
|
43
37
|
const segmentRegex =
|
|
44
38
|
/\/(:([a-zA-Z_][a-zA-Z0-9_]*)(\(([^)]+)\))?(\?)?([^/]*)|(\*)|([^/]+))/g;
|
|
45
39
|
|
|
@@ -81,7 +75,6 @@ export function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
81
75
|
export interface CompiledPattern {
|
|
82
76
|
regex: RegExp;
|
|
83
77
|
paramNames: string[];
|
|
84
|
-
optionalParams: Set<string>;
|
|
85
78
|
hasTrailingSlash: boolean;
|
|
86
79
|
/**
|
|
87
80
|
* Param-name → allowed values for constrained params (e.g. `:lang(en|gb)`).
|
|
@@ -149,7 +142,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
149
142
|
|
|
150
143
|
const segments = parsePattern(normalizedPattern);
|
|
151
144
|
const paramNames: string[] = [];
|
|
152
|
-
const optionalParams = new Set<string>();
|
|
153
145
|
let constraints: Record<string, string[]> | undefined;
|
|
154
146
|
|
|
155
147
|
let regexPattern = "";
|
|
@@ -160,7 +152,7 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
160
152
|
regexPattern += "/(.*)";
|
|
161
153
|
} else if (segment.type === "param") {
|
|
162
154
|
paramNames.push(segment.value);
|
|
163
|
-
const suffixPattern = segment.suffix ?
|
|
155
|
+
const suffixPattern = segment.suffix ? escapeRegExp(segment.suffix) : "";
|
|
164
156
|
// Constrained params capture anything here; the allowed values are
|
|
165
157
|
// checked post-decode in findMatch so URL-encoded constraint values
|
|
166
158
|
// (e.g. `:lang(en GB)` via `/en%20GB`) still match.
|
|
@@ -171,7 +163,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
171
163
|
}
|
|
172
164
|
|
|
173
165
|
if (segment.optional) {
|
|
174
|
-
optionalParams.add(segment.value);
|
|
175
166
|
// Optional: make the whole /segment optional
|
|
176
167
|
regexPattern += `(?:/${valuePattern}${suffixPattern})?`;
|
|
177
168
|
} else {
|
|
@@ -179,15 +170,28 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
179
170
|
}
|
|
180
171
|
} else {
|
|
181
172
|
// Static segment
|
|
182
|
-
regexPattern += `/${
|
|
173
|
+
regexPattern += `/${escapeRegExp(segment.value)}`;
|
|
183
174
|
}
|
|
184
175
|
}
|
|
185
176
|
|
|
186
|
-
// Handle root path
|
|
187
177
|
if (regexPattern === "") {
|
|
188
178
|
regexPattern = "/";
|
|
189
179
|
}
|
|
190
180
|
|
|
181
|
+
// Patterns of only optional segments (e.g. `/:locale?`, `/:a?/:b?`) need
|
|
182
|
+
// an explicit `/` alternative so a bare `/` matches the absent form. The
|
|
183
|
+
// optional template `(?:/X)?` matches `/X` or empty string, but pathnames
|
|
184
|
+
// are never empty. Arises from `include("/:locale?", routes)` + inner
|
|
185
|
+
// `path("/")`. Skip when an explicit trailing slash already anchors the
|
|
186
|
+
// match.
|
|
187
|
+
const hasOnlyOptionalSegments =
|
|
188
|
+
!hasTrailingSlash &&
|
|
189
|
+
segments.length > 0 &&
|
|
190
|
+
segments.every((segment) => segment.type === "param" && segment.optional);
|
|
191
|
+
if (hasOnlyOptionalSegments) {
|
|
192
|
+
regexPattern = `(?:/|${regexPattern})`;
|
|
193
|
+
}
|
|
194
|
+
|
|
191
195
|
// Add trailing slash to regex if pattern has one
|
|
192
196
|
if (hasTrailingSlash) {
|
|
193
197
|
regexPattern += "/";
|
|
@@ -196,7 +200,6 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
196
200
|
return {
|
|
197
201
|
regex: new RegExp(`^${regexPattern}$`),
|
|
198
202
|
paramNames,
|
|
199
|
-
optionalParams,
|
|
200
203
|
hasTrailingSlash,
|
|
201
204
|
...(constraints ? { constraints } : {}),
|
|
202
205
|
};
|
|
@@ -205,7 +208,9 @@ export function compilePattern(pattern: string): CompiledPattern {
|
|
|
205
208
|
/**
|
|
206
209
|
* Validate decoded params against a compiled pattern's constraints.
|
|
207
210
|
* Returns false if any constrained param has a non-empty value not in the
|
|
208
|
-
* allowed list (
|
|
211
|
+
* allowed list. Absent optionals (key missing or `undefined`) are allowed;
|
|
212
|
+
* `""` is also tolerated as "absent" so user-provided params or fixtures
|
|
213
|
+
* that pass empty strings explicitly behave the same way.
|
|
209
214
|
*/
|
|
210
215
|
function satisfiesConstraints(
|
|
211
216
|
params: Record<string, string>,
|
|
@@ -226,10 +231,24 @@ function satisfiesConstraints(
|
|
|
226
231
|
}
|
|
227
232
|
|
|
228
233
|
/**
|
|
229
|
-
*
|
|
234
|
+
* Build the named-params record from a regex match. Optional segments that
|
|
235
|
+
* didn't capture leave the corresponding group `undefined`; we skip those
|
|
236
|
+
* keys so `ctx.params.<name>` reads as `undefined` rather than `""`. This
|
|
237
|
+
* keeps the runtime aligned with the `ExtractParams` type and matches the
|
|
238
|
+
* trie matcher's contract (see `trie-matching.ts:validateAndBuild`).
|
|
230
239
|
*/
|
|
231
|
-
function
|
|
232
|
-
|
|
240
|
+
function buildParamsFromMatch(
|
|
241
|
+
match: RegExpExecArray,
|
|
242
|
+
paramNames: string[],
|
|
243
|
+
): Record<string, string> {
|
|
244
|
+
const params: Record<string, string> = {};
|
|
245
|
+
paramNames.forEach((name, index) => {
|
|
246
|
+
const captured = match[index + 1];
|
|
247
|
+
if (captured !== undefined) {
|
|
248
|
+
params[name] = safeDecodeURIComponent(captured);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
return params;
|
|
233
252
|
}
|
|
234
253
|
|
|
235
254
|
/**
|
|
@@ -248,43 +267,58 @@ function escapeRegex(str: string): string {
|
|
|
248
267
|
export function extractStaticPrefix(pattern: string): string {
|
|
249
268
|
if (!pattern || pattern === "/") return "";
|
|
250
269
|
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
270
|
+
// Walk segments and stop at the first that is a real param (`:name`) or a
|
|
271
|
+
// wildcard (`*`). A literal `:` or `*` not at a segment boundary (e.g. the
|
|
272
|
+
// `a:b` in `/a:b/c/:id`, or `tel:+1`) is a STATIC segment and must NOT
|
|
273
|
+
// terminate the prefix — `pattern.indexOf(":")` misread it as a param marker,
|
|
274
|
+
// returning "" and dropping the findMatch fast-skip optimization for that
|
|
275
|
+
// entry on every request. Classification mirrors parsePattern: a leading `:`
|
|
276
|
+
// marks a param, a leading `*` marks a wildcard.
|
|
277
|
+
const hasLeadingSlash = pattern.startsWith("/");
|
|
278
|
+
const body = hasLeadingSlash ? pattern.slice(1) : pattern;
|
|
279
|
+
const segments = body.split("/");
|
|
280
|
+
|
|
281
|
+
const staticSegments: string[] = [];
|
|
282
|
+
for (const segment of segments) {
|
|
283
|
+
if (segment.startsWith(":") || segment.startsWith("*")) {
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
staticSegments.push(segment);
|
|
267
287
|
}
|
|
268
288
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
289
|
+
// No leading static segment (first segment is a param/wildcard) -> no prefix.
|
|
290
|
+
if (staticSegments.length === 0) return "";
|
|
291
|
+
// Every segment was static (no param/wildcard) -> the whole pattern is the
|
|
292
|
+
// prefix. Preserve a trailing slash only when it existed in the input; a
|
|
293
|
+
// split of "/a/b" yields ["a","b"] (no empty tail) so a re-join is exact.
|
|
294
|
+
if (staticSegments.length === segments.length) return pattern;
|
|
273
295
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return "";
|
|
278
|
-
}
|
|
296
|
+
const prefix = staticSegments.join("/");
|
|
297
|
+
return hasLeadingSlash ? "/" + prefix : prefix;
|
|
298
|
+
}
|
|
279
299
|
|
|
280
|
-
|
|
300
|
+
/**
|
|
301
|
+
* Join a URL prefix to a sub-prefix, collapsing the duplicate slash when the
|
|
302
|
+
* base ends with "/" and the sub-prefix starts with "/". This mirrors the
|
|
303
|
+
* canonical join in `include()` (urls/include-helper.ts) and `runWithPrefixes`
|
|
304
|
+
* (server/context.ts) so a nested lazy include's runtime staticPrefix matches
|
|
305
|
+
* the build-time trie's `sp` (e.g. `include("/parent/", …)` containing
|
|
306
|
+
* `include("/child", …)` resolves to `/parent/child`, not `/parent//child`).
|
|
307
|
+
*/
|
|
308
|
+
export function joinPrefix(base: string | undefined, prefix: string): string {
|
|
309
|
+
if (!base) return prefix;
|
|
310
|
+
return base.endsWith("/") && prefix.startsWith("/")
|
|
311
|
+
? base + prefix.slice(1)
|
|
312
|
+
: base + prefix;
|
|
281
313
|
}
|
|
282
314
|
|
|
283
315
|
/**
|
|
284
316
|
* Match a pathname against registered routes
|
|
285
317
|
*
|
|
286
|
-
* Note: Optional params that are absent in the path
|
|
287
|
-
*
|
|
318
|
+
* Note: Optional params that are absent in the path are omitted from the
|
|
319
|
+
* returned `params` (read as `undefined`), matching the trie matcher and
|
|
320
|
+
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition to
|
|
321
|
+
* determine which keys are optional.
|
|
288
322
|
*
|
|
289
323
|
* Trailing slash handling (priority order):
|
|
290
324
|
* 1. Per-route `trailingSlash` config from route()
|
|
@@ -302,10 +336,7 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
302
336
|
entry: RouteEntry<TEnv>;
|
|
303
337
|
routeKey: string;
|
|
304
338
|
params: Record<string, string>;
|
|
305
|
-
optionalParams: Set<string>;
|
|
306
339
|
redirectTo?: string;
|
|
307
|
-
/** Ancestry shortCodes for layout pruning (from trie match) */
|
|
308
|
-
ancestry?: string[];
|
|
309
340
|
/** Route has pre-rendered data available (from trie) */
|
|
310
341
|
pr?: true;
|
|
311
342
|
/** Passthrough: handler kept for live fallback on unknown params (from trie) */
|
|
@@ -383,8 +414,6 @@ export function findMatch<TEnv>(
|
|
|
383
414
|
: pathname + "/";
|
|
384
415
|
|
|
385
416
|
for (const entry of routesEntries) {
|
|
386
|
-
// Short-circuit: skip entry if pathname doesn't start with static prefix
|
|
387
|
-
// staticPrefix is pre-computed at registration time, so this is O(1)
|
|
388
417
|
if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
|
|
389
418
|
if (effectiveDebug) {
|
|
390
419
|
debugStats.entriesSkipped++;
|
|
@@ -396,8 +425,6 @@ export function findMatch<TEnv>(
|
|
|
396
425
|
continue;
|
|
397
426
|
}
|
|
398
427
|
|
|
399
|
-
// Check if this is a lazy entry that needs evaluation
|
|
400
|
-
// When staticPrefix matches but routes are not yet populated, signal caller to evaluate
|
|
401
428
|
if (entry.lazy && !entry.lazyEvaluated) {
|
|
402
429
|
if (effectiveDebug) {
|
|
403
430
|
debugLog("findMatch", "lazy entry requires evaluation", {
|
|
@@ -418,7 +445,6 @@ export function findMatch<TEnv>(
|
|
|
418
445
|
debugStats.routesChecked++;
|
|
419
446
|
}
|
|
420
447
|
|
|
421
|
-
// Join prefix and pattern, handling edge cases
|
|
422
448
|
let fullPattern: string;
|
|
423
449
|
if (entry.prefix === "" || entry.prefix === "/") {
|
|
424
450
|
fullPattern = pattern;
|
|
@@ -428,19 +454,12 @@ export function findMatch<TEnv>(
|
|
|
428
454
|
fullPattern = entry.prefix + pattern;
|
|
429
455
|
}
|
|
430
456
|
|
|
431
|
-
const {
|
|
432
|
-
|
|
433
|
-
paramNames,
|
|
434
|
-
optionalParams,
|
|
435
|
-
hasTrailingSlash,
|
|
436
|
-
constraints,
|
|
437
|
-
} = getCompiledPattern(fullPattern);
|
|
457
|
+
const { regex, paramNames, hasTrailingSlash, constraints } =
|
|
458
|
+
getCompiledPattern(fullPattern);
|
|
438
459
|
|
|
439
|
-
// Get trailing slash mode for this route (per-route config or pattern-based)
|
|
440
460
|
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
441
461
|
entry.trailingSlash?.[routeKey];
|
|
442
462
|
|
|
443
|
-
// Prerender flag from entry metadata (set by urls() for prerender handlers)
|
|
444
463
|
const prFlag = entry.prerenderRouteKeys?.has(routeKey)
|
|
445
464
|
? { pr: true as const }
|
|
446
465
|
: {};
|
|
@@ -448,16 +467,10 @@ export function findMatch<TEnv>(
|
|
|
448
467
|
? { pt: true as const }
|
|
449
468
|
: {};
|
|
450
469
|
|
|
451
|
-
// Try exact match first
|
|
452
470
|
const match = regex.exec(pathname);
|
|
453
471
|
if (match) {
|
|
454
|
-
const params
|
|
455
|
-
paramNames.forEach((name, index) => {
|
|
456
|
-
params[name] = safeDecodeURIComponent(match[index + 1] ?? "");
|
|
457
|
-
});
|
|
472
|
+
const params = buildParamsFromMatch(match, paramNames);
|
|
458
473
|
|
|
459
|
-
// Validate constraints against decoded values; a failure falls
|
|
460
|
-
// through to the next route so other patterns can still match.
|
|
461
474
|
if (!satisfiesConstraints(params, constraints)) {
|
|
462
475
|
continue;
|
|
463
476
|
}
|
|
@@ -470,29 +483,24 @@ export function findMatch<TEnv>(
|
|
|
470
483
|
});
|
|
471
484
|
}
|
|
472
485
|
|
|
473
|
-
// Check if trailing slash mode requires redirect even on exact match
|
|
474
486
|
if (
|
|
475
487
|
trailingSlashMode === "always" &&
|
|
476
488
|
!pathnameHasTrailingSlash &&
|
|
477
489
|
pathname !== "/"
|
|
478
490
|
) {
|
|
479
|
-
// Mode says always have trailing slash, but pathname doesn't have it
|
|
480
491
|
return {
|
|
481
492
|
entry,
|
|
482
493
|
routeKey,
|
|
483
494
|
params,
|
|
484
|
-
optionalParams,
|
|
485
495
|
redirectTo: pathname + "/",
|
|
486
496
|
...prFlag,
|
|
487
497
|
...ptFlag,
|
|
488
498
|
};
|
|
489
499
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
490
|
-
// Mode says never have trailing slash, but pathname has it
|
|
491
500
|
return {
|
|
492
501
|
entry,
|
|
493
502
|
routeKey,
|
|
494
503
|
params,
|
|
495
|
-
optionalParams,
|
|
496
504
|
redirectTo: pathname.slice(0, -1),
|
|
497
505
|
...prFlag,
|
|
498
506
|
...ptFlag,
|
|
@@ -503,43 +511,33 @@ export function findMatch<TEnv>(
|
|
|
503
511
|
entry,
|
|
504
512
|
routeKey,
|
|
505
513
|
params,
|
|
506
|
-
optionalParams,
|
|
507
514
|
...prFlag,
|
|
508
515
|
...ptFlag,
|
|
509
516
|
};
|
|
510
517
|
}
|
|
511
518
|
|
|
512
|
-
// Try alternate pathname (opposite trailing slash)
|
|
513
519
|
const altMatch = regex.exec(alternatePathname);
|
|
514
520
|
if (altMatch) {
|
|
515
|
-
const params
|
|
516
|
-
paramNames.forEach((name, index) => {
|
|
517
|
-
params[name] = safeDecodeURIComponent(altMatch[index + 1] ?? "");
|
|
518
|
-
});
|
|
521
|
+
const params = buildParamsFromMatch(altMatch, paramNames);
|
|
519
522
|
|
|
520
523
|
if (!satisfiesConstraints(params, constraints)) {
|
|
521
524
|
continue;
|
|
522
525
|
}
|
|
523
526
|
|
|
524
|
-
// Determine redirect behavior based on mode
|
|
525
527
|
if (trailingSlashMode === "ignore") {
|
|
526
|
-
// Match without redirect
|
|
527
528
|
return {
|
|
528
529
|
entry,
|
|
529
530
|
routeKey,
|
|
530
531
|
params,
|
|
531
|
-
optionalParams,
|
|
532
532
|
...prFlag,
|
|
533
533
|
...ptFlag,
|
|
534
534
|
};
|
|
535
535
|
} else if (trailingSlashMode === "never") {
|
|
536
|
-
// Redirect to no trailing slash
|
|
537
536
|
if (pathnameHasTrailingSlash) {
|
|
538
537
|
return {
|
|
539
538
|
entry,
|
|
540
539
|
routeKey,
|
|
541
540
|
params,
|
|
542
|
-
optionalParams,
|
|
543
541
|
redirectTo: alternatePathname,
|
|
544
542
|
...prFlag,
|
|
545
543
|
...ptFlag,
|
|
@@ -549,18 +547,15 @@ export function findMatch<TEnv>(
|
|
|
549
547
|
entry,
|
|
550
548
|
routeKey,
|
|
551
549
|
params,
|
|
552
|
-
optionalParams,
|
|
553
550
|
...prFlag,
|
|
554
551
|
...ptFlag,
|
|
555
552
|
};
|
|
556
553
|
} else if (trailingSlashMode === "always") {
|
|
557
|
-
// Redirect to with trailing slash
|
|
558
554
|
if (!pathnameHasTrailingSlash) {
|
|
559
555
|
return {
|
|
560
556
|
entry,
|
|
561
557
|
routeKey,
|
|
562
558
|
params,
|
|
563
|
-
optionalParams,
|
|
564
559
|
redirectTo: alternatePathname,
|
|
565
560
|
...prFlag,
|
|
566
561
|
...ptFlag,
|
|
@@ -570,13 +565,10 @@ export function findMatch<TEnv>(
|
|
|
570
565
|
entry,
|
|
571
566
|
routeKey,
|
|
572
567
|
params,
|
|
573
|
-
optionalParams,
|
|
574
568
|
...prFlag,
|
|
575
569
|
...ptFlag,
|
|
576
570
|
};
|
|
577
571
|
} else {
|
|
578
|
-
// No explicit mode - use pattern-based detection
|
|
579
|
-
// Redirect to canonical form (what the pattern defines)
|
|
580
572
|
const canonicalPath = hasTrailingSlash
|
|
581
573
|
? alternatePathname
|
|
582
574
|
: pathname.slice(0, -1);
|
|
@@ -584,7 +576,6 @@ export function findMatch<TEnv>(
|
|
|
584
576
|
entry,
|
|
585
577
|
routeKey,
|
|
586
578
|
params,
|
|
587
|
-
optionalParams,
|
|
588
579
|
redirectTo: canonicalPath,
|
|
589
580
|
...prFlag,
|
|
590
581
|
...ptFlag,
|
|
@@ -605,7 +596,7 @@ export function* traverseBack(entry: EntryData): Generator<EntryData> {
|
|
|
605
596
|
let current: EntryData | null = entry;
|
|
606
597
|
const items = [] as EntryData[];
|
|
607
598
|
while (current !== null) {
|
|
608
|
-
items.push(current);
|
|
599
|
+
items.push(current);
|
|
609
600
|
current = current.parent;
|
|
610
601
|
}
|
|
611
602
|
for (let i = items.length - 1; i >= 0; i--) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the prefetch cache TTL once, at router init, into the three derived
|
|
3
|
+
* values the rest of the router consumes: seconds (for the Cache-Control
|
|
4
|
+
* max-age), milliseconds (for the client-side in-memory cache), and the
|
|
5
|
+
* Cache-Control header string (or false when caching is disabled).
|
|
6
|
+
*
|
|
7
|
+
* `false` disables prefetch caching (seconds 0). A non-finite input
|
|
8
|
+
* (NaN/Infinity, e.g. from an env-derived number that failed to parse) is
|
|
9
|
+
* treated as the default rather than producing a malformed
|
|
10
|
+
* `Cache-Control: max-age=NaN` header; CDNs/browsers ignore such a directive,
|
|
11
|
+
* which would silently disable caching on EVERY prefetch response. Negative
|
|
12
|
+
* finite values clamp to 0 (disabled).
|
|
13
|
+
*
|
|
14
|
+
* Policy note: this is the prefetch-TTL policy specifically. Other finite-number
|
|
15
|
+
* guards in the codebase deliberately differ — defer.ts treats Infinity as an
|
|
16
|
+
* intentional disable, and profile-registry.ts THROWS on non-finite/negative ttl
|
|
17
|
+
* at config time. They are NOT the same guard, so don't unify them.
|
|
18
|
+
*/
|
|
19
|
+
export interface ResolvedPrefetchCacheTTL {
|
|
20
|
+
/** TTL in seconds for the Cache-Control max-age directive. */
|
|
21
|
+
seconds: number;
|
|
22
|
+
/** TTL in milliseconds for the client-side in-memory prefetch cache. */
|
|
23
|
+
ms: number;
|
|
24
|
+
/** Cache-Control header value, or false when caching is disabled. */
|
|
25
|
+
cacheControl: string | false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const DEFAULT_PREFETCH_CACHE_TTL_SECONDS = 300;
|
|
29
|
+
|
|
30
|
+
export function resolvePrefetchCacheTTL(
|
|
31
|
+
rawTTL: number | false | undefined,
|
|
32
|
+
): ResolvedPrefetchCacheTTL {
|
|
33
|
+
let seconds: number;
|
|
34
|
+
if (rawTTL === false) {
|
|
35
|
+
seconds = 0;
|
|
36
|
+
} else if (rawTTL === undefined) {
|
|
37
|
+
seconds = DEFAULT_PREFETCH_CACHE_TTL_SECONDS;
|
|
38
|
+
} else if (typeof rawTTL === "number" && Number.isFinite(rawTTL)) {
|
|
39
|
+
seconds = Math.max(0, Math.floor(rawTTL));
|
|
40
|
+
} else {
|
|
41
|
+
// Non-finite (NaN/Infinity): fall back to the default rather than emit a
|
|
42
|
+
// malformed max-age=NaN/Infinity that CDNs and browsers reject.
|
|
43
|
+
seconds = DEFAULT_PREFETCH_CACHE_TTL_SECONDS;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
seconds,
|
|
48
|
+
ms: seconds * 1000,
|
|
49
|
+
cacheControl: seconds === 0 ? false : `private, max-age=${seconds}`,
|
|
50
|
+
};
|
|
51
|
+
}
|