@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- 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 +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- 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 +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- 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 +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -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 +2170 -377
- 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 +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- 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 +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- 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 +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- 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 +35 -15
- 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 +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -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 +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- 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 +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- 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 +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- 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
package/src/route-map-builder.ts
CHANGED
|
@@ -8,15 +8,10 @@
|
|
|
8
8
|
* See docs/manifests.md for the full data flow.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
// Singleton route map instance - populated incrementally as routes are encountered
|
|
12
11
|
let globalRouteMap: Record<string, string> = {};
|
|
13
12
|
|
|
14
|
-
// Cached complete manifest - includes all routes (including lazy includes)
|
|
15
|
-
// Set from runtime cache or build-time import
|
|
16
13
|
let cachedManifest: Record<string, string> | null = null;
|
|
17
14
|
|
|
18
|
-
// Pre-computed route entries from build-time prefix tree leaf nodes.
|
|
19
|
-
// Used by evaluateLazyEntry() to skip running the handler for route matching.
|
|
20
15
|
let cachedPrecomputedEntries: Array<{
|
|
21
16
|
staticPrefix: string;
|
|
22
17
|
routes: Record<string, string>;
|
|
@@ -25,11 +20,25 @@ let cachedPrecomputedEntries: Array<{
|
|
|
25
20
|
/**
|
|
26
21
|
* Register routes into the global route map.
|
|
27
22
|
* Routes are merged with any existing registered routes.
|
|
28
|
-
* Called by createRouter() during module evaluation
|
|
23
|
+
* Called by createRouter() during module evaluation, and by lazy-include
|
|
24
|
+
* expansion (src/router/lazy-includes.ts) with each expansion's route delta.
|
|
25
|
+
*
|
|
26
|
+
* Merges IN PLACE — O(|map|), not O(total routes). The previous
|
|
27
|
+
* `globalRouteMap = { ...globalRouteMap, ...map }` copy made every
|
|
28
|
+
* lazy-include first hit O(total routes) on the request path: with a 26k-route
|
|
29
|
+
* manifest the spread measured 8.9ms/call (M4, node), paid once per level of a
|
|
30
|
+
* nested async-include chain (3 calls on a 3-level chain — the 464ms edge
|
|
31
|
+
* cold-hit in issue #666).
|
|
32
|
+
*
|
|
33
|
+
* In-place mutation is safe because every getGlobalRouteMap() consumer reads
|
|
34
|
+
* it fresh per call (server/request-context.ts, rsc/loader-fetch.ts,
|
|
35
|
+
* router/intercept-resolution.ts, testing/generated-routes.ts,
|
|
36
|
+
* rsc/manifest-init.ts) — none memoizes the returned reference. If you add a
|
|
37
|
+
* consumer that caches the map object, it will now observe later
|
|
38
|
+
* registrations; snapshot it yourself if you need frozen contents.
|
|
29
39
|
*/
|
|
30
40
|
export function registerRouteMap(map: Record<string, string>): void {
|
|
31
|
-
|
|
32
|
-
globalRouteMap = { ...globalRouteMap, ...map };
|
|
41
|
+
Object.assign(globalRouteMap, map);
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
/**
|
|
@@ -43,7 +52,6 @@ export function registerRouteMap(map: Record<string, string>): void {
|
|
|
43
52
|
* @internal
|
|
44
53
|
*/
|
|
45
54
|
export function getGlobalRouteMap(): Record<string, string> {
|
|
46
|
-
// Cached manifest is complete (includes lazy routes), so prefer it
|
|
47
55
|
if (cachedManifest) {
|
|
48
56
|
return cachedManifest;
|
|
49
57
|
}
|
|
@@ -145,6 +153,7 @@ export function clearAllRouterData(): void {
|
|
|
145
153
|
perRouterManifestMap.clear();
|
|
146
154
|
perRouterTrieMap.clear();
|
|
147
155
|
perRouterPrecomputedEntriesMap.clear();
|
|
156
|
+
authoritativeTrieRouters.clear();
|
|
148
157
|
}
|
|
149
158
|
|
|
150
159
|
export function setRouterManifest(
|
|
@@ -168,6 +177,23 @@ export function setRouterTrie(
|
|
|
168
177
|
perRouterTrieMap.set(routerId, trie);
|
|
169
178
|
}
|
|
170
179
|
|
|
180
|
+
// Routers whose trie came from the COMPLETE build manifest (deserialized via
|
|
181
|
+
// ensureRouterManifest). For these, a trie miss is a real 404 and findMatch
|
|
182
|
+
// skips the regex fallback scan — the only remaining route-count-proportional
|
|
183
|
+
// match path (#664). Dev rebuilds (manifest-init.ts, router-discovery HMR
|
|
184
|
+
// pushes) deliberately never mark authoritative: the dev-only trie-gap warning
|
|
185
|
+
// in find-match.ts depends on the fallback running on misses, and dev route
|
|
186
|
+
// churn (HMR, dev-time routes) makes a stale-trie 404 unacceptable there.
|
|
187
|
+
const authoritativeTrieRouters: Set<string> = new Set();
|
|
188
|
+
|
|
189
|
+
export function markRouterTrieAuthoritative(routerId: string): void {
|
|
190
|
+
authoritativeTrieRouters.add(routerId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function isRouterTrieAuthoritative(routerId: string): boolean {
|
|
194
|
+
return authoritativeTrieRouters.has(routerId);
|
|
195
|
+
}
|
|
196
|
+
|
|
171
197
|
export function getRouterTrie(
|
|
172
198
|
routerId: string,
|
|
173
199
|
): import("./build/route-trie.js").TrieNode | undefined {
|
|
@@ -210,7 +236,12 @@ export async function ensureRouterManifest(routerId: string): Promise<void> {
|
|
|
210
236
|
if (loader) {
|
|
211
237
|
const mod = await loader();
|
|
212
238
|
if (mod.manifest) perRouterManifestMap.set(routerId, mod.manifest);
|
|
213
|
-
if (mod.trie)
|
|
239
|
+
if (mod.trie) {
|
|
240
|
+
perRouterTrieMap.set(routerId, mod.trie);
|
|
241
|
+
// A trie serialized into the build manifest comes from complete
|
|
242
|
+
// discovery — misses are authoritative 404s (see find-match.ts).
|
|
243
|
+
markRouterTrieAuthoritative(routerId);
|
|
244
|
+
}
|
|
214
245
|
if (mod.precomputedEntries)
|
|
215
246
|
perRouterPrecomputedEntriesMap.set(routerId, mod.precomputedEntries);
|
|
216
247
|
routerManifestLoaders.delete(routerId);
|
|
@@ -231,10 +262,6 @@ export function waitForManifestReady(): Promise<void> | null {
|
|
|
231
262
|
return manifestReadyPromise;
|
|
232
263
|
}
|
|
233
264
|
|
|
234
|
-
// ============================================================================
|
|
235
|
-
// Route Scope Registry
|
|
236
|
-
// ============================================================================
|
|
237
|
-
|
|
238
265
|
// Tracks whether each route is at root scope (no named include boundary above).
|
|
239
266
|
// Used by dot-local reverse resolution to decide whether bare-name fallback
|
|
240
267
|
// is allowed after scoped lookups are exhausted.
|
|
@@ -259,14 +286,8 @@ export function isRouteRootScoped(routeName: string): boolean | undefined {
|
|
|
259
286
|
return rootScopeRoutes.get(routeName);
|
|
260
287
|
}
|
|
261
288
|
|
|
262
|
-
// ============================================================================
|
|
263
|
-
// Search Schema Registry
|
|
264
|
-
// ============================================================================
|
|
265
|
-
|
|
266
289
|
import type { SearchSchema } from "./search-params.js";
|
|
267
290
|
|
|
268
|
-
// Global search schema map: route name -> search schema descriptor.
|
|
269
|
-
// Populated by path() when a search option is provided.
|
|
270
291
|
const globalSearchSchemas: Map<string, SearchSchema> = new Map();
|
|
271
292
|
|
|
272
293
|
export function registerSearchSchema(
|
package/src/route-types.ts
CHANGED
|
@@ -98,10 +98,6 @@ export type NotFoundBoundaryItem = {
|
|
|
98
98
|
type: "notFoundBoundary";
|
|
99
99
|
uses?: AllUseItems[];
|
|
100
100
|
};
|
|
101
|
-
export type WhenItem = {
|
|
102
|
-
name: string;
|
|
103
|
-
type: "when";
|
|
104
|
-
};
|
|
105
101
|
export type CacheItem = {
|
|
106
102
|
name: string;
|
|
107
103
|
type: "cache";
|
|
@@ -231,7 +227,6 @@ export type InterceptUseItem =
|
|
|
231
227
|
| NotFoundBoundaryItem
|
|
232
228
|
| LayoutItem
|
|
233
229
|
| RouteItem
|
|
234
|
-
| WhenItem
|
|
235
230
|
| TransitionItem;
|
|
236
231
|
export type LoaderUseItem = RevalidateItem | CacheItem;
|
|
237
232
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { EntryData } from "../server/context.js";
|
|
10
|
+
import type { NegotiateVariant } from "../build/route-trie.js";
|
|
10
11
|
import type { CollectedMiddleware } from "./middleware-types.js";
|
|
11
12
|
import { collectRouteMiddleware } from "./middleware.js";
|
|
12
13
|
import { loadManifest } from "./manifest.js";
|
|
@@ -14,7 +15,6 @@ import { traverseBack } from "./pattern-matching.js";
|
|
|
14
15
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
15
16
|
import type { RouteSnapshot } from "./route-snapshot.js";
|
|
16
17
|
|
|
17
|
-
// Response type -> MIME type used for Accept header matching
|
|
18
18
|
export const RESPONSE_TYPE_MIME: Record<string, string> = {
|
|
19
19
|
json: "application/json",
|
|
20
20
|
text: "text/plain",
|
|
@@ -23,7 +23,6 @@ export const RESPONSE_TYPE_MIME: Record<string, string> = {
|
|
|
23
23
|
md: "text/markdown",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
// Reverse lookup: MIME type -> response type tag (e.g. "text/html" -> "html")
|
|
27
26
|
export const MIME_RESPONSE_TYPE: Record<string, string> = Object.fromEntries(
|
|
28
27
|
Object.entries(RESPONSE_TYPE_MIME).map(([tag, mime]) => [mime, tag]),
|
|
29
28
|
);
|
|
@@ -71,12 +70,10 @@ export function parseAcceptTypes(accept: string): AcceptEntry[] {
|
|
|
71
70
|
}
|
|
72
71
|
entries.push({ mime, q, order: i });
|
|
73
72
|
}
|
|
74
|
-
// Sort: highest q first, then lowest client order first (stable)
|
|
75
73
|
entries.sort((a, b) => b.q - a.q || a.order - b.order);
|
|
76
74
|
return entries;
|
|
77
75
|
}
|
|
78
76
|
|
|
79
|
-
// Sentinel response type for RSC routes in negotiation candidates
|
|
80
77
|
export const RSC_RESPONSE_TYPE = "__rsc__";
|
|
81
78
|
|
|
82
79
|
/**
|
|
@@ -85,15 +82,10 @@ export const RSC_RESPONSE_TYPE = "__rsc__";
|
|
|
85
82
|
* candidate serves that type. Wildcards match the first candidate.
|
|
86
83
|
* Falls back to the first candidate if nothing matches.
|
|
87
84
|
*/
|
|
88
|
-
export function pickNegotiateVariant
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
// Build a MIME -> candidate lookup for O(1) matching
|
|
93
|
-
const byCandidateMime = new Map<
|
|
94
|
-
string,
|
|
95
|
-
{ routeKey: string; responseType: string }
|
|
96
|
-
>();
|
|
85
|
+
export function pickNegotiateVariant<
|
|
86
|
+
T extends { routeKey: string; responseType: string },
|
|
87
|
+
>(acceptEntries: AcceptEntry[], candidates: T[]): T {
|
|
88
|
+
const byCandidateMime = new Map<string, T>();
|
|
97
89
|
for (const c of candidates) {
|
|
98
90
|
const mime =
|
|
99
91
|
c.responseType === RSC_RESPONSE_TYPE
|
|
@@ -106,9 +98,7 @@ export function pickNegotiateVariant(
|
|
|
106
98
|
|
|
107
99
|
for (const entry of acceptEntries) {
|
|
108
100
|
if (entry.q === 0) continue;
|
|
109
|
-
// Wildcard matches first candidate
|
|
110
101
|
if (entry.mime === "*/*") return candidates[0]!;
|
|
111
|
-
// Type wildcard (e.g. "text/*") -- match first candidate with that type
|
|
112
102
|
if (entry.mime.endsWith("/*")) {
|
|
113
103
|
const typePrefix = entry.mime.slice(0, entry.mime.indexOf("/"));
|
|
114
104
|
for (const [mime, candidate] of byCandidateMime) {
|
|
@@ -119,10 +109,52 @@ export function pickNegotiateVariant(
|
|
|
119
109
|
const match = byCandidateMime.get(entry.mime);
|
|
120
110
|
if (match) return match;
|
|
121
111
|
}
|
|
122
|
-
// No match -- use first candidate as default
|
|
123
112
|
return candidates[0]!;
|
|
124
113
|
}
|
|
125
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Re-key params from the primary leaf's names to a winning variant's names.
|
|
117
|
+
*
|
|
118
|
+
* The trie match builds `params` positionally under the primary leaf's pa, so
|
|
119
|
+
* `/widgets/:id` matched as the primary yields `{ id }` even when the winning
|
|
120
|
+
* `/widgets/:file` response variant expects `{ file }`. Both share the same trie
|
|
121
|
+
* terminal, so they bind the same number of positional named params; we zip the
|
|
122
|
+
* variant's pa against the named values in insertion order (which is the
|
|
123
|
+
* primary's pa order). The wildcard key (`*`) is positional-independent and left
|
|
124
|
+
* untouched.
|
|
125
|
+
*
|
|
126
|
+
* Mutates `params` in place. No-op when `variantPa` is absent, when names already
|
|
127
|
+
* match (the common case), or when the positional count diverges (defensive:
|
|
128
|
+
* never corrupt params by zipping mismatched lengths).
|
|
129
|
+
*/
|
|
130
|
+
export function rekeyParamsForVariant(
|
|
131
|
+
params: Record<string, string>,
|
|
132
|
+
variantPa: string[] | undefined,
|
|
133
|
+
): void {
|
|
134
|
+
if (!variantPa || variantPa.length === 0) return;
|
|
135
|
+
|
|
136
|
+
const namedKeys: string[] = [];
|
|
137
|
+
for (const key in params) {
|
|
138
|
+
if (key !== "*") namedKeys.push(key);
|
|
139
|
+
}
|
|
140
|
+
if (namedKeys.length !== variantPa.length) return;
|
|
141
|
+
|
|
142
|
+
let identical = true;
|
|
143
|
+
for (let i = 0; i < variantPa.length; i++) {
|
|
144
|
+
if (namedKeys[i] !== variantPa[i]) {
|
|
145
|
+
identical = false;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (identical) return;
|
|
150
|
+
|
|
151
|
+
const values = namedKeys.map((k) => params[k]!);
|
|
152
|
+
for (const key of namedKeys) delete params[key];
|
|
153
|
+
for (let i = 0; i < variantPa.length; i++) {
|
|
154
|
+
params[variantPa[i]!] = values[i]!;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
126
158
|
/**
|
|
127
159
|
* Result of content negotiation for a route with negotiate variants.
|
|
128
160
|
*/
|
|
@@ -173,9 +205,10 @@ export async function negotiateRoute(
|
|
|
173
205
|
|
|
174
206
|
const acceptEntries = parseAcceptTypes(request.headers.get("accept") || "");
|
|
175
207
|
|
|
176
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
208
|
+
// Variants carry the variant's own pa (positional param names); the synthetic
|
|
209
|
+
// primary/RSC candidates have none (their params are already keyed correctly).
|
|
210
|
+
const variants = matched.negotiateVariants as NegotiateVariant[];
|
|
211
|
+
let candidates: NegotiateVariant[];
|
|
179
212
|
if (responseType) {
|
|
180
213
|
candidates = [...variants, { routeKey: matched.routeKey, responseType }];
|
|
181
214
|
} else {
|
|
@@ -190,12 +223,10 @@ export async function negotiateRoute(
|
|
|
190
223
|
|
|
191
224
|
const variant = pickNegotiateVariant(acceptEntries, candidates);
|
|
192
225
|
|
|
193
|
-
// RSC won negotiation
|
|
194
226
|
if (variant.responseType === RSC_RESPONSE_TYPE) {
|
|
195
227
|
return null;
|
|
196
228
|
}
|
|
197
229
|
|
|
198
|
-
// Primary response-type won — use existing manifest entry and middleware
|
|
199
230
|
if (responseType && variant.routeKey === matched.routeKey) {
|
|
200
231
|
return {
|
|
201
232
|
responseType,
|
|
@@ -205,8 +236,12 @@ export async function negotiateRoute(
|
|
|
205
236
|
negotiated: true,
|
|
206
237
|
};
|
|
207
238
|
}
|
|
208
|
-
|
|
209
|
-
//
|
|
239
|
+
// The trie extracted params under the PRIMARY leaf's pa, but the winning
|
|
240
|
+
// variant's handler is keyed by the variant's own param names. Re-key in place
|
|
241
|
+
// so plan.route.params (and the variant middleware collected just below) see
|
|
242
|
+
// the variant's names. No-op when the variant has no pa or shares the primary's
|
|
243
|
+
// names (the common case).
|
|
244
|
+
rekeyParamsForVariant(matched.params, variant.pa);
|
|
210
245
|
const negotiateEntry = await loadManifest(
|
|
211
246
|
matched.entry,
|
|
212
247
|
variant.routeKey,
|
|
@@ -117,16 +117,10 @@ export function findNearestErrorBoundary(
|
|
|
117
117
|
let current: EntryData | null = entry;
|
|
118
118
|
|
|
119
119
|
while (current) {
|
|
120
|
-
// Check if this entry has error boundaries defined
|
|
121
120
|
if (current.errorBoundary && current.errorBoundary.length > 0) {
|
|
122
|
-
// Return the last error boundary (most recently defined takes precedence)
|
|
123
121
|
return current.errorBoundary[current.errorBoundary.length - 1];
|
|
124
122
|
}
|
|
125
123
|
|
|
126
|
-
// Check orphan layouts for error boundaries
|
|
127
|
-
// Orphan layouts are siblings that render alongside the main route chain
|
|
128
|
-
// They can define error boundaries that catch errors from routes in the same route group
|
|
129
|
-
// Check from first to last (first sibling takes precedence as the "outer" wrapper)
|
|
130
124
|
if (current.layout && current.layout.length > 0) {
|
|
131
125
|
for (const orphan of current.layout) {
|
|
132
126
|
if (orphan.errorBoundary && orphan.errorBoundary.length > 0) {
|
|
@@ -153,11 +147,21 @@ export function findNearestNotFoundBoundary(
|
|
|
153
147
|
let current: EntryData | null = entry;
|
|
154
148
|
|
|
155
149
|
while (current) {
|
|
156
|
-
// Check if this entry has notFound boundaries defined
|
|
157
150
|
if (current.notFoundBoundary && current.notFoundBoundary.length > 0) {
|
|
158
|
-
// Return the last notFound boundary (most recently defined takes precedence)
|
|
159
151
|
return current.notFoundBoundary[current.notFoundBoundary.length - 1];
|
|
160
152
|
}
|
|
153
|
+
|
|
154
|
+
// Check orphan layouts mirroring findNearestErrorBoundary: notFoundBoundary
|
|
155
|
+
// attaches identically (onto parent.notFoundBoundary), and an orphan layout
|
|
156
|
+
// (parent=null) is reachable only via this scan. First sibling is "outer".
|
|
157
|
+
if (current.layout && current.layout.length > 0) {
|
|
158
|
+
for (const orphan of current.layout) {
|
|
159
|
+
if (orphan.notFoundBoundary && orphan.notFoundBoundary.length > 0) {
|
|
160
|
+
return orphan.notFoundBoundary[orphan.notFoundBoundary.length - 1];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
161
165
|
current = current.parent;
|
|
162
166
|
}
|
|
163
167
|
|
|
@@ -165,6 +169,37 @@ export function findNearestNotFoundBoundary(
|
|
|
165
169
|
return defaultNotFoundBoundary || null;
|
|
166
170
|
}
|
|
167
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Normalize an error's cause into a Flight-serializable shape.
|
|
174
|
+
* ErrorInfo.cause crosses the RSC serialization boundary (via
|
|
175
|
+
* LoaderDataResult.error + error-segment fallback props); a non-serializable
|
|
176
|
+
* cause (function, class instance, circular object) would make Flight
|
|
177
|
+
* serialization throw and mask the original loader error.
|
|
178
|
+
*/
|
|
179
|
+
function normalizeCause(cause: unknown): unknown {
|
|
180
|
+
if (cause == null) return undefined;
|
|
181
|
+
const t = typeof cause;
|
|
182
|
+
if (t === "string" || t === "number" || t === "boolean") return cause;
|
|
183
|
+
// The whole body is guarded: even `instanceof`/clone can run user code (a
|
|
184
|
+
// Proxy trap, a throwing getter), and this helper must never throw —
|
|
185
|
+
// throwing here would mask the original loader error it exists to protect.
|
|
186
|
+
try {
|
|
187
|
+
if (cause instanceof Error) {
|
|
188
|
+
return { name: cause.name, message: cause.message, stack: cause.stack };
|
|
189
|
+
}
|
|
190
|
+
// Prefer preserving a serializable object/array intact (Flight uses
|
|
191
|
+
// structured-clone-like semantics); fall back to a string when the value
|
|
192
|
+
// is circular, host-bound, or otherwise non-serializable.
|
|
193
|
+
return structuredClone(cause);
|
|
194
|
+
} catch {
|
|
195
|
+
try {
|
|
196
|
+
return String(cause);
|
|
197
|
+
} catch {
|
|
198
|
+
return "[unstringifiable cause]";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
168
203
|
/**
|
|
169
204
|
* Create ErrorInfo from an error object
|
|
170
205
|
* Sanitizes error details in production
|
|
@@ -182,7 +217,7 @@ export function createErrorInfo(
|
|
|
182
217
|
name: error.name,
|
|
183
218
|
code: (error as any).code,
|
|
184
219
|
stack: isDev ? error.stack : undefined,
|
|
185
|
-
cause: isDev ? error.cause : undefined,
|
|
220
|
+
cause: isDev ? normalizeCause(error.cause) : undefined,
|
|
186
221
|
segmentId,
|
|
187
222
|
segmentType,
|
|
188
223
|
};
|
|
@@ -207,22 +242,17 @@ export function createErrorSegment(
|
|
|
207
242
|
entry: EntryData,
|
|
208
243
|
params: Record<string, string>,
|
|
209
244
|
): ResolvedSegment {
|
|
210
|
-
// Determine the component to render
|
|
211
245
|
let component: ReactNode;
|
|
212
246
|
|
|
213
247
|
if (typeof fallback === "function") {
|
|
214
|
-
// ErrorBoundaryHandler - call with error info
|
|
215
248
|
const props: ErrorBoundaryFallbackProps = {
|
|
216
249
|
error: errorInfo,
|
|
217
250
|
};
|
|
218
251
|
component = fallback(props);
|
|
219
252
|
} else {
|
|
220
|
-
// Static ReactNode fallback
|
|
221
253
|
component = fallback;
|
|
222
254
|
}
|
|
223
255
|
|
|
224
|
-
// Error segment uses the same ID as the layout that has the error boundary
|
|
225
|
-
// The error boundary content replaces the layout's outlet content
|
|
226
256
|
return {
|
|
227
257
|
id: entry.shortCode,
|
|
228
258
|
namespace: entry.id,
|
|
@@ -261,17 +291,14 @@ export function createNotFoundSegment(
|
|
|
261
291
|
entry: EntryData,
|
|
262
292
|
params: Record<string, string>,
|
|
263
293
|
): ResolvedSegment {
|
|
264
|
-
// Determine the component to render
|
|
265
294
|
let component: ReactNode;
|
|
266
295
|
|
|
267
296
|
if (typeof fallback === "function") {
|
|
268
|
-
// NotFoundBoundaryHandler - call with props
|
|
269
297
|
const props: NotFoundBoundaryFallbackProps = {
|
|
270
298
|
notFound: notFoundInfo,
|
|
271
299
|
};
|
|
272
300
|
component = fallback(props);
|
|
273
301
|
} else {
|
|
274
|
-
// Static ReactNode fallback
|
|
275
302
|
component = fallback;
|
|
276
303
|
}
|
|
277
304
|
|