@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request Classification
|
|
3
|
+
*
|
|
4
|
+
* Replaces the implicit "preview then match again" model with a clean
|
|
5
|
+
* two-stage architecture:
|
|
6
|
+
*
|
|
7
|
+
* 1. Classification — classifyRequest() produces a RequestPlan that answers
|
|
8
|
+
* all routing questions once: target route, request mode, route middleware,
|
|
9
|
+
* response-route info, negotiation state.
|
|
10
|
+
*
|
|
11
|
+
* 2. Execution — executeRequest() dispatches on the plan to the appropriate
|
|
12
|
+
* handler (response route, loader fetch, full render, partial render,
|
|
13
|
+
* action revalidation, PE render).
|
|
14
|
+
*
|
|
15
|
+
* Builds on RouteSnapshot from route-snapshot.ts.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { RouteNotFoundError } from "../errors.js";
|
|
19
|
+
import type { EntryData } from "../server/context.js";
|
|
20
|
+
import type { CollectedMiddleware } from "./middleware-types.js";
|
|
21
|
+
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
22
|
+
import { negotiateRoute } from "./content-negotiation.js";
|
|
23
|
+
import { stripInternalParams } from "./handler-context.js";
|
|
24
|
+
import { resolveRoute, type RouteSnapshot } from "./route-snapshot.js";
|
|
25
|
+
|
|
26
|
+
interface RedirectPlan<TEnv = any> {
|
|
27
|
+
mode: "redirect";
|
|
28
|
+
route: RouteSnapshot<TEnv>;
|
|
29
|
+
redirectUrl: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface VersionMismatchPlan<TEnv = any> {
|
|
33
|
+
mode: "version-mismatch";
|
|
34
|
+
/** May be undefined when version mismatch is detected before route resolution */
|
|
35
|
+
route?: RouteSnapshot<TEnv>;
|
|
36
|
+
reloadUrl: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface AppSwitchReloadPlan {
|
|
40
|
+
mode: "app-switch";
|
|
41
|
+
/** Clean target URL (internal _rsc_* params stripped) to navigate to. */
|
|
42
|
+
reloadUrl: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ResponseRoutePlan<TEnv = any> {
|
|
46
|
+
mode: "response";
|
|
47
|
+
route: RouteSnapshot<TEnv>;
|
|
48
|
+
handler: Function;
|
|
49
|
+
responseType: string;
|
|
50
|
+
negotiated: boolean;
|
|
51
|
+
manifestEntry: EntryData;
|
|
52
|
+
routeMiddleware: CollectedMiddleware[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface LoaderFetchPlan<TEnv = any> {
|
|
56
|
+
mode: "loader";
|
|
57
|
+
route: RouteSnapshot<TEnv>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface PeRenderPlan<TEnv = any> {
|
|
61
|
+
mode: "pe-render";
|
|
62
|
+
route: RouteSnapshot<TEnv>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface ActionPlan<TEnv = any> {
|
|
66
|
+
mode: "action";
|
|
67
|
+
route: RouteSnapshot<TEnv>;
|
|
68
|
+
actionId: string;
|
|
69
|
+
negotiated: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface FullRenderPlan<TEnv = any> {
|
|
73
|
+
mode: "full-render";
|
|
74
|
+
route: RouteSnapshot<TEnv>;
|
|
75
|
+
negotiated: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface PartialRenderPlan<TEnv = any> {
|
|
79
|
+
mode: "partial-render";
|
|
80
|
+
route: RouteSnapshot<TEnv>;
|
|
81
|
+
negotiated: boolean;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The output of request classification. A discriminated union where each
|
|
86
|
+
* variant carries exactly the fields needed for its execution path.
|
|
87
|
+
*/
|
|
88
|
+
export type RequestPlan<TEnv = any> =
|
|
89
|
+
| RedirectPlan<TEnv>
|
|
90
|
+
| VersionMismatchPlan<TEnv>
|
|
91
|
+
| AppSwitchReloadPlan
|
|
92
|
+
| ResponseRoutePlan<TEnv>
|
|
93
|
+
| LoaderFetchPlan<TEnv>
|
|
94
|
+
| PeRenderPlan<TEnv>
|
|
95
|
+
| ActionPlan<TEnv>
|
|
96
|
+
| FullRenderPlan<TEnv>
|
|
97
|
+
| PartialRenderPlan<TEnv>;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Plans that have passed the terminal-check gate (version-mismatch and
|
|
101
|
+
* app-switch reloads handled) and are ready for execution. Always have a
|
|
102
|
+
* `route` field.
|
|
103
|
+
*/
|
|
104
|
+
export type ExecutableRequestPlan<TEnv = any> = Exclude<
|
|
105
|
+
RequestPlan<TEnv>,
|
|
106
|
+
VersionMismatchPlan<TEnv> | AppSwitchReloadPlan
|
|
107
|
+
>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Re-export individual plan types for consumers that need to narrow.
|
|
111
|
+
*/
|
|
112
|
+
export type {
|
|
113
|
+
RedirectPlan,
|
|
114
|
+
VersionMismatchPlan,
|
|
115
|
+
ResponseRoutePlan,
|
|
116
|
+
LoaderFetchPlan,
|
|
117
|
+
PeRenderPlan,
|
|
118
|
+
ActionPlan,
|
|
119
|
+
FullRenderPlan,
|
|
120
|
+
PartialRenderPlan,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export interface ClassifyRequestDeps<TEnv = any> {
|
|
124
|
+
findMatch: (
|
|
125
|
+
pathname: string,
|
|
126
|
+
) => RouteMatchResult<TEnv> | null | Promise<RouteMatchResult<TEnv> | null>;
|
|
127
|
+
routerVersion: string;
|
|
128
|
+
routerId: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Classify an incoming request into a RequestPlan.
|
|
133
|
+
*
|
|
134
|
+
* This is the single source of truth for request mode detection. It replaces
|
|
135
|
+
* the scattered previewMatch + isAction/isLoaderFetch/isPartial checks in
|
|
136
|
+
* handler.ts.
|
|
137
|
+
*
|
|
138
|
+
* Classification order:
|
|
139
|
+
* 1. Route resolution (findMatch + loadManifest via resolveRoute lite)
|
|
140
|
+
* 2. Redirect detection
|
|
141
|
+
* 3. Version mismatch
|
|
142
|
+
* 4. Response route + content negotiation
|
|
143
|
+
* 5. Mode detection from headers/params
|
|
144
|
+
*/
|
|
145
|
+
export async function classifyRequest<TEnv = any>(
|
|
146
|
+
request: Request,
|
|
147
|
+
url: URL,
|
|
148
|
+
deps: ClassifyRequestDeps<TEnv>,
|
|
149
|
+
): Promise<RequestPlan<TEnv>> {
|
|
150
|
+
const pathname = url.pathname;
|
|
151
|
+
const isAction =
|
|
152
|
+
request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
|
|
153
|
+
|
|
154
|
+
const clientVersion = url.searchParams.get("_rsc_v");
|
|
155
|
+
if (
|
|
156
|
+
deps.routerVersion &&
|
|
157
|
+
clientVersion &&
|
|
158
|
+
clientVersion !== deps.routerVersion
|
|
159
|
+
) {
|
|
160
|
+
let reloadUrl = stripInternalParams(url).toString();
|
|
161
|
+
if (isAction) {
|
|
162
|
+
const referer = request.headers.get("referer");
|
|
163
|
+
if (referer) {
|
|
164
|
+
try {
|
|
165
|
+
const refererUrl = new URL(referer);
|
|
166
|
+
if (refererUrl.origin === url.origin) {
|
|
167
|
+
reloadUrl = referer;
|
|
168
|
+
}
|
|
169
|
+
} catch {}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
mode: "version-mismatch",
|
|
175
|
+
reloadUrl,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
180
|
+
if (
|
|
181
|
+
clientRouterId &&
|
|
182
|
+
clientRouterId !== deps.routerId &&
|
|
183
|
+
url.searchParams.has("_rsc_partial")
|
|
184
|
+
) {
|
|
185
|
+
return {
|
|
186
|
+
mode: "app-switch",
|
|
187
|
+
reloadUrl: stripInternalParams(url).toString(),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const result = await resolveRoute<TEnv>(pathname, {
|
|
192
|
+
findMatch: deps.findMatch,
|
|
193
|
+
lite: true,
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (!result) {
|
|
197
|
+
throw new RouteNotFoundError(`No route matched for ${pathname}`, {
|
|
198
|
+
cause: { pathname, method: request.method },
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (result.type === "redirect") {
|
|
203
|
+
const snapshot: RouteSnapshot<TEnv> = {
|
|
204
|
+
matched: result as any,
|
|
205
|
+
manifestEntry: null as any,
|
|
206
|
+
entries: [],
|
|
207
|
+
routeKey: "",
|
|
208
|
+
localRouteName: "",
|
|
209
|
+
params: {},
|
|
210
|
+
routeMiddleware: [],
|
|
211
|
+
cacheScope: null,
|
|
212
|
+
isPassthrough: false,
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
mode: "redirect",
|
|
216
|
+
route: snapshot,
|
|
217
|
+
redirectUrl: result.redirectTo + url.search,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const snapshot = result.snapshot;
|
|
222
|
+
|
|
223
|
+
const responseResult = await classifyResponseRoute(
|
|
224
|
+
request,
|
|
225
|
+
pathname,
|
|
226
|
+
snapshot,
|
|
227
|
+
);
|
|
228
|
+
if (responseResult) {
|
|
229
|
+
return responseResult;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const actionId =
|
|
233
|
+
request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
|
|
234
|
+
const isLoaderFetch = url.searchParams.has("_rsc_loader");
|
|
235
|
+
|
|
236
|
+
const hasVariants =
|
|
237
|
+
snapshot.matched.negotiateVariants &&
|
|
238
|
+
snapshot.matched.negotiateVariants.length > 0;
|
|
239
|
+
const negotiated = !!hasVariants;
|
|
240
|
+
|
|
241
|
+
if (isAction && actionId) {
|
|
242
|
+
return { mode: "action", route: snapshot, actionId, negotiated };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (isLoaderFetch) {
|
|
246
|
+
return { mode: "loader", route: snapshot };
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const contentType = request.headers.get("content-type") || "";
|
|
250
|
+
const isFormSubmission =
|
|
251
|
+
contentType.includes("multipart/form-data") ||
|
|
252
|
+
contentType.includes("application/x-www-form-urlencoded");
|
|
253
|
+
if (request.method === "POST" && !isAction && isFormSubmission) {
|
|
254
|
+
return { mode: "pe-render", route: snapshot };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (url.searchParams.has("_rsc_partial")) {
|
|
258
|
+
return { mode: "partial-render", route: snapshot, negotiated };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return { mode: "full-render", route: snapshot, negotiated };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Check if the route is a response route and perform content negotiation
|
|
266
|
+
* if negotiate variants exist. Returns a ResponseRoutePlan if the route
|
|
267
|
+
* is a response route, null otherwise (RSC route).
|
|
268
|
+
*/
|
|
269
|
+
async function classifyResponseRoute<TEnv>(
|
|
270
|
+
request: Request,
|
|
271
|
+
pathname: string,
|
|
272
|
+
snapshot: RouteSnapshot<TEnv>,
|
|
273
|
+
): Promise<ResponseRoutePlan<TEnv> | null> {
|
|
274
|
+
const negotiation = await negotiateRoute(request, pathname, snapshot);
|
|
275
|
+
return negotiation
|
|
276
|
+
? { mode: "response", route: snapshot, ...negotiation }
|
|
277
|
+
: null;
|
|
278
|
+
}
|
|
@@ -4,9 +4,63 @@
|
|
|
4
4
|
* Evaluates whether segments should revalidate based on params, actions, and custom functions.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ResolvedSegment, HandlerContext } from "../types";
|
|
7
|
+
import type { ResolvedSegment, HandlerContext, ActionRef } from "../types";
|
|
8
8
|
import type { ActionContext } from "./types";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
debugLog,
|
|
11
|
+
pushRevalidationTraceEntry,
|
|
12
|
+
isTraceActive,
|
|
13
|
+
} from "./logging.js";
|
|
14
|
+
import type { RevalidationTraceEntry } from "./logging.js";
|
|
15
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
16
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
17
|
+
import { paramsEqual } from "./params-util.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Resolve a server-action reference's stable id, mirroring how the action
|
|
21
|
+
* boundary derives `actionContext.actionId` in `rsc/server-action.ts`
|
|
22
|
+
* (`$id ?? $$id`): the file-path `$id` set by the expose-action-id plugin in a
|
|
23
|
+
* production RSC build when present, otherwise React's `$$id`. Resolving both
|
|
24
|
+
* the incoming `actionId` and the reference with the same precedence makes
|
|
25
|
+
* `isAction()` form-agnostic across dev and production.
|
|
26
|
+
*/
|
|
27
|
+
function resolveActionRefId(ref: unknown): string | undefined {
|
|
28
|
+
if (ref == null) return undefined;
|
|
29
|
+
const r = ref as { $id?: unknown; $$id?: unknown };
|
|
30
|
+
if (typeof r.$id === "string") return r.$id;
|
|
31
|
+
if (typeof r.$$id === "string") return r.$$id;
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build the `isAction()` helper bound to the current action's id. Called with no
|
|
37
|
+
* arguments it answers "is this request an action at all?" (any action) — `true`
|
|
38
|
+
* during action handling, `false` on plain navigation. Called with one or more
|
|
39
|
+
* action references it narrows to those: a single imported action, several
|
|
40
|
+
* (variadic), or any export of a namespace import (`import * as Mod`). Returns
|
|
41
|
+
* `false` when there is no action (plain navigation) or nothing matches.
|
|
42
|
+
*/
|
|
43
|
+
function makeIsAction(
|
|
44
|
+
currentActionId: string | undefined,
|
|
45
|
+
): (...actions: ActionRef[]) => boolean {
|
|
46
|
+
return (...actions: ActionRef[]): boolean => {
|
|
47
|
+
if (!currentActionId) return false;
|
|
48
|
+
// Bare isAction(): an action is in flight (currentActionId is set) and the
|
|
49
|
+
// caller did not narrow to a specific action, so this is "any action".
|
|
50
|
+
if (actions.length === 0) return true;
|
|
51
|
+
for (const action of actions) {
|
|
52
|
+
if (typeof action === "function") {
|
|
53
|
+
if (resolveActionRefId(action) === currentActionId) return true;
|
|
54
|
+
} else if (action && typeof action === "object") {
|
|
55
|
+
// Namespace import: match any export of the module.
|
|
56
|
+
for (const value of Object.values(action)) {
|
|
57
|
+
if (resolveActionRefId(value) === currentActionId) return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
10
64
|
|
|
11
65
|
/**
|
|
12
66
|
* Options for revalidation evaluation
|
|
@@ -34,6 +88,16 @@ interface EvaluateRevalidationOptions<TEnv> {
|
|
|
34
88
|
actionContext?: ActionContext;
|
|
35
89
|
/** If true, this is a stale cache revalidation request */
|
|
36
90
|
stale?: boolean;
|
|
91
|
+
/** Trace source hint for the revalidation trace */
|
|
92
|
+
traceSource?: RevalidationTraceEntry["source"];
|
|
93
|
+
/**
|
|
94
|
+
* Override the segment-type-derived default. When set, the value is used as
|
|
95
|
+
* the seed `defaultShouldRevalidate` passed to user revalidate fns and the
|
|
96
|
+
* reason flows into the trace. Callers use this when client-knowledge
|
|
97
|
+
* (e.g. parallel slot not in clientSegmentIds) should dictate the seed
|
|
98
|
+
* instead of the params/method-based heuristic.
|
|
99
|
+
*/
|
|
100
|
+
defaultOverride?: { value: boolean; reason: string };
|
|
37
101
|
}
|
|
38
102
|
|
|
39
103
|
/**
|
|
@@ -41,7 +105,7 @@ interface EvaluateRevalidationOptions<TEnv> {
|
|
|
41
105
|
* Optimized to use prevParams directly and avoid building previous segments
|
|
42
106
|
*/
|
|
43
107
|
export async function evaluateRevalidation<TEnv>(
|
|
44
|
-
options: EvaluateRevalidationOptions<TEnv
|
|
108
|
+
options: EvaluateRevalidationOptions<TEnv>,
|
|
45
109
|
): Promise<boolean> {
|
|
46
110
|
const {
|
|
47
111
|
segment,
|
|
@@ -55,51 +119,81 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
55
119
|
context,
|
|
56
120
|
actionContext,
|
|
57
121
|
stale,
|
|
122
|
+
traceSource,
|
|
123
|
+
defaultOverride,
|
|
58
124
|
} = options;
|
|
59
125
|
const nextParams = segment.params || {};
|
|
60
|
-
const paramsChanged =
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
126
|
+
const paramsChanged = !paramsEqual(nextParams, prevParams);
|
|
127
|
+
const searchChanged = prevUrl.search !== nextUrl.search;
|
|
128
|
+
|
|
129
|
+
function pushTrace(
|
|
130
|
+
defaultVal: boolean,
|
|
131
|
+
finalVal: boolean,
|
|
132
|
+
reason: string,
|
|
133
|
+
): void {
|
|
134
|
+
if (!isTraceActive()) return;
|
|
135
|
+
pushRevalidationTraceEntry({
|
|
136
|
+
segmentId: segment.id,
|
|
137
|
+
segmentType: segment.type,
|
|
138
|
+
belongsToRoute: segment.belongsToRoute ?? false,
|
|
139
|
+
source: traceSource ?? "segment-resolution",
|
|
140
|
+
defaultShouldRevalidate: defaultVal,
|
|
141
|
+
finalShouldRevalidate: finalVal,
|
|
142
|
+
reason,
|
|
143
|
+
customRevalidators: revalidations.length || undefined,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
65
146
|
|
|
66
|
-
// Calculate default revalidation based on segment type and request method
|
|
67
147
|
let defaultShouldRevalidate: boolean;
|
|
148
|
+
let defaultReason: string;
|
|
68
149
|
|
|
69
|
-
if (
|
|
70
|
-
|
|
150
|
+
if (defaultOverride) {
|
|
151
|
+
defaultShouldRevalidate = defaultOverride.value;
|
|
152
|
+
defaultReason = defaultOverride.reason;
|
|
153
|
+
} else if (request.method === "POST") {
|
|
71
154
|
if (segment.type === "route") {
|
|
72
|
-
// Route segment always revalidates on actions
|
|
73
155
|
defaultShouldRevalidate = true;
|
|
156
|
+
defaultReason = "action:route-segment";
|
|
74
157
|
} else if (segment.type === "loader") {
|
|
75
|
-
// Loaders always revalidate on actions - they often contain action-sensitive data
|
|
76
|
-
// (e.g., cart count after add-to-cart action)
|
|
77
158
|
defaultShouldRevalidate = true;
|
|
159
|
+
defaultReason = "action:loader-segment";
|
|
78
160
|
} else if (segment.belongsToRoute) {
|
|
79
|
-
// Segment belongs to route (orphan layouts/parallels) - revalidate
|
|
80
161
|
defaultShouldRevalidate = true;
|
|
162
|
+
defaultReason = "action:belongs-to-route";
|
|
81
163
|
} else {
|
|
82
|
-
// Parent chain segment (shared layouts/parallels) - don't revalidate
|
|
83
164
|
defaultShouldRevalidate = false;
|
|
165
|
+
defaultReason = "action:parent-chain-skip";
|
|
84
166
|
}
|
|
85
167
|
} else {
|
|
86
|
-
// Navigation (GET): Conservative defaults to minimize unnecessary revalidations
|
|
87
|
-
// Only the route segment revalidates by default - all others require explicit opt-in
|
|
88
|
-
|
|
89
168
|
if (segment.type === "route") {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
169
|
+
const routeChanged = paramsChanged || searchChanged;
|
|
170
|
+
defaultShouldRevalidate = routeChanged;
|
|
171
|
+
defaultReason = paramsChanged
|
|
172
|
+
? "nav:params-changed"
|
|
173
|
+
: searchChanged
|
|
174
|
+
? "nav:search-changed"
|
|
175
|
+
: "nav:params-unchanged";
|
|
176
|
+
if (routeChanged) {
|
|
177
|
+
debugLog("revalidation", "route revalidating", {
|
|
95
178
|
segmentId: segment.id,
|
|
179
|
+
paramsChanged,
|
|
180
|
+
searchChanged,
|
|
96
181
|
});
|
|
97
182
|
}
|
|
183
|
+
} else if (segment.belongsToRoute && (paramsChanged || searchChanged)) {
|
|
184
|
+
defaultShouldRevalidate = true;
|
|
185
|
+
defaultReason = paramsChanged
|
|
186
|
+
? "nav:route-child-params-changed"
|
|
187
|
+
: "nav:route-child-search-changed";
|
|
188
|
+
debugLog("revalidation", "route child revalidating", {
|
|
189
|
+
segmentId: segment.id,
|
|
190
|
+
segmentType: segment.type,
|
|
191
|
+
paramsChanged,
|
|
192
|
+
searchChanged,
|
|
193
|
+
});
|
|
98
194
|
} else {
|
|
99
|
-
// Layouts and parallels default to no revalidation
|
|
100
|
-
// Cannot assume these segments depend on params without explicit declaration
|
|
101
|
-
// Use custom revalidation functions to opt-in when needed
|
|
102
195
|
defaultShouldRevalidate = false;
|
|
196
|
+
defaultReason = "nav:non-route-skip";
|
|
103
197
|
debugLog("revalidation", "non-route segment skipped by default", {
|
|
104
198
|
segmentId: segment.id,
|
|
105
199
|
segmentType: segment.type,
|
|
@@ -107,7 +201,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
107
201
|
}
|
|
108
202
|
}
|
|
109
203
|
|
|
110
|
-
// No custom revalidations defined - return default behavior without prev segment
|
|
111
204
|
if (revalidations.length === 0) {
|
|
112
205
|
if (defaultShouldRevalidate) {
|
|
113
206
|
debugLog("revalidation", "default revalidate=true", {
|
|
@@ -120,57 +213,103 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
120
213
|
segmentId: segment.id,
|
|
121
214
|
});
|
|
122
215
|
}
|
|
216
|
+
pushTrace(defaultShouldRevalidate, defaultShouldRevalidate, defaultReason);
|
|
123
217
|
return defaultShouldRevalidate;
|
|
124
218
|
}
|
|
125
219
|
|
|
126
|
-
// Custom revalidations exist - may need full prev segment
|
|
127
|
-
// Lazy load prev segment only if getPrevSegment provided
|
|
128
220
|
const prevSegment = getPrevSegment ? await getPrevSegment() : null;
|
|
129
221
|
|
|
130
|
-
// Execute revalidation functions with soft/hard decision pattern
|
|
131
222
|
let currentSuggestion = defaultShouldRevalidate;
|
|
132
223
|
|
|
224
|
+
const toRouteName =
|
|
225
|
+
routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
226
|
+
const reqCtx = _getRequestContext();
|
|
227
|
+
const prevRouteKey = reqCtx?._prevRouteKey;
|
|
228
|
+
const fromRouteName =
|
|
229
|
+
prevRouteKey && !isAutoGeneratedRouteName(prevRouteKey)
|
|
230
|
+
? prevRouteKey
|
|
231
|
+
: undefined;
|
|
232
|
+
|
|
133
233
|
for (const { name, fn } of revalidations) {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
234
|
+
let result: any;
|
|
235
|
+
try {
|
|
236
|
+
result = fn({
|
|
237
|
+
currentParams: prevSegment?.params || prevParams, // Use segment params if available, else route params
|
|
238
|
+
currentUrl: prevUrl,
|
|
239
|
+
nextParams,
|
|
240
|
+
nextUrl,
|
|
241
|
+
defaultShouldRevalidate: currentSuggestion,
|
|
242
|
+
context,
|
|
243
|
+
// Segment metadata (which segment is being evaluated)
|
|
244
|
+
segmentType: segment.type,
|
|
245
|
+
layoutName: segment.layoutName,
|
|
246
|
+
slotName: segment.slot,
|
|
247
|
+
// Action context (only populated when triggered by server action)
|
|
248
|
+
actionId: actionContext?.actionId,
|
|
249
|
+
isAction: makeIsAction(actionContext?.actionId),
|
|
250
|
+
actionUrl: actionContext?.actionUrl,
|
|
251
|
+
actionResult: actionContext?.actionResult,
|
|
252
|
+
formData: actionContext?.formData,
|
|
253
|
+
method: request.method,
|
|
254
|
+
routeName: toRouteName,
|
|
255
|
+
fromRouteName,
|
|
256
|
+
toRouteName,
|
|
257
|
+
stale,
|
|
258
|
+
});
|
|
259
|
+
} catch (error) {
|
|
260
|
+
// A thrown Response is control flow (e.g. `throw redirect(...)`), not a
|
|
261
|
+
// failure: re-throw it so the handler chokepoint (match-handlers.ts)
|
|
262
|
+
// turns it into the intended redirect/response. This mirrors how that
|
|
263
|
+
// catch special-cases `error instanceof Response`.
|
|
264
|
+
if (error instanceof Response) throw error;
|
|
265
|
+
// Fail open for genuine errors: a buggy user revalidate fn must not
|
|
266
|
+
// collapse the whole entry's loader batch into a failed partial render.
|
|
267
|
+
// Mirror the dynamic-tags fail-open in cache/cache-policy.ts: log and
|
|
268
|
+
// defer to the current default decision, leaving currentSuggestion
|
|
269
|
+
// unchanged. TODO: route through callOnError(phase "revalidation") once
|
|
270
|
+
// evaluateRevalidation is given the onError seam (today the error only
|
|
271
|
+
// reaches onError via the entry-collapse path in match-handlers.ts).
|
|
272
|
+
console.error(
|
|
273
|
+
`[revalidate] "${name}" threw for segment "${segment.id}"; using default decision:`,
|
|
274
|
+
error,
|
|
275
|
+
);
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// The revalidate fn contract (handler-context.ts) is SYNCHRONOUS: it must
|
|
280
|
+
// return a boolean, a { defaultShouldRevalidate } object, or null/undefined.
|
|
281
|
+
// A Promise-returning (async) fn matches none of the decision branches below
|
|
282
|
+
// and silently falls through keeping the current default — a hard-to-find
|
|
283
|
+
// misuse. We do NOT await it (that would change the sync contract); instead
|
|
284
|
+
// we surface it as a dev-mode warning so the silent drop is diagnosable.
|
|
285
|
+
// Mirrors defer.ts: gated to dev, stripped from production builds.
|
|
286
|
+
if (
|
|
287
|
+
process.env.NODE_ENV !== "production" &&
|
|
288
|
+
result != null &&
|
|
289
|
+
typeof (result as { then?: unknown }).then === "function"
|
|
290
|
+
) {
|
|
291
|
+
console.warn(
|
|
292
|
+
`[rango] revalidate fn "${name}" returned a Promise; revalidate ` +
|
|
293
|
+
`functions must be synchronous (return a boolean, ` +
|
|
294
|
+
`{ defaultShouldRevalidate }, or null/undefined). The async result ` +
|
|
295
|
+
`was IGNORED and the default (${currentSuggestion}) was kept. ` +
|
|
296
|
+
`Move async work into a loader instead.`,
|
|
297
|
+
);
|
|
298
|
+
}
|
|
155
299
|
|
|
156
|
-
// Check return type:
|
|
157
|
-
// - boolean: hard decision, short-circuit immediately
|
|
158
|
-
// - { defaultShouldRevalidate: boolean }: soft decision, update suggestion and continue
|
|
159
|
-
// - null/undefined: use default behavior (equivalent to returning { defaultShouldRevalidate })
|
|
160
300
|
if (typeof result === "boolean") {
|
|
161
|
-
// Hard decision - short-circuit
|
|
162
301
|
debugLog("revalidation", "hard decision", {
|
|
163
302
|
segmentId: segment.id,
|
|
164
303
|
revalidator: name,
|
|
165
304
|
revalidate: result,
|
|
166
305
|
});
|
|
306
|
+
pushTrace(defaultShouldRevalidate, result, `hard:${name}`);
|
|
167
307
|
return result;
|
|
168
308
|
} else if (
|
|
169
309
|
result &&
|
|
170
310
|
typeof result === "object" &&
|
|
171
311
|
"defaultShouldRevalidate" in result
|
|
172
312
|
) {
|
|
173
|
-
// Soft decision - update suggestion and continue
|
|
174
313
|
currentSuggestion = result.defaultShouldRevalidate;
|
|
175
314
|
debugLog("revalidation", "soft decision", {
|
|
176
315
|
segmentId: segment.id,
|
|
@@ -178,21 +317,23 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
178
317
|
revalidate: currentSuggestion,
|
|
179
318
|
});
|
|
180
319
|
} else if (result === null || result === undefined) {
|
|
181
|
-
// Defer to default - equivalent to { defaultShouldRevalidate: currentSuggestion }
|
|
182
|
-
// This means "I don't care, use whatever the default is"
|
|
183
320
|
debugLog("revalidation", "deferred to current default", {
|
|
184
321
|
segmentId: segment.id,
|
|
185
322
|
revalidator: name,
|
|
186
323
|
revalidate: currentSuggestion,
|
|
187
324
|
});
|
|
188
|
-
// currentSuggestion stays the same, continue to next function
|
|
189
325
|
}
|
|
190
326
|
}
|
|
191
327
|
|
|
192
|
-
// All revalidators completed - use final suggestion
|
|
193
328
|
debugLog("revalidation", "final decision", {
|
|
194
329
|
segmentId: segment.id,
|
|
195
330
|
revalidate: currentSuggestion,
|
|
196
331
|
});
|
|
332
|
+
const softNames = revalidations.map((r) => r.name).join(",");
|
|
333
|
+
pushTrace(
|
|
334
|
+
defaultShouldRevalidate,
|
|
335
|
+
currentSuggestion,
|
|
336
|
+
`soft-chain:${softNames}`,
|
|
337
|
+
);
|
|
197
338
|
return currentSuggestion;
|
|
198
339
|
}
|