@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.141
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +293 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- 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 +131 -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 +85 -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/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- 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 +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- 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/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- 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 +2508 -158
- 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 +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +24 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-snapshot.ts +368 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +222 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1113 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +173 -35
- package/src/index.ts +241 -73
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- 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 +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- 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 +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +527 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +897 -0
- package/src/rsc/shell-serve.ts +124 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +459 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +848 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +443 -135
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -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 +199 -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 +584 -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 +76 -98
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +44 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +280 -0
- package/src/urls/pattern-types.ts +160 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- 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/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- 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 +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -7,58 +7,70 @@
|
|
|
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";
|
|
11
|
+
import { safeDecodeURIComponent } from "./url-params.js";
|
|
12
|
+
import { parsePattern, type ParsedSegment } from "./parse-pattern.js";
|
|
13
|
+
|
|
14
|
+
// `parsePattern`/`ParsedSegment` live in the dependency-free `parse-pattern.ts`
|
|
15
|
+
// so the client reverse helper can import them without dragging this module's
|
|
16
|
+
// server-only deps into the browser bundle. Re-exported here for existing
|
|
17
|
+
// importers (build/route-trie, middleware, tests).
|
|
18
|
+
export { parsePattern, type ParsedSegment };
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
|
-
*
|
|
21
|
+
* Compiled pattern result containing regex, param metadata, and trailing slash info.
|
|
13
22
|
*/
|
|
14
|
-
export interface
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
export interface CompiledPattern {
|
|
24
|
+
regex: RegExp;
|
|
25
|
+
paramNames: string[];
|
|
26
|
+
hasTrailingSlash: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Param-name → allowed values for constrained params (e.g. `:lang(en|gb)`).
|
|
29
|
+
* Validated against the **decoded** param value after regex extraction so
|
|
30
|
+
* a URL like `/en%20GB` still matches `:lang(en GB)` — matching the trie
|
|
31
|
+
* path's behavior (trie-matching.ts:validateAndBuild).
|
|
32
|
+
*/
|
|
33
|
+
constraints?: Record<string, string[]>;
|
|
34
|
+
/**
|
|
35
|
+
* The pattern's catch-all param, if any (`*` for bare `/*`, the name for a
|
|
36
|
+
* named `:name+`/`:name*`). A zero-or-more catch-all (`oneOrMore: false`)
|
|
37
|
+
* whose optional group is absent binds "" rather than being omitted — so
|
|
38
|
+
* `/docs` matches `/docs/:slug*` with `slug === ""`. `oneOrMore` keeps the
|
|
39
|
+
* same polarity as `ParsedSegment.oneOrMore` and the trie's `w1`.
|
|
40
|
+
*/
|
|
41
|
+
catchAll?: { name: string; oneOrMore: boolean };
|
|
19
42
|
}
|
|
20
43
|
|
|
44
|
+
// Module-level cache for compiled patterns. Route patterns are a finite set
|
|
45
|
+
// defined at build time, so this map is bounded by the number of routes.
|
|
46
|
+
const compiledPatternCache = new Map<string, CompiledPattern>();
|
|
47
|
+
|
|
21
48
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* Supports:
|
|
25
|
-
* - Static: /blog, /about
|
|
26
|
-
* - Params: /:slug, /:id
|
|
27
|
-
* - Optional: /:locale?, /:page?
|
|
28
|
-
* - Constrained: /:locale(en|gb), /:type(post|page)
|
|
29
|
-
* - Optional + Constrained: /:locale(en|gb)?
|
|
30
|
-
* - Wildcard: /*
|
|
49
|
+
* Get a compiled pattern from cache or compile and cache it.
|
|
50
|
+
* Avoids O(routes) regex compilations per request in the fallback path.
|
|
31
51
|
*/
|
|
32
|
-
export function
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (wildcard) {
|
|
48
|
-
segments.push({ type: "wildcard", value: "*", optional: false });
|
|
49
|
-
} else if (paramName) {
|
|
50
|
-
segments.push({
|
|
51
|
-
type: "param",
|
|
52
|
-
value: paramName,
|
|
53
|
-
optional: optional === "?",
|
|
54
|
-
constraint: constraint ? constraint.split("|") : undefined,
|
|
55
|
-
});
|
|
56
|
-
} else if (staticText) {
|
|
57
|
-
segments.push({ type: "static", value: staticText, optional: false });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
52
|
+
export function getCompiledPattern(pattern: string): CompiledPattern {
|
|
53
|
+
let compiled = compiledPatternCache.get(pattern);
|
|
54
|
+
if (compiled) return compiled;
|
|
55
|
+
compiled = compilePattern(pattern);
|
|
56
|
+
compiledPatternCache.set(pattern, compiled);
|
|
57
|
+
return compiled;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Return the current size of the compiled pattern cache.
|
|
62
|
+
* Exposed for testing.
|
|
63
|
+
*/
|
|
64
|
+
export function getPatternCacheSize(): number {
|
|
65
|
+
return compiledPatternCache.size;
|
|
66
|
+
}
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Clear the compiled pattern cache.
|
|
70
|
+
* Exposed for testing.
|
|
71
|
+
*/
|
|
72
|
+
export function clearPatternCache(): void {
|
|
73
|
+
compiledPatternCache.clear();
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
/**
|
|
@@ -78,12 +90,7 @@ export function parsePattern(pattern: string): ParsedSegment[] {
|
|
|
78
90
|
* compilePattern("/:locale(en|gb)/blog") // matches /en/blog or /gb/blog
|
|
79
91
|
* compilePattern("/:locale(en|gb)?/blog") // matches /blog, /en/blog, or /gb/blog
|
|
80
92
|
*/
|
|
81
|
-
export function compilePattern(pattern: string): {
|
|
82
|
-
regex: RegExp;
|
|
83
|
-
paramNames: string[];
|
|
84
|
-
optionalParams: Set<string>;
|
|
85
|
-
hasTrailingSlash: boolean;
|
|
86
|
-
} {
|
|
93
|
+
export function compilePattern(pattern: string): CompiledPattern {
|
|
87
94
|
// Detect if pattern has trailing slash (but not just "/")
|
|
88
95
|
const hasTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
89
96
|
// Remove trailing slash for parsing (we'll add it back to regex if needed)
|
|
@@ -91,38 +98,75 @@ export function compilePattern(pattern: string): {
|
|
|
91
98
|
|
|
92
99
|
const segments = parsePattern(normalizedPattern);
|
|
93
100
|
const paramNames: string[] = [];
|
|
94
|
-
|
|
101
|
+
let constraints: Record<string, string[]> | undefined;
|
|
102
|
+
let catchAll: { name: string; oneOrMore: boolean } | undefined;
|
|
95
103
|
|
|
96
104
|
let regexPattern = "";
|
|
97
105
|
|
|
98
106
|
for (const segment of segments) {
|
|
99
107
|
if (segment.type === "wildcard") {
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
// Wildcards capture the remainder under `segment.value` ("*" for the bare
|
|
109
|
+
// form, the param name for a named catch-all).
|
|
110
|
+
paramNames.push(segment.value);
|
|
111
|
+
catchAll = { name: segment.value, oneOrMore: Boolean(segment.oneOrMore) };
|
|
112
|
+
if (segment.oneOrMore) {
|
|
113
|
+
// `:name+` — one-or-more, rejects the zero-segment (bare-prefix) case.
|
|
114
|
+
regexPattern += "/(.+)";
|
|
115
|
+
} else {
|
|
116
|
+
// Zero-or-more catch-all: named `:name*` OR the bare `/*` (both parse to
|
|
117
|
+
// `oneOrMore: false`). The whole `/segment` is optional so the bare
|
|
118
|
+
// prefix matches directly, aligning the regex fallback with the trie
|
|
119
|
+
// (which already matches the bare prefix binding "" — trie-matching.ts);
|
|
120
|
+
// buildParamsFromMatch binds "" when the optional group is absent.
|
|
121
|
+
//
|
|
122
|
+
// The bare `/*` previously used a required `/(.*)`, so `/files/*` failed
|
|
123
|
+
// to match `/files` and fell through to trailing-slash normalization,
|
|
124
|
+
// emitting a corrupt `/file` redirect instead of a match (issue #636,
|
|
125
|
+
// parity row C1). It is the same alignment #635 made for named `:name*`.
|
|
126
|
+
regexPattern += "(?:/(.*))?";
|
|
127
|
+
}
|
|
102
128
|
} else if (segment.type === "param") {
|
|
103
129
|
paramNames.push(segment.value);
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
const suffixPattern = segment.suffix ? escapeRegExp(segment.suffix) : "";
|
|
131
|
+
// Constrained params capture anything here; the allowed values are
|
|
132
|
+
// checked post-decode in findMatch so URL-encoded constraint values
|
|
133
|
+
// (e.g. `:lang(en GB)` via `/en%20GB`) still match.
|
|
134
|
+
const valuePattern = segment.suffix ? "([^/]+?)" : "([^/]+)";
|
|
135
|
+
|
|
136
|
+
if (segment.constraint) {
|
|
137
|
+
(constraints ??= {})[segment.value] = segment.constraint;
|
|
138
|
+
}
|
|
107
139
|
|
|
108
140
|
if (segment.optional) {
|
|
109
|
-
optionalParams.add(segment.value);
|
|
110
141
|
// Optional: make the whole /segment optional
|
|
111
|
-
regexPattern += `(?:/${valuePattern})?`;
|
|
142
|
+
regexPattern += `(?:/${valuePattern}${suffixPattern})?`;
|
|
112
143
|
} else {
|
|
113
|
-
regexPattern += `/${valuePattern}`;
|
|
144
|
+
regexPattern += `/${valuePattern}${suffixPattern}`;
|
|
114
145
|
}
|
|
115
146
|
} else {
|
|
116
147
|
// Static segment
|
|
117
|
-
regexPattern += `/${
|
|
148
|
+
regexPattern += `/${escapeRegExp(segment.value)}`;
|
|
118
149
|
}
|
|
119
150
|
}
|
|
120
151
|
|
|
121
|
-
// Handle root path
|
|
122
152
|
if (regexPattern === "") {
|
|
123
153
|
regexPattern = "/";
|
|
124
154
|
}
|
|
125
155
|
|
|
156
|
+
// Patterns of only optional segments (e.g. `/:locale?`, `/:a?/:b?`) need
|
|
157
|
+
// an explicit `/` alternative so a bare `/` matches the absent form. The
|
|
158
|
+
// optional template `(?:/X)?` matches `/X` or empty string, but pathnames
|
|
159
|
+
// are never empty. Arises from `include("/:locale?", routes)` + inner
|
|
160
|
+
// `path("/")`. Skip when an explicit trailing slash already anchors the
|
|
161
|
+
// match.
|
|
162
|
+
const hasOnlyOptionalSegments =
|
|
163
|
+
!hasTrailingSlash &&
|
|
164
|
+
segments.length > 0 &&
|
|
165
|
+
segments.every((segment) => segment.type === "param" && segment.optional);
|
|
166
|
+
if (hasOnlyOptionalSegments) {
|
|
167
|
+
regexPattern = `(?:/|${regexPattern})`;
|
|
168
|
+
}
|
|
169
|
+
|
|
126
170
|
// Add trailing slash to regex if pattern has one
|
|
127
171
|
if (hasTrailingSlash) {
|
|
128
172
|
regexPattern += "/";
|
|
@@ -131,16 +175,69 @@ export function compilePattern(pattern: string): {
|
|
|
131
175
|
return {
|
|
132
176
|
regex: new RegExp(`^${regexPattern}$`),
|
|
133
177
|
paramNames,
|
|
134
|
-
optionalParams,
|
|
135
178
|
hasTrailingSlash,
|
|
179
|
+
...(constraints ? { constraints } : {}),
|
|
180
|
+
...(catchAll ? { catchAll } : {}),
|
|
136
181
|
};
|
|
137
182
|
}
|
|
138
183
|
|
|
139
184
|
/**
|
|
140
|
-
*
|
|
185
|
+
* Validate decoded params against a compiled pattern's constraints.
|
|
186
|
+
* Returns false if any constrained param has a non-empty value not in the
|
|
187
|
+
* allowed list. Absent optionals (key missing or `undefined`) are allowed;
|
|
188
|
+
* `""` is also tolerated as "absent" so user-provided params or fixtures
|
|
189
|
+
* that pass empty strings explicitly behave the same way.
|
|
141
190
|
*/
|
|
142
|
-
function
|
|
143
|
-
|
|
191
|
+
function satisfiesConstraints(
|
|
192
|
+
params: Record<string, string>,
|
|
193
|
+
constraints: Record<string, string[]> | undefined,
|
|
194
|
+
): boolean {
|
|
195
|
+
if (!constraints) return true;
|
|
196
|
+
for (const name in constraints) {
|
|
197
|
+
const value = params[name];
|
|
198
|
+
if (
|
|
199
|
+
value !== undefined &&
|
|
200
|
+
value !== "" &&
|
|
201
|
+
!constraints[name].includes(value)
|
|
202
|
+
) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Build the named-params record from a regex match. Optional segments that
|
|
211
|
+
* didn't capture leave the corresponding group `undefined`; we skip those
|
|
212
|
+
* keys so `ctx.params.<name>` reads as `undefined` rather than `""`. This
|
|
213
|
+
* keeps the runtime aligned with the `ExtractParams` type and matches the
|
|
214
|
+
* trie matcher's contract (see `trie-matching.ts:validateAndBuild`).
|
|
215
|
+
*
|
|
216
|
+
* A zero-or-more catch-all (`compiled.catchAll`, `oneOrMore: false`) whose
|
|
217
|
+
* optional group didn't capture binds "" instead of being omitted, so `/docs`
|
|
218
|
+
* matches `/docs/:slug*` with `slug === ""`. Exported so the `renderRoute`
|
|
219
|
+
* testing harness (`matchLeaf`) shares this exact logic instead of forking it.
|
|
220
|
+
*/
|
|
221
|
+
export function buildParamsFromMatch(
|
|
222
|
+
match: RegExpExecArray,
|
|
223
|
+
paramNames: string[],
|
|
224
|
+
catchAll?: { name: string; oneOrMore: boolean },
|
|
225
|
+
): Record<string, string> {
|
|
226
|
+
const params: Record<string, string> = {};
|
|
227
|
+
paramNames.forEach((name, index) => {
|
|
228
|
+
const captured = match[index + 1];
|
|
229
|
+
if (captured !== undefined) {
|
|
230
|
+
// A catch-all remainder decodes identically whether split-per-segment or
|
|
231
|
+
// whole-string (a literal `/` never lives inside a `%XX` escape), so a
|
|
232
|
+
// single decode is correct and cheapest.
|
|
233
|
+
params[name] = safeDecodeURIComponent(captured);
|
|
234
|
+
} else if (catchAll && name === catchAll.name && !catchAll.oneOrMore) {
|
|
235
|
+
// A zero-or-more catch-all (`:name*` or the bare `/*`) whose optional
|
|
236
|
+
// group was absent binds "" rather than being omitted.
|
|
237
|
+
params[name] = "";
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
return params;
|
|
144
241
|
}
|
|
145
242
|
|
|
146
243
|
/**
|
|
@@ -159,43 +256,58 @@ function escapeRegex(str: string): string {
|
|
|
159
256
|
export function extractStaticPrefix(pattern: string): string {
|
|
160
257
|
if (!pattern || pattern === "/") return "";
|
|
161
258
|
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
259
|
+
// Walk segments and stop at the first that is a real param (`:name`) or a
|
|
260
|
+
// wildcard (`*`). A literal `:` or `*` not at a segment boundary (e.g. the
|
|
261
|
+
// `a:b` in `/a:b/c/:id`, or `tel:+1`) is a STATIC segment and must NOT
|
|
262
|
+
// terminate the prefix — `pattern.indexOf(":")` misread it as a param marker,
|
|
263
|
+
// returning "" and dropping the findMatch fast-skip optimization for that
|
|
264
|
+
// entry on every request. Classification mirrors parsePattern: a leading `:`
|
|
265
|
+
// marks a param, a leading `*` marks a wildcard.
|
|
266
|
+
const hasLeadingSlash = pattern.startsWith("/");
|
|
267
|
+
const body = hasLeadingSlash ? pattern.slice(1) : pattern;
|
|
268
|
+
const segments = body.split("/");
|
|
269
|
+
|
|
270
|
+
const staticSegments: string[] = [];
|
|
271
|
+
for (const segment of segments) {
|
|
272
|
+
if (segment.startsWith(":") || segment.startsWith("*")) {
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
staticSegments.push(segment);
|
|
178
276
|
}
|
|
179
277
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
278
|
+
// No leading static segment (first segment is a param/wildcard) -> no prefix.
|
|
279
|
+
if (staticSegments.length === 0) return "";
|
|
280
|
+
// Every segment was static (no param/wildcard) -> the whole pattern is the
|
|
281
|
+
// prefix. Preserve a trailing slash only when it existed in the input; a
|
|
282
|
+
// split of "/a/b" yields ["a","b"] (no empty tail) so a re-join is exact.
|
|
283
|
+
if (staticSegments.length === segments.length) return pattern;
|
|
184
284
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return "";
|
|
189
|
-
}
|
|
285
|
+
const prefix = staticSegments.join("/");
|
|
286
|
+
return hasLeadingSlash ? "/" + prefix : prefix;
|
|
287
|
+
}
|
|
190
288
|
|
|
191
|
-
|
|
289
|
+
/**
|
|
290
|
+
* Join a URL prefix to a sub-prefix, collapsing the duplicate slash when the
|
|
291
|
+
* base ends with "/" and the sub-prefix starts with "/". This mirrors the
|
|
292
|
+
* canonical join in `include()` (urls/include-helper.ts) and `runWithPrefixes`
|
|
293
|
+
* (server/context.ts) so a nested lazy include's runtime staticPrefix matches
|
|
294
|
+
* the build-time trie's `sp` (e.g. `include("/parent/", …)` containing
|
|
295
|
+
* `include("/child", …)` resolves to `/parent/child`, not `/parent//child`).
|
|
296
|
+
*/
|
|
297
|
+
export function joinPrefix(base: string | undefined, prefix: string): string {
|
|
298
|
+
if (!base) return prefix;
|
|
299
|
+
return base.endsWith("/") && prefix.startsWith("/")
|
|
300
|
+
? base + prefix.slice(1)
|
|
301
|
+
: base + prefix;
|
|
192
302
|
}
|
|
193
303
|
|
|
194
304
|
/**
|
|
195
305
|
* Match a pathname against registered routes
|
|
196
306
|
*
|
|
197
|
-
* Note: Optional params that are absent in the path
|
|
198
|
-
*
|
|
307
|
+
* Note: Optional params that are absent in the path are omitted from the
|
|
308
|
+
* returned `params` (read as `undefined`), matching the trie matcher and
|
|
309
|
+
* the `ExtractParams<"/:locale?/...">` type. Use the pattern definition to
|
|
310
|
+
* determine which keys are optional.
|
|
199
311
|
*
|
|
200
312
|
* Trailing slash handling (priority order):
|
|
201
313
|
* 1. Per-route `trailingSlash` config from route()
|
|
@@ -213,10 +325,7 @@ export interface RouteMatchResult<TEnv = any> {
|
|
|
213
325
|
entry: RouteEntry<TEnv>;
|
|
214
326
|
routeKey: string;
|
|
215
327
|
params: Record<string, string>;
|
|
216
|
-
optionalParams: Set<string>;
|
|
217
328
|
redirectTo?: string;
|
|
218
|
-
/** Ancestry shortCodes for layout pruning (from trie match) */
|
|
219
|
-
ancestry?: string[];
|
|
220
329
|
/** Route has pre-rendered data available (from trie) */
|
|
221
330
|
pr?: true;
|
|
222
331
|
/** Passthrough: handler kept for live fallback on unknown params (from trie) */
|
|
@@ -240,7 +349,7 @@ export interface LazyEvaluationNeeded<TEnv = any> {
|
|
|
240
349
|
* Type guard to check if result is a lazy evaluation needed response
|
|
241
350
|
*/
|
|
242
351
|
export function isLazyEvaluationNeeded<TEnv>(
|
|
243
|
-
result: RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null
|
|
352
|
+
result: RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null,
|
|
244
353
|
): result is LazyEvaluationNeeded<TEnv> {
|
|
245
354
|
return result !== null && "lazyEntry" in result;
|
|
246
355
|
}
|
|
@@ -261,12 +370,16 @@ export function enableMatchDebug(enabled: boolean): void {
|
|
|
261
370
|
}
|
|
262
371
|
|
|
263
372
|
export function getMatchDebugStats(): MatchDebugStats {
|
|
264
|
-
return {
|
|
373
|
+
return {
|
|
374
|
+
entriesChecked: debugStats.entriesChecked,
|
|
375
|
+
entriesSkipped: debugStats.entriesSkipped,
|
|
376
|
+
routesChecked: debugStats.routesChecked,
|
|
377
|
+
};
|
|
265
378
|
}
|
|
266
379
|
|
|
267
380
|
export function findMatch<TEnv>(
|
|
268
381
|
pathname: string,
|
|
269
|
-
routesEntries: RouteEntry<TEnv>[]
|
|
382
|
+
routesEntries: RouteEntry<TEnv>[],
|
|
270
383
|
): RouteMatchResult<TEnv> | LazyEvaluationNeeded<TEnv> | null {
|
|
271
384
|
const effectiveDebug = debugEnabled || isRouterDebugEnabled();
|
|
272
385
|
|
|
@@ -282,15 +395,14 @@ export function findMatch<TEnv>(
|
|
|
282
395
|
}
|
|
283
396
|
}
|
|
284
397
|
|
|
285
|
-
const pathnameHasTrailingSlash =
|
|
398
|
+
const pathnameHasTrailingSlash =
|
|
399
|
+
pathname.length > 1 && pathname.endsWith("/");
|
|
286
400
|
// Try alternate pathname for redirect matching
|
|
287
401
|
const alternatePathname = pathnameHasTrailingSlash
|
|
288
402
|
? pathname.slice(0, -1)
|
|
289
403
|
: pathname + "/";
|
|
290
404
|
|
|
291
405
|
for (const entry of routesEntries) {
|
|
292
|
-
// Short-circuit: skip entry if pathname doesn't start with static prefix
|
|
293
|
-
// staticPrefix is pre-computed at registration time, so this is O(1)
|
|
294
406
|
if (entry.staticPrefix && !pathname.startsWith(entry.staticPrefix)) {
|
|
295
407
|
if (effectiveDebug) {
|
|
296
408
|
debugStats.entriesSkipped++;
|
|
@@ -302,8 +414,6 @@ export function findMatch<TEnv>(
|
|
|
302
414
|
continue;
|
|
303
415
|
}
|
|
304
416
|
|
|
305
|
-
// Check if this is a lazy entry that needs evaluation
|
|
306
|
-
// When staticPrefix matches but routes are not yet populated, signal caller to evaluate
|
|
307
417
|
if (entry.lazy && !entry.lazyEvaluated) {
|
|
308
418
|
if (effectiveDebug) {
|
|
309
419
|
debugLog("findMatch", "lazy entry requires evaluation", {
|
|
@@ -324,7 +434,6 @@ export function findMatch<TEnv>(
|
|
|
324
434
|
debugStats.routesChecked++;
|
|
325
435
|
}
|
|
326
436
|
|
|
327
|
-
// Join prefix and pattern, handling edge cases
|
|
328
437
|
let fullPattern: string;
|
|
329
438
|
if (entry.prefix === "" || entry.prefix === "/") {
|
|
330
439
|
fullPattern = pattern;
|
|
@@ -334,22 +443,26 @@ export function findMatch<TEnv>(
|
|
|
334
443
|
fullPattern = entry.prefix + pattern;
|
|
335
444
|
}
|
|
336
445
|
|
|
337
|
-
const { regex, paramNames,
|
|
338
|
-
|
|
339
|
-
// Get trailing slash mode for this route (per-route config or pattern-based)
|
|
340
|
-
const trailingSlashMode: TrailingSlashMode | undefined = entry.trailingSlash?.[routeKey];
|
|
446
|
+
const { regex, paramNames, hasTrailingSlash, constraints, catchAll } =
|
|
447
|
+
getCompiledPattern(fullPattern);
|
|
341
448
|
|
|
449
|
+
const trailingSlashMode: TrailingSlashMode | undefined =
|
|
450
|
+
entry.trailingSlash?.[routeKey];
|
|
342
451
|
|
|
343
|
-
|
|
344
|
-
|
|
452
|
+
const prFlag = entry.prerenderRouteKeys?.has(routeKey)
|
|
453
|
+
? { pr: true as const }
|
|
454
|
+
: {};
|
|
455
|
+
const ptFlag = entry.passthroughRouteKeys?.has(routeKey)
|
|
456
|
+
? { pt: true as const }
|
|
457
|
+
: {};
|
|
345
458
|
|
|
346
|
-
// Try exact match first
|
|
347
459
|
const match = regex.exec(pathname);
|
|
348
460
|
if (match) {
|
|
349
|
-
const params
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
461
|
+
const params = buildParamsFromMatch(match, paramNames, catchAll);
|
|
462
|
+
|
|
463
|
+
if (!satisfiesConstraints(params, constraints)) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
353
466
|
|
|
354
467
|
if (effectiveDebug) {
|
|
355
468
|
debugLog("findMatch", "matched route", {
|
|
@@ -359,47 +472,103 @@ export function findMatch<TEnv>(
|
|
|
359
472
|
});
|
|
360
473
|
}
|
|
361
474
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
475
|
+
if (
|
|
476
|
+
trailingSlashMode === "always" &&
|
|
477
|
+
!pathnameHasTrailingSlash &&
|
|
478
|
+
pathname !== "/"
|
|
479
|
+
) {
|
|
480
|
+
return {
|
|
481
|
+
entry,
|
|
482
|
+
routeKey,
|
|
483
|
+
params,
|
|
484
|
+
redirectTo: pathname + "/",
|
|
485
|
+
...prFlag,
|
|
486
|
+
...ptFlag,
|
|
487
|
+
};
|
|
366
488
|
} else if (trailingSlashMode === "never" && pathnameHasTrailingSlash) {
|
|
367
|
-
|
|
368
|
-
|
|
489
|
+
return {
|
|
490
|
+
entry,
|
|
491
|
+
routeKey,
|
|
492
|
+
params,
|
|
493
|
+
redirectTo: pathname.slice(0, -1),
|
|
494
|
+
...prFlag,
|
|
495
|
+
...ptFlag,
|
|
496
|
+
};
|
|
369
497
|
}
|
|
370
498
|
|
|
371
|
-
return {
|
|
499
|
+
return {
|
|
500
|
+
entry,
|
|
501
|
+
routeKey,
|
|
502
|
+
params,
|
|
503
|
+
...prFlag,
|
|
504
|
+
...ptFlag,
|
|
505
|
+
};
|
|
372
506
|
}
|
|
373
507
|
|
|
374
|
-
// Try alternate pathname (opposite trailing slash)
|
|
375
508
|
const altMatch = regex.exec(alternatePathname);
|
|
376
509
|
if (altMatch) {
|
|
377
|
-
const params
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
510
|
+
const params = buildParamsFromMatch(altMatch, paramNames, catchAll);
|
|
511
|
+
|
|
512
|
+
if (!satisfiesConstraints(params, constraints)) {
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
381
515
|
|
|
382
|
-
// Determine redirect behavior based on mode
|
|
383
516
|
if (trailingSlashMode === "ignore") {
|
|
384
|
-
|
|
385
|
-
|
|
517
|
+
return {
|
|
518
|
+
entry,
|
|
519
|
+
routeKey,
|
|
520
|
+
params,
|
|
521
|
+
...prFlag,
|
|
522
|
+
...ptFlag,
|
|
523
|
+
};
|
|
386
524
|
} else if (trailingSlashMode === "never") {
|
|
387
|
-
// Redirect to no trailing slash
|
|
388
525
|
if (pathnameHasTrailingSlash) {
|
|
389
|
-
return {
|
|
526
|
+
return {
|
|
527
|
+
entry,
|
|
528
|
+
routeKey,
|
|
529
|
+
params,
|
|
530
|
+
redirectTo: alternatePathname,
|
|
531
|
+
...prFlag,
|
|
532
|
+
...ptFlag,
|
|
533
|
+
};
|
|
390
534
|
}
|
|
391
|
-
return {
|
|
535
|
+
return {
|
|
536
|
+
entry,
|
|
537
|
+
routeKey,
|
|
538
|
+
params,
|
|
539
|
+
...prFlag,
|
|
540
|
+
...ptFlag,
|
|
541
|
+
};
|
|
392
542
|
} else if (trailingSlashMode === "always") {
|
|
393
|
-
// Redirect to with trailing slash
|
|
394
543
|
if (!pathnameHasTrailingSlash) {
|
|
395
|
-
return {
|
|
544
|
+
return {
|
|
545
|
+
entry,
|
|
546
|
+
routeKey,
|
|
547
|
+
params,
|
|
548
|
+
redirectTo: alternatePathname,
|
|
549
|
+
...prFlag,
|
|
550
|
+
...ptFlag,
|
|
551
|
+
};
|
|
396
552
|
}
|
|
397
|
-
return {
|
|
553
|
+
return {
|
|
554
|
+
entry,
|
|
555
|
+
routeKey,
|
|
556
|
+
params,
|
|
557
|
+
...prFlag,
|
|
558
|
+
...ptFlag,
|
|
559
|
+
};
|
|
398
560
|
} else {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return {
|
|
561
|
+
const canonicalPath = hasTrailingSlash
|
|
562
|
+
? alternatePathname
|
|
563
|
+
: pathname.slice(0, -1);
|
|
564
|
+
return {
|
|
565
|
+
entry,
|
|
566
|
+
routeKey,
|
|
567
|
+
params,
|
|
568
|
+
redirectTo: canonicalPath,
|
|
569
|
+
...prFlag,
|
|
570
|
+
...ptFlag,
|
|
571
|
+
};
|
|
403
572
|
}
|
|
404
573
|
}
|
|
405
574
|
}
|
|
@@ -416,7 +585,7 @@ export function* traverseBack(entry: EntryData): Generator<EntryData> {
|
|
|
416
585
|
let current: EntryData | null = entry;
|
|
417
586
|
const items = [] as EntryData[];
|
|
418
587
|
while (current !== null) {
|
|
419
|
-
items.push(current);
|
|
588
|
+
items.push(current);
|
|
420
589
|
current = current.parent;
|
|
421
590
|
}
|
|
422
591
|
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
|
+
}
|