@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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 +426 -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 +2500 -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 +29 -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-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -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 +177 -35
- package/src/index.ts +255 -71
- 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 +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -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 +452 -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/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- 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 +40 -72
- 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 +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -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
package/src/cache/cache-scope.ts
CHANGED
|
@@ -2,263 +2,118 @@
|
|
|
2
2
|
* CacheScope - Runtime cache scope for iterator-based caching
|
|
3
3
|
*
|
|
4
4
|
* Each cache() boundary in the route tree creates a new CacheScope.
|
|
5
|
-
* The scope owns: config,
|
|
5
|
+
* The scope owns: config, key management, and storage operations.
|
|
6
|
+
*
|
|
7
|
+
* Serialization is delegated to segment-codec.ts.
|
|
8
|
+
* Handle data capture/restore is delegated to handle-snapshot.ts.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
|
-
/// <reference types="@vitejs/plugin-rsc/types" />
|
|
9
|
-
|
|
10
11
|
import type { PartialCacheOptions } from "../types.js";
|
|
11
12
|
import type { ResolvedSegment } from "../types.js";
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
SegmentHandleData,
|
|
15
|
-
CachedEntryData,
|
|
16
|
-
SerializedSegmentData,
|
|
17
|
-
} from "./types.js";
|
|
18
|
-
import { getRequestContext } from "../server/request-context.js";
|
|
13
|
+
import type { SegmentCacheStore, CachedEntryData } from "./types.js";
|
|
14
|
+
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
19
15
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} from "
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Generate default cache key for a route request.
|
|
57
|
-
* Single cache entry per route - uses pathname as the key.
|
|
58
|
-
* Includes request type prefix since they produce different segment sets:
|
|
59
|
-
* - doc: document requests (full page load)
|
|
60
|
-
* - partial: navigation requests (client-side navigation)
|
|
61
|
-
* - intercept: intercept navigation (modal/overlay routes)
|
|
62
|
-
* @internal
|
|
63
|
-
*/
|
|
64
|
-
function getDefaultRouteCacheKey(
|
|
65
|
-
pathname: string,
|
|
66
|
-
params?: Record<string, string>,
|
|
67
|
-
isIntercept?: boolean
|
|
68
|
-
): string {
|
|
69
|
-
const ctx = getRequestContext();
|
|
70
|
-
const isPartial = ctx?.url.searchParams.has("_rsc_partial") ?? false;
|
|
71
|
-
|
|
72
|
-
// Intercept navigations get their own cache namespace
|
|
73
|
-
const prefix = isIntercept ? "intercept" : isPartial ? "partial" : "doc";
|
|
74
|
-
|
|
75
|
-
return `${prefix}:${getCacheKeyBase(pathname, params)}`;
|
|
16
|
+
getRequestContext,
|
|
17
|
+
_getRequestContext,
|
|
18
|
+
} from "../server/request-context.js";
|
|
19
|
+
import { recordRequestTags } from "./cache-tag.js";
|
|
20
|
+
import { reportCacheError } from "./cache-error.js";
|
|
21
|
+
// segment-codec is the only module on cache-scope's import graph that eagerly
|
|
22
|
+
// pulls @vitejs/plugin-rsc (a virtual: module the plain node/vitest runner cannot
|
|
23
|
+
// resolve). It is imported LAZILY at the two call sites below (deserializeSegments
|
|
24
|
+
// in lookupRoute, serializeSegments in cacheRoute) so that requiring cache-scope —
|
|
25
|
+
// e.g. dispatch's lazy `import("../cache/cache-scope.js")` for the response-route
|
|
26
|
+
// cache path — does not crash a consumer test that never mocks plugin-rsc. Behavior
|
|
27
|
+
// is unchanged: both methods are async and already awaited the codec.
|
|
28
|
+
import {
|
|
29
|
+
captureHandles,
|
|
30
|
+
restoreHandles,
|
|
31
|
+
encodeHandles,
|
|
32
|
+
decodeHandles,
|
|
33
|
+
} from "./handle-snapshot.js";
|
|
34
|
+
import { sortedSearchString, sortedRouteParams } from "./cache-key-utils.js";
|
|
35
|
+
import {
|
|
36
|
+
DEFAULT_ROUTE_TTL,
|
|
37
|
+
isFiniteNonNegativeSeconds,
|
|
38
|
+
resolveCacheKey,
|
|
39
|
+
resolveCacheStore,
|
|
40
|
+
resolveTagsOption,
|
|
41
|
+
} from "./cache-policy.js";
|
|
42
|
+
import type { RequestContext } from "../server/request-context.js";
|
|
43
|
+
|
|
44
|
+
export function resolveCacheTags(
|
|
45
|
+
config: PartialCacheOptions | false,
|
|
46
|
+
ctx: RequestContext | undefined,
|
|
47
|
+
): string[] | undefined {
|
|
48
|
+
if (config === false) return undefined;
|
|
49
|
+
return resolveTagsOption(config.tags, ctx, "CacheScope");
|
|
76
50
|
}
|
|
77
51
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
*/
|
|
82
|
-
async function streamToString(
|
|
83
|
-
stream: ReadableStream<Uint8Array>
|
|
84
|
-
): Promise<string> {
|
|
85
|
-
const reader = stream.getReader();
|
|
86
|
-
const decoder = new TextDecoder();
|
|
87
|
-
let result = "";
|
|
88
|
-
|
|
89
|
-
while (true) {
|
|
90
|
-
const { done, value } = await reader.read();
|
|
91
|
-
if (done) break;
|
|
92
|
-
result += decoder.decode(value, { stream: true });
|
|
52
|
+
function debugCacheLog(message: string): void {
|
|
53
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
54
|
+
console.log(message);
|
|
93
55
|
}
|
|
94
|
-
|
|
95
|
-
result += decoder.decode(); // flush
|
|
96
|
-
return result;
|
|
97
56
|
}
|
|
98
57
|
|
|
99
58
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
59
|
+
* A finite, non-negative seconds value? A NaN/Infinity ttl/swr (from a bad
|
|
60
|
+
* cache() option or store defaults) flows into computeExpiration ->
|
|
61
|
+
* staleAt/expiresAt = NaN, where every `now > NaN` is false so the entry never
|
|
62
|
+
* evicts and is served fresh forever; a negative value makes every read a miss.
|
|
63
|
+
* Mirror profile-registry.ts's Number.isFinite + >= 0 check, but the callers
|
|
64
|
+
* degrade to a default (warning in dev) rather than throw — this runs on the
|
|
65
|
+
* foreground render.
|
|
102
66
|
*/
|
|
103
|
-
function
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
},
|
|
112
|
-
});
|
|
67
|
+
function isValidCacheSeconds(value: number, label: string): boolean {
|
|
68
|
+
if (isFiniteNonNegativeSeconds(value)) return true;
|
|
69
|
+
if (process.env.NODE_ENV !== "production") {
|
|
70
|
+
console.warn(
|
|
71
|
+
`[CacheScope] Invalid ${label} ${value}; falling back to default`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
113
75
|
}
|
|
114
76
|
|
|
115
|
-
/**
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
* @internal
|
|
119
|
-
*/
|
|
120
|
-
async function rscSerialize(value: unknown): Promise<string | undefined> {
|
|
121
|
-
if (value === undefined || value === null) return undefined;
|
|
122
|
-
|
|
123
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
124
|
-
const stream = renderToReadableStream(value, { temporaryReferences });
|
|
125
|
-
return streamToString(stream);
|
|
77
|
+
/** Coerce a resolved ttl to a finite, non-negative number (default on invalid). */
|
|
78
|
+
function validatedTtl(value: number): number {
|
|
79
|
+
return isValidCacheSeconds(value, "ttl") ? value : DEFAULT_ROUTE_TTL;
|
|
126
80
|
}
|
|
127
81
|
|
|
128
|
-
/**
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
async function rscDeserialize<T>(
|
|
133
|
-
encoded: string | undefined
|
|
134
|
-
): Promise<T | undefined> {
|
|
135
|
-
if (!encoded) return undefined;
|
|
136
|
-
|
|
137
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
138
|
-
const stream = stringToStream(encoded);
|
|
139
|
-
return createFromReadableStream<T>(stream, { temporaryReferences });
|
|
82
|
+
/** Coerce a resolved swr to a finite, non-negative number, or undefined (no SWR window). */
|
|
83
|
+
function validatedSwr(value: number | undefined): number | undefined {
|
|
84
|
+
if (value === undefined) return undefined;
|
|
85
|
+
return isValidCacheSeconds(value, "swr") ? value : undefined;
|
|
140
86
|
}
|
|
141
87
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
):
|
|
150
|
-
const serialized: SerializedSegmentData[] = [];
|
|
151
|
-
|
|
152
|
-
for (const segment of segments) {
|
|
153
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
154
|
-
|
|
155
|
-
// Await component if it's a Promise (intercepts with loading keep component as Promise)
|
|
156
|
-
const componentResolved =
|
|
157
|
-
segment.component instanceof Promise
|
|
158
|
-
? await segment.component
|
|
159
|
-
: segment.component;
|
|
160
|
-
|
|
161
|
-
// Serialize the component to RSC stream
|
|
162
|
-
const stream = renderToReadableStream(componentResolved, {
|
|
163
|
-
temporaryReferences,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
// Convert stream to string
|
|
167
|
-
const encoded = await streamToString(stream);
|
|
168
|
-
|
|
169
|
-
// RSC-serialize layout if present (ReactNode)
|
|
170
|
-
const encodedLayout = segment.layout
|
|
171
|
-
? await rscSerialize(segment.layout)
|
|
172
|
-
: undefined;
|
|
173
|
-
|
|
174
|
-
// RSC-serialize loading if present (ReactNode) - preserves tree structure
|
|
175
|
-
// Use "null" string to distinguish explicit null from undefined
|
|
176
|
-
const encodedLoading =
|
|
177
|
-
segment.loading !== undefined
|
|
178
|
-
? segment.loading === null
|
|
179
|
-
? "null"
|
|
180
|
-
: await rscSerialize(segment.loading)
|
|
181
|
-
: undefined;
|
|
182
|
-
|
|
183
|
-
// Await and RSC-serialize loaderData if present
|
|
184
|
-
const loaderDataResolved =
|
|
185
|
-
segment.loaderData instanceof Promise
|
|
186
|
-
? await segment.loaderData
|
|
187
|
-
: segment.loaderData;
|
|
188
|
-
const encodedLoaderData = await rscSerialize(loaderDataResolved);
|
|
189
|
-
|
|
190
|
-
// Await and RSC-serialize loaderDataPromise if present
|
|
191
|
-
const loaderDataPromiseResolved =
|
|
192
|
-
segment.loaderDataPromise instanceof Promise
|
|
193
|
-
? await segment.loaderDataPromise
|
|
194
|
-
: segment.loaderDataPromise;
|
|
195
|
-
const encodedLoaderDataPromise = await rscSerialize(
|
|
196
|
-
loaderDataPromiseResolved
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
serialized.push({
|
|
200
|
-
encoded,
|
|
201
|
-
encodedLayout,
|
|
202
|
-
encodedLoading,
|
|
203
|
-
encodedLoaderData,
|
|
204
|
-
encodedLoaderDataPromise,
|
|
205
|
-
metadata: {
|
|
206
|
-
id: segment.id,
|
|
207
|
-
type: segment.type,
|
|
208
|
-
namespace: segment.namespace,
|
|
209
|
-
index: segment.index,
|
|
210
|
-
params: segment.params,
|
|
211
|
-
slot: segment.slot,
|
|
212
|
-
belongsToRoute: segment.belongsToRoute,
|
|
213
|
-
layoutName: segment.layoutName,
|
|
214
|
-
parallelName: segment.parallelName,
|
|
215
|
-
loaderId: segment.loaderId,
|
|
216
|
-
loaderIds: segment.loaderIds,
|
|
217
|
-
},
|
|
218
|
-
});
|
|
219
|
-
}
|
|
88
|
+
function getCacheKeyBase(
|
|
89
|
+
host: string,
|
|
90
|
+
pathname: string,
|
|
91
|
+
params?: Record<string, string>,
|
|
92
|
+
searchParams?: URLSearchParams,
|
|
93
|
+
): string {
|
|
94
|
+
const paramStr = sortedRouteParams(params);
|
|
95
|
+
const searchStr = searchParams ? sortedSearchString(searchParams) : "";
|
|
220
96
|
|
|
221
|
-
|
|
97
|
+
let key = `${host}${pathname}`;
|
|
98
|
+
if (paramStr) key += `:${paramStr}`;
|
|
99
|
+
if (searchStr) key += `?${searchStr}`;
|
|
100
|
+
return key;
|
|
222
101
|
}
|
|
223
102
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
)
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
for (const item of data) {
|
|
234
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
235
|
-
|
|
236
|
-
// Revive the component from cached string
|
|
237
|
-
const stream = stringToStream(item.encoded);
|
|
238
|
-
const component = await createFromReadableStream(stream, {
|
|
239
|
-
temporaryReferences,
|
|
240
|
-
});
|
|
103
|
+
function getDefaultRouteCacheKey(
|
|
104
|
+
pathname: string,
|
|
105
|
+
params?: Record<string, string>,
|
|
106
|
+
isIntercept?: boolean,
|
|
107
|
+
): string {
|
|
108
|
+
const ctx = getRequestContext();
|
|
109
|
+
const isPartial = ctx?.originalUrl?.searchParams.has("_rsc_partial") ?? false;
|
|
110
|
+
const searchParams = ctx?.url.searchParams;
|
|
111
|
+
const host = ctx?.url.host ?? "localhost";
|
|
241
112
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
await Promise.all([
|
|
245
|
-
rscDeserialize(item.encodedLayout),
|
|
246
|
-
rscDeserialize(item.encodedLoaderData),
|
|
247
|
-
rscDeserialize(item.encodedLoaderDataPromise),
|
|
248
|
-
rscDeserialize(item.encodedLoading),
|
|
249
|
-
]);
|
|
250
|
-
|
|
251
|
-
segments.push({
|
|
252
|
-
...item.metadata,
|
|
253
|
-
component: await component,
|
|
254
|
-
layout,
|
|
255
|
-
loading: loadingData,
|
|
256
|
-
loaderData,
|
|
257
|
-
loaderDataPromise,
|
|
258
|
-
} as ResolvedSegment);
|
|
259
|
-
}
|
|
113
|
+
// Intercept navigations get their own cache namespace
|
|
114
|
+
const prefix = isIntercept ? "intercept" : isPartial ? "partial" : "doc";
|
|
260
115
|
|
|
261
|
-
return
|
|
116
|
+
return `${prefix}:${getCacheKeyBase(host, pathname, params, searchParams)}`;
|
|
262
117
|
}
|
|
263
118
|
|
|
264
119
|
// ============================================================================
|
|
@@ -269,7 +124,8 @@ export async function deserializeSegments(
|
|
|
269
124
|
* CacheScope represents a cache boundary in the route tree.
|
|
270
125
|
*
|
|
271
126
|
* When withCache encounters an entry with cache config, it creates
|
|
272
|
-
* a new CacheScope. The scope owns
|
|
127
|
+
* a new CacheScope. The scope owns key management, TTL resolution,
|
|
128
|
+
* and storage operations. Serialization is handled by segment-codec.ts.
|
|
273
129
|
*
|
|
274
130
|
* Store resolution priority:
|
|
275
131
|
* 1. Explicit store in cache() options
|
|
@@ -289,7 +145,7 @@ export class CacheScope {
|
|
|
289
145
|
|
|
290
146
|
constructor(
|
|
291
147
|
config: PartialCacheOptions | false,
|
|
292
|
-
parent: CacheScope | null = null
|
|
148
|
+
parent: CacheScope | null = null,
|
|
293
149
|
) {
|
|
294
150
|
this.config = config;
|
|
295
151
|
this.parent = parent;
|
|
@@ -305,40 +161,49 @@ export class CacheScope {
|
|
|
305
161
|
}
|
|
306
162
|
|
|
307
163
|
/**
|
|
308
|
-
* Get effective TTL from config or store defaults
|
|
164
|
+
* Get effective TTL from config or store defaults.
|
|
165
|
+
*
|
|
166
|
+
* Unlike profile-registry.ts (which fails fast at config time), the render
|
|
167
|
+
* path must DEGRADE: a non-finite/negative ttl (NaN/Infinity from a bad
|
|
168
|
+
* defaults config) would make computeExpiration produce NaN deadlines so the
|
|
169
|
+
* entry never evicts, or a guaranteed miss for a negative value. Fall back to
|
|
170
|
+
* DEFAULT_ROUTE_TTL instead of throwing in the foreground render.
|
|
309
171
|
*/
|
|
310
172
|
get ttl(): number {
|
|
311
173
|
if (this.config === false) return 0;
|
|
312
174
|
|
|
313
175
|
// Explicit TTL in cache() options
|
|
314
176
|
if (this.config.ttl !== undefined) {
|
|
315
|
-
return this.config.ttl;
|
|
177
|
+
return validatedTtl(this.config.ttl);
|
|
316
178
|
}
|
|
317
179
|
|
|
318
180
|
// Fall back to store defaults (explicit store first, then app-level)
|
|
319
181
|
const store = this.getStore();
|
|
320
182
|
if (store?.defaults?.ttl !== undefined) {
|
|
321
|
-
return store.defaults.ttl;
|
|
183
|
+
return validatedTtl(store.defaults.ttl);
|
|
322
184
|
}
|
|
323
185
|
|
|
324
186
|
// Hardcoded fallback
|
|
325
|
-
return
|
|
187
|
+
return DEFAULT_ROUTE_TTL;
|
|
326
188
|
}
|
|
327
189
|
|
|
328
190
|
/**
|
|
329
|
-
* Get SWR window from config or store defaults
|
|
191
|
+
* Get SWR window from config or store defaults.
|
|
192
|
+
*
|
|
193
|
+
* A non-finite/negative swr is degraded to undefined (no SWR window) rather
|
|
194
|
+
* than fed into expiry math; see the ttl getter for the rationale.
|
|
330
195
|
*/
|
|
331
196
|
get swr(): number | undefined {
|
|
332
197
|
if (this.config === false) return undefined;
|
|
333
198
|
|
|
334
199
|
// Explicit SWR in cache() options
|
|
335
200
|
if (this.config.swr !== undefined) {
|
|
336
|
-
return this.config.swr;
|
|
201
|
+
return validatedSwr(this.config.swr);
|
|
337
202
|
}
|
|
338
203
|
|
|
339
204
|
// Fall back to store defaults
|
|
340
205
|
const store = this.getStore();
|
|
341
|
-
return store?.defaults?.swr;
|
|
206
|
+
return validatedSwr(store?.defaults?.swr);
|
|
342
207
|
}
|
|
343
208
|
|
|
344
209
|
/**
|
|
@@ -346,65 +211,48 @@ export class CacheScope {
|
|
|
346
211
|
* 1. Explicit store from cache() options
|
|
347
212
|
* 2. App-level store from request context
|
|
348
213
|
*/
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if (this.explicitStore) {
|
|
352
|
-
return this.explicitStore;
|
|
353
|
-
}
|
|
354
|
-
// Fall back to app-level store from request context
|
|
355
|
-
const ctx = getRequestContext();
|
|
356
|
-
return ctx?._cacheStore ?? null;
|
|
214
|
+
getStore(): SegmentCacheStore | null {
|
|
215
|
+
return resolveCacheStore(this.explicitStore);
|
|
357
216
|
}
|
|
358
217
|
|
|
359
218
|
/**
|
|
360
|
-
* Resolve the cache key using
|
|
361
|
-
*
|
|
362
|
-
* Resolution priority:
|
|
363
|
-
* 1. Route-level `key` function (full override)
|
|
364
|
-
* 2. Store-level `keyGenerator` (modifies default key)
|
|
365
|
-
* 3. Default key generation (prefix:pathname:params)
|
|
366
|
-
*
|
|
219
|
+
* Resolve the cache key using the shared 3-tier priority.
|
|
367
220
|
* @internal
|
|
368
221
|
*/
|
|
369
222
|
private async resolveKey(
|
|
370
223
|
pathname: string,
|
|
371
224
|
params: Record<string, string>,
|
|
372
|
-
isIntercept?: boolean
|
|
225
|
+
isIntercept?: boolean,
|
|
373
226
|
): Promise<string> {
|
|
374
|
-
const requestCtx = getRequestContext();
|
|
375
|
-
if (!requestCtx) {
|
|
376
|
-
// Fallback to default key if no request context
|
|
377
|
-
return getDefaultRouteCacheKey(pathname, params, isIntercept);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Priority 1: Route-level key function (full override)
|
|
381
|
-
if (this.config !== false && this.config.key) {
|
|
382
|
-
try {
|
|
383
|
-
const customKey = await this.config.key(requestCtx);
|
|
384
|
-
return customKey;
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error(`[CacheScope] Custom key function failed, using default:`, error);
|
|
387
|
-
return getDefaultRouteCacheKey(pathname, params, isIntercept);
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// Generate default key
|
|
392
227
|
const defaultKey = getDefaultRouteCacheKey(pathname, params, isIntercept);
|
|
228
|
+
const keyFn = this.config !== false ? this.config.key : undefined;
|
|
229
|
+
return resolveCacheKey(keyFn, this.getStore(), defaultKey, "CacheScope");
|
|
230
|
+
}
|
|
393
231
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
232
|
+
/**
|
|
233
|
+
* Evaluate the cache `condition` predicate. Returns false (skip the cache
|
|
234
|
+
* operation) when the predicate returns false or throws; returns true when
|
|
235
|
+
* there is no condition or no request context to evaluate it against.
|
|
236
|
+
*/
|
|
237
|
+
private conditionAllows(op: "read" | "write"): boolean {
|
|
238
|
+
if (this.config === false || !this.config.condition) return true;
|
|
239
|
+
const requestCtx = getRequestContext();
|
|
240
|
+
if (!requestCtx) return true;
|
|
241
|
+
try {
|
|
242
|
+
if (!this.config.condition(requestCtx)) {
|
|
243
|
+
debugCacheLog(
|
|
244
|
+
`[CacheScope] condition returned false, skipping cache ${op}`,
|
|
245
|
+
);
|
|
246
|
+
return false;
|
|
403
247
|
}
|
|
248
|
+
return true;
|
|
249
|
+
} catch (error) {
|
|
250
|
+
console.error(
|
|
251
|
+
`[CacheScope] condition function threw, skipping cache ${op}:`,
|
|
252
|
+
error,
|
|
253
|
+
);
|
|
254
|
+
return false;
|
|
404
255
|
}
|
|
405
|
-
|
|
406
|
-
// Priority 3: Default key
|
|
407
|
-
return defaultKey;
|
|
408
256
|
}
|
|
409
257
|
|
|
410
258
|
/**
|
|
@@ -418,56 +266,118 @@ export class CacheScope {
|
|
|
418
266
|
async lookupRoute(
|
|
419
267
|
pathname: string,
|
|
420
268
|
params: Record<string, string>,
|
|
421
|
-
isIntercept?: boolean
|
|
269
|
+
isIntercept?: boolean,
|
|
422
270
|
): Promise<{
|
|
423
271
|
segments: ResolvedSegment[];
|
|
424
272
|
shouldRevalidate: boolean;
|
|
425
273
|
} | null> {
|
|
426
274
|
if (!this.enabled) return null;
|
|
275
|
+
if (!this.conditionAllows("read")) return null;
|
|
427
276
|
|
|
428
277
|
const store = this.getStore();
|
|
429
278
|
if (!store) return null;
|
|
430
279
|
|
|
431
|
-
// Resolve cache key
|
|
432
|
-
|
|
433
|
-
|
|
280
|
+
// Resolve cache key INSIDE the try so a throwing consumer key() (or a
|
|
281
|
+
// store.keyGenerator) degrades to a cache miss (return null -> render
|
|
282
|
+
// uncached) instead of crashing the foreground render. resolveCacheKey
|
|
283
|
+
// itself keeps its hard-fail/no-fallback-to-default contract (a throw must
|
|
284
|
+
// not silently collide onto the default slot); the graceful degradation
|
|
285
|
+
// happens here, where a miss is a safe outcome.
|
|
286
|
+
let key: string | undefined;
|
|
434
287
|
try {
|
|
288
|
+
key = await this.resolveKey(pathname, params, isIntercept);
|
|
289
|
+
|
|
435
290
|
const result = await store.get(key);
|
|
436
291
|
|
|
437
292
|
if (!result) {
|
|
438
|
-
|
|
293
|
+
debugCacheLog(`[CacheScope] MISS: ${key}`);
|
|
439
294
|
return null;
|
|
440
295
|
}
|
|
441
296
|
|
|
442
297
|
const { data: cached, shouldRevalidate } = result;
|
|
443
298
|
|
|
444
|
-
// Deserialize segments
|
|
445
|
-
|
|
299
|
+
// Deserialize segments. A failure means the cached segments are corrupt/
|
|
300
|
+
// partial: evict the entry (self-heal - the re-render re-caches under the
|
|
301
|
+
// same key) and report it as corruption, distinct from a transient infra
|
|
302
|
+
// error (handled by the outer catch).
|
|
303
|
+
let segments: ResolvedSegment[];
|
|
304
|
+
try {
|
|
305
|
+
const { deserializeSegments } = await import("./segment-codec.js");
|
|
306
|
+
segments = await deserializeSegments(cached.segments);
|
|
307
|
+
} catch (error) {
|
|
308
|
+
reportCacheError(
|
|
309
|
+
error,
|
|
310
|
+
"cache-corrupt",
|
|
311
|
+
`[CacheScope] ${key}: corrupt cached segments, evicting`,
|
|
312
|
+
);
|
|
313
|
+
await store
|
|
314
|
+
.delete(key)
|
|
315
|
+
.catch((e) =>
|
|
316
|
+
reportCacheError(e, "cache-delete", `[CacheScope] ${key}: evict`),
|
|
317
|
+
);
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
446
320
|
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
321
|
+
// A hit serves content that was tagged at write time, so the document
|
|
322
|
+
// tag union must include this entry's tags for updateTag()/revalidateTag()
|
|
323
|
+
// to invalidate any full-page entry built on top of it. The write path
|
|
324
|
+
// records via cacheRoute (resolveCacheTags); the hit path records here.
|
|
325
|
+
recordRequestTags(cached.tags);
|
|
326
|
+
|
|
327
|
+
// Replay handle data. An empty string means the route pushed no handles —
|
|
328
|
+
// skip the decode entirely (the common case). Otherwise decode the
|
|
329
|
+
// Flight-encoded blob; a decode failure skips handle restore but keeps the
|
|
330
|
+
// valid cached segments.
|
|
331
|
+
const handleStore = _getRequestContext()?._handleStore;
|
|
332
|
+
if (handleStore && cached.handles) {
|
|
333
|
+
const handlesRecord = await decodeHandles(cached.handles);
|
|
334
|
+
if (handlesRecord) {
|
|
335
|
+
restoreHandles(handlesRecord, handleStore);
|
|
454
336
|
}
|
|
455
337
|
}
|
|
456
338
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
339
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
340
|
+
const segmentTypes = segments.map((s) =>
|
|
341
|
+
s.type === "parallel" ? s.slot : s.type,
|
|
342
|
+
);
|
|
343
|
+
debugCacheLog(
|
|
344
|
+
`[CacheScope] ${shouldRevalidate ? "STALE" : "HIT"}: ${key} (${segmentTypes.join(", ")})`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
463
347
|
|
|
464
348
|
return { segments, shouldRevalidate };
|
|
465
349
|
} catch (error) {
|
|
466
|
-
|
|
350
|
+
// Covers a store.get() failure AND a throwing consumer key()/keyGenerator
|
|
351
|
+
// (resolveKey). Either way degrade to a cache miss so the render proceeds.
|
|
352
|
+
reportCacheError(
|
|
353
|
+
error,
|
|
354
|
+
"cache-read",
|
|
355
|
+
`[CacheScope] lookup ${key ?? "(key resolution failed)"}`,
|
|
356
|
+
);
|
|
467
357
|
return null;
|
|
468
358
|
}
|
|
469
359
|
}
|
|
470
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Record this scope's segment-DSL cache({ tags }) into the request tag union
|
|
363
|
+
* synchronously, under the same gate cacheRoute() uses for a write.
|
|
364
|
+
*
|
|
365
|
+
* cacheRoute() already records these tags, but it is invoked inside
|
|
366
|
+
* requestCtx.waitUntil() by the cache-store middleware (and the proactive path
|
|
367
|
+
* re-resolves the whole tree before calling it), so its recording is deferred
|
|
368
|
+
* and RACES the document cache's post-body-drain snapshot of _requestTags. On a
|
|
369
|
+
* first-write (segment-cache miss) the document tag union could miss these
|
|
370
|
+
* tags, and updateTag()/revalidateTag() would then fail to invalidate the
|
|
371
|
+
* cached document until a later write reseeded it. Calling this synchronously
|
|
372
|
+
* in the request pipeline (before the snapshot) closes that window. Idempotent
|
|
373
|
+
* (the tag union is a Set), so the duplicate record in cacheRoute is harmless.
|
|
374
|
+
*/
|
|
375
|
+
recordTags(requestCtx: RequestContext | undefined): void {
|
|
376
|
+
if (!this.enabled) return;
|
|
377
|
+
if (!this.conditionAllows("write")) return;
|
|
378
|
+
recordRequestTags(resolveCacheTags(this.config, requestCtx), requestCtx);
|
|
379
|
+
}
|
|
380
|
+
|
|
471
381
|
/**
|
|
472
382
|
* Cache all segments for a route (non-blocking via waitUntil)
|
|
473
383
|
* Single cache entry per route request.
|
|
@@ -482,9 +392,10 @@ export class CacheScope {
|
|
|
482
392
|
pathname: string,
|
|
483
393
|
params: Record<string, string>,
|
|
484
394
|
segments: ResolvedSegment[],
|
|
485
|
-
isIntercept?: boolean
|
|
395
|
+
isIntercept?: boolean,
|
|
486
396
|
): Promise<void> {
|
|
487
397
|
if (!this.enabled || segments.length === 0) return;
|
|
398
|
+
if (!this.conditionAllows("write")) return;
|
|
488
399
|
|
|
489
400
|
const store = this.getStore();
|
|
490
401
|
if (!store) return;
|
|
@@ -505,47 +416,102 @@ export class CacheScope {
|
|
|
505
416
|
// Resolve cache key early (while request context is available)
|
|
506
417
|
const key = await this.resolveKey(pathname, params, isIntercept);
|
|
507
418
|
|
|
419
|
+
// Resolve tags early (while request context is available, before waitUntil)
|
|
420
|
+
const tags = resolveCacheTags(this.config, requestCtx);
|
|
421
|
+
recordRequestTags(tags, requestCtx);
|
|
422
|
+
|
|
508
423
|
// Check if this is a partial request (navigation) vs document request
|
|
509
|
-
const isPartial = requestCtx.
|
|
424
|
+
const isPartial = requestCtx.originalUrl.searchParams.has("_rsc_partial");
|
|
425
|
+
|
|
426
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
427
|
+
debugCacheLog(
|
|
428
|
+
`[CacheScope] cacheRoute: scheduling waitUntil for ${key} (${nonLoaderSegments.length} segments, isPartial=${isPartial})`,
|
|
429
|
+
);
|
|
430
|
+
}
|
|
510
431
|
|
|
511
432
|
requestCtx.waitUntil(async () => {
|
|
433
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
434
|
+
debugCacheLog(
|
|
435
|
+
`[CacheScope] waitUntil: awaiting handleStore.settled for ${key}`,
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
512
439
|
await handleStore.settled;
|
|
513
440
|
|
|
514
|
-
|
|
515
|
-
|
|
441
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
442
|
+
debugCacheLog(`[CacheScope] waitUntil: handleStore settled for ${key}`);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// For document requests: only cache if layout segments have components
|
|
446
|
+
// (complete render). Parallel and route segments may legitimately have
|
|
447
|
+
// null components — UI-less @meta parallels return null, and void route
|
|
448
|
+
// handlers produce null when the UI lives in parallel slots/layouts.
|
|
449
|
+
// Partial requests always allow null components (client already has them).
|
|
516
450
|
if (!isPartial) {
|
|
517
|
-
const
|
|
518
|
-
(s) => s.component
|
|
451
|
+
const hasIncompleteLayouts = nonLoaderSegments.some(
|
|
452
|
+
(s) => s.component === null && s.type === "layout",
|
|
519
453
|
);
|
|
520
|
-
if (
|
|
454
|
+
if (hasIncompleteLayouts) {
|
|
455
|
+
const nullSegments = nonLoaderSegments
|
|
456
|
+
.filter((s) => s.component === null && s.type === "layout")
|
|
457
|
+
.map((s) => s.id);
|
|
458
|
+
const error = new Error(
|
|
459
|
+
`[CacheScope] Cache write skipped: layout segments have null components ` +
|
|
460
|
+
`(${nullSegments.join(", ")}). This indicates an incomplete render — ` +
|
|
461
|
+
`layout handlers must return JSX for document requests to be cacheable.`,
|
|
462
|
+
);
|
|
463
|
+
error.name = "CacheScopeInvariantError";
|
|
464
|
+
console.error(error.message);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
521
467
|
}
|
|
522
468
|
|
|
523
469
|
// Collect handle data for non-loader segments only
|
|
524
|
-
const handles
|
|
525
|
-
for (const seg of nonLoaderSegments) {
|
|
526
|
-
handles[seg.id] = handleStore.getDataForSegment(seg.id);
|
|
527
|
-
}
|
|
470
|
+
const handles = captureHandles(nonLoaderSegments, handleStore);
|
|
528
471
|
|
|
529
472
|
try {
|
|
530
|
-
|
|
531
|
-
|
|
473
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
474
|
+
debugCacheLog(
|
|
475
|
+
`[CacheScope] waitUntil: serializing ${nonLoaderSegments.length} segments for ${key}`,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Serialize segments and Flight-encode handles in parallel. Handles go
|
|
480
|
+
// through the codec (not raw into the entry) so Promise/ReactNode handle
|
|
481
|
+
// values survive a JSON-serializing store — see encodeHandles.
|
|
482
|
+
const { serializeSegments } = await import("./segment-codec.js");
|
|
483
|
+
const [serializedSegments, encodedHandles] = await Promise.all([
|
|
484
|
+
serializeSegments(nonLoaderSegments),
|
|
485
|
+
encodeHandles(handles),
|
|
486
|
+
]);
|
|
532
487
|
|
|
533
488
|
const data: CachedEntryData = {
|
|
534
489
|
segments: serializedSegments,
|
|
535
|
-
handles,
|
|
490
|
+
handles: encodedHandles,
|
|
536
491
|
expiresAt: Date.now() + ttl * 1000,
|
|
492
|
+
tags,
|
|
537
493
|
};
|
|
538
494
|
|
|
495
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
496
|
+
debugCacheLog(`[CacheScope] waitUntil: calling store.set for ${key}`);
|
|
497
|
+
}
|
|
498
|
+
|
|
539
499
|
await store.set(key, data, ttl, swr);
|
|
540
500
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
501
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
502
|
+
const segmentTypes = nonLoaderSegments.map((s) =>
|
|
503
|
+
s.type === "parallel" ? s.slot : s.type,
|
|
504
|
+
);
|
|
505
|
+
debugCacheLog(
|
|
506
|
+
`[CacheScope] Cached: ${key} (${segmentTypes.join(", ")}) ttl=${ttl}s [loaders excluded]`,
|
|
507
|
+
);
|
|
508
|
+
}
|
|
547
509
|
} catch (error) {
|
|
548
|
-
|
|
510
|
+
reportCacheError(
|
|
511
|
+
error,
|
|
512
|
+
"cache-write",
|
|
513
|
+
`[CacheScope] Failed to cache ${key}`,
|
|
514
|
+
);
|
|
549
515
|
}
|
|
550
516
|
});
|
|
551
517
|
}
|
|
@@ -556,7 +522,7 @@ export class CacheScope {
|
|
|
556
522
|
*/
|
|
557
523
|
export function createCacheScope(
|
|
558
524
|
config: { options: PartialCacheOptions | false } | undefined,
|
|
559
|
-
parent: CacheScope | null = null
|
|
525
|
+
parent: CacheScope | null = null,
|
|
560
526
|
): CacheScope | null {
|
|
561
527
|
if (!config) return parent; // No config, inherit parent
|
|
562
528
|
return new CacheScope(config.options, parent);
|