@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
|
@@ -12,20 +12,26 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { MiddlewareFn, MiddlewareContext } from "../router/middleware.js";
|
|
15
|
-
import {
|
|
15
|
+
import { hasPerClientSignal } from "../browser/cookie-name.js";
|
|
16
|
+
import {
|
|
17
|
+
getRequestContext,
|
|
18
|
+
runWithRequestContext,
|
|
19
|
+
type RequestContext,
|
|
20
|
+
} from "../server/request-context.js";
|
|
21
|
+
import { mayNeedSSR } from "../rsc/ssr-setup.js";
|
|
22
|
+
import { sortedSearchString } from "./cache-key-utils.js";
|
|
23
|
+
import { runBackground } from "./background-task.js";
|
|
24
|
+
import { reportCacheError } from "./cache-error.js";
|
|
16
25
|
|
|
17
|
-
// ============================================================================
|
|
18
|
-
// Constants
|
|
19
|
-
// ============================================================================
|
|
20
|
-
|
|
21
|
-
/** Header indicating cache status for debugging */
|
|
22
26
|
const CACHE_STATUS_HEADER = "x-document-cache-status";
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
function collectRequestTags(
|
|
29
|
+
requestCtx: RequestContext | undefined,
|
|
30
|
+
): string[] | undefined {
|
|
31
|
+
const tags = requestCtx?._requestTags;
|
|
32
|
+
return tags && tags.size > 0 ? [...tags] : undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
29
35
|
function hashSegmentIds(segmentIds: string): string {
|
|
30
36
|
if (!segmentIds) return "";
|
|
31
37
|
|
|
@@ -34,12 +40,9 @@ function hashSegmentIds(segmentIds: string): string {
|
|
|
34
40
|
const char = segmentIds.charCodeAt(i);
|
|
35
41
|
hash = ((hash << 5) - hash + char) | 0;
|
|
36
42
|
}
|
|
37
|
-
// Convert to base36 for shorter string, take absolute value
|
|
38
43
|
return Math.abs(hash).toString(36);
|
|
39
44
|
}
|
|
40
45
|
|
|
41
|
-
// ============================================================================
|
|
42
|
-
// Cache Control Parsing
|
|
43
46
|
// ============================================================================
|
|
44
47
|
|
|
45
48
|
interface CacheDirectives {
|
|
@@ -53,6 +56,27 @@ interface CacheDirectives {
|
|
|
53
56
|
function parseCacheControl(header: string | null): CacheDirectives | null {
|
|
54
57
|
if (!header) return null;
|
|
55
58
|
|
|
59
|
+
// RFC 7234: in a SHARED cache, `private` and `no-store` forbid storage and
|
|
60
|
+
// MUST win over `s-maxage` even though `private, s-maxage` is contradictory.
|
|
61
|
+
// The document cache is a shared edge store, so refuse both regardless of any
|
|
62
|
+
// s-maxage / stale-while-revalidate also present. Match standalone directive
|
|
63
|
+
// tokens (start/end, whitespace, comma, semicolon, or `=` bounded), not a
|
|
64
|
+
// substring, so a value containing "private" cannot false-veto.
|
|
65
|
+
if (/(^|[\s,;])(private|no-store)(?=$|[\s,;=])/i.test(header)) {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// RFC 7234 §5.2.2.2: a shared cache MUST NOT serve a stored `no-cache`
|
|
70
|
+
// response without successful origin validation. This store's hit path has no
|
|
71
|
+
// validation step, so serving a stored no-cache response within s-maxage
|
|
72
|
+
// would hand the client content the origin marked must-revalidate. Refuse to
|
|
73
|
+
// store it. Only UNqualified `no-cache` (no `=`) vetoes — the field-name-
|
|
74
|
+
// scoped `no-cache="set-cookie"` form IS storable per the RFC, so the `=`
|
|
75
|
+
// boundary is excluded from the lookahead (unlike private/no-store above).
|
|
76
|
+
if (/(^|[\s,;])no-cache(?=$|[\s,;])/i.test(header)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
const directives: CacheDirectives = {};
|
|
57
81
|
|
|
58
82
|
// Parse s-maxage
|
|
@@ -84,6 +108,16 @@ function shouldCacheResponse(response: Response): CacheDirectives | null {
|
|
|
84
108
|
return null;
|
|
85
109
|
}
|
|
86
110
|
|
|
111
|
+
// Never cache a per-client signal into a SHARED response store. A Set-Cookie
|
|
112
|
+
// (e.g. a rango state rotation from invalidateClientCache(), or any cookie a
|
|
113
|
+
// loader set) would be replayed to every client on a hit — pinning them to
|
|
114
|
+
// one value and even rolling a rotated client back to a prior one. The
|
|
115
|
+
// x-rango-keep-cache directive header is the mirror image: a replayed "keep"
|
|
116
|
+
// would suppress invalidation for every replayed client. Refuse both.
|
|
117
|
+
if (hasPerClientSignal(response.headers)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
87
121
|
const cacheControl = response.headers.get("Cache-Control");
|
|
88
122
|
return parseCacheControl(cacheControl);
|
|
89
123
|
}
|
|
@@ -110,15 +144,24 @@ function addCacheStatusHeader(
|
|
|
110
144
|
}
|
|
111
145
|
|
|
112
146
|
/**
|
|
113
|
-
*
|
|
147
|
+
* Drain and run onResponse callbacks registered on the request context.
|
|
148
|
+
* Mirrors the drain semantics of finalizeResponse() in rsc/helpers.ts:
|
|
149
|
+
* callbacks are spliced out so they fire at most once per request.
|
|
114
150
|
*/
|
|
115
|
-
function
|
|
151
|
+
function drainOnResponseCallbacks(
|
|
116
152
|
response: Response,
|
|
117
|
-
|
|
153
|
+
requestCtx:
|
|
154
|
+
| { _onResponseCallbacks: Array<(r: Response) => Response> }
|
|
155
|
+
| undefined,
|
|
118
156
|
): Response {
|
|
157
|
+
if (!requestCtx || requestCtx._onResponseCallbacks.length === 0) {
|
|
158
|
+
return response;
|
|
159
|
+
}
|
|
160
|
+
const callbacks = requestCtx._onResponseCallbacks;
|
|
161
|
+
requestCtx._onResponseCallbacks = [];
|
|
119
162
|
let result = response;
|
|
120
163
|
for (const callback of callbacks) {
|
|
121
|
-
result = callback(result);
|
|
164
|
+
result = callback(result) ?? result;
|
|
122
165
|
}
|
|
123
166
|
return result;
|
|
124
167
|
}
|
|
@@ -185,9 +228,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
185
228
|
): MiddlewareFn<TEnv> {
|
|
186
229
|
const { skipPaths = [], keyGenerator, isEnabled, debug = false } = options;
|
|
187
230
|
|
|
188
|
-
const log = debug
|
|
189
|
-
? (message: string) => console.log(message)
|
|
190
|
-
: () => {};
|
|
231
|
+
const log = debug ? (message: string) => console.log(message) : () => {};
|
|
191
232
|
|
|
192
233
|
return async function documentCacheMiddleware(
|
|
193
234
|
ctx: MiddlewareContext<TEnv>,
|
|
@@ -195,13 +236,24 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
195
236
|
): Promise<Response> {
|
|
196
237
|
const url = ctx.url;
|
|
197
238
|
|
|
239
|
+
// Use the original request URL for _rsc* param detection and cache key
|
|
240
|
+
// differentiation. ctx.url is stripped of _rsc* params by the middleware
|
|
241
|
+
// pipeline (stripInternalParams), so _rsc_partial, _rsc_segments, etc.
|
|
242
|
+
// are not visible on ctx.url in production.
|
|
243
|
+
const rawUrl = new URL(ctx.request.url);
|
|
244
|
+
|
|
245
|
+
// Only cache GET requests — mutations and other methods must not be cached
|
|
246
|
+
if (ctx.request.method !== "GET") {
|
|
247
|
+
return next();
|
|
248
|
+
}
|
|
249
|
+
|
|
198
250
|
// Skip RSC action requests (mutations shouldn't be cached)
|
|
199
|
-
if (
|
|
251
|
+
if (rawUrl.searchParams.has("_rsc_action")) {
|
|
200
252
|
return next();
|
|
201
253
|
}
|
|
202
254
|
|
|
203
255
|
// Skip loader requests (have their own caching)
|
|
204
|
-
if (
|
|
256
|
+
if (rawUrl.searchParams.has("_rsc_loader")) {
|
|
205
257
|
return next();
|
|
206
258
|
}
|
|
207
259
|
|
|
@@ -227,21 +279,38 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
227
279
|
return next();
|
|
228
280
|
}
|
|
229
281
|
|
|
230
|
-
// Determine request type for cache key differentiation
|
|
231
|
-
|
|
232
|
-
|
|
282
|
+
// Determine request type for cache key differentiation.
|
|
283
|
+
// Uses rawUrl for _rsc* param checks and mayNeedSSR for Accept-based
|
|
284
|
+
// detection. Full-document RSC fetches must not share the HTML cache slot.
|
|
285
|
+
const isPartial = rawUrl.searchParams.has("_rsc_partial");
|
|
286
|
+
const isRscRequest = !mayNeedSSR(ctx.request, rawUrl);
|
|
287
|
+
const typeLabel = isRscRequest ? "RSC" : "HTML";
|
|
233
288
|
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
const clientSegments = url.searchParams.get("_rsc_segments") || "";
|
|
238
|
-
const segmentHash = isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
239
|
-
const typeSuffix = isPartial ? ":rsc" : ":html";
|
|
240
|
-
const cacheKey = keyGenerator
|
|
241
|
-
? keyGenerator(url) + segmentHash + typeSuffix
|
|
242
|
-
: `${url.pathname}${segmentHash}${typeSuffix}`;
|
|
289
|
+
// Track whether next() has been called so the catch block knows
|
|
290
|
+
// whether it is safe to fall through to the handler.
|
|
291
|
+
let handlerCalled = false;
|
|
243
292
|
|
|
244
293
|
try {
|
|
294
|
+
// Generate cache key inside try so a throwing keyGenerator degrades
|
|
295
|
+
// gracefully to the origin handler instead of rejecting the request.
|
|
296
|
+
// This is a deliberate fail-open-to-origin policy: the fallback is
|
|
297
|
+
// "serve uncached from origin", not "use a different cache key".
|
|
298
|
+
const clientSegments = rawUrl.searchParams.get("_rsc_segments") || "";
|
|
299
|
+
const segmentHash =
|
|
300
|
+
isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
301
|
+
const typeSuffix = isRscRequest ? ":rsc" : ":html";
|
|
302
|
+
|
|
303
|
+
let searchSuffix = "";
|
|
304
|
+
if (!keyGenerator) {
|
|
305
|
+
const sorted = sortedSearchString(url.searchParams);
|
|
306
|
+
if (sorted) {
|
|
307
|
+
searchSuffix = `?${sorted}`;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const cacheKey = keyGenerator
|
|
312
|
+
? keyGenerator(url) + segmentHash + typeSuffix
|
|
313
|
+
: `${url.pathname}${searchSuffix}${segmentHash}${typeSuffix}`;
|
|
245
314
|
// 1. Check cache
|
|
246
315
|
const cached = await store.getResponse(cacheKey);
|
|
247
316
|
|
|
@@ -249,79 +318,112 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
249
318
|
if (!cached.shouldRevalidate) {
|
|
250
319
|
// Fresh hit - return immediately
|
|
251
320
|
log(`[DocumentCache] HIT ${typeLabel}: ${url.pathname}`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
response,
|
|
257
|
-
requestCtx._onResponseCallbacks,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
return response;
|
|
321
|
+
return drainOnResponseCallbacks(
|
|
322
|
+
addCacheStatusHeader(cached.response, "HIT"),
|
|
323
|
+
requestCtx,
|
|
324
|
+
);
|
|
261
325
|
}
|
|
262
326
|
|
|
263
327
|
// Stale hit - return cached response, revalidate in background
|
|
264
|
-
log(
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
328
|
+
log(
|
|
329
|
+
`[DocumentCache] STALE ${typeLabel}: ${url.pathname} (revalidating)`,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
runBackground(requestCtx, async () => {
|
|
333
|
+
try {
|
|
334
|
+
// Re-establish the request-context ALS around the background
|
|
335
|
+
// re-render: next() re-runs the full handler pipeline, and on
|
|
336
|
+
// workerd a waitUntil task runs detached from the request's I/O
|
|
337
|
+
// context, so a handler/component reading getRequestContext() would
|
|
338
|
+
// otherwise throw. Same fix as the route-level/use-cache background
|
|
339
|
+
// revalidation paths.
|
|
340
|
+
const fresh = await runWithRequestContext(requestCtx, () => next());
|
|
341
|
+
const directives = shouldCacheResponse(fresh);
|
|
342
|
+
|
|
343
|
+
if (directives && fresh.body) {
|
|
344
|
+
// Background revalidation: nothing streams to a client, so drain
|
|
345
|
+
// the fresh render fully before snapshotting tags (same
|
|
346
|
+
// render-complete barrier as the miss path).
|
|
347
|
+
const body = await new Response(fresh.body).arrayBuffer();
|
|
348
|
+
await store.putResponse!(
|
|
349
|
+
cacheKey,
|
|
350
|
+
new Response(body, fresh),
|
|
351
|
+
directives.sMaxAge!,
|
|
352
|
+
directives.staleWhileRevalidate,
|
|
353
|
+
collectRequestTags(requestCtx),
|
|
354
|
+
);
|
|
355
|
+
log(`[DocumentCache] REVALIDATED ${typeLabel}: ${url.pathname}`);
|
|
283
356
|
}
|
|
284
|
-
})
|
|
285
|
-
|
|
357
|
+
} catch (error) {
|
|
358
|
+
// Pass requestCtx explicitly: this runs in a detached waitUntil task
|
|
359
|
+
// where the ALS context is gone, so onError only fires if we hand it
|
|
360
|
+
// the captured context (reportCacheError falls back to _getRequestContext
|
|
361
|
+
// otherwise, which is null here).
|
|
362
|
+
reportCacheError(
|
|
363
|
+
error,
|
|
364
|
+
"cache-write",
|
|
365
|
+
"[DocumentCache] revalidation",
|
|
366
|
+
requestCtx,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
286
370
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
response,
|
|
292
|
-
requestCtx._onResponseCallbacks,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
return response;
|
|
371
|
+
return drainOnResponseCallbacks(
|
|
372
|
+
addCacheStatusHeader(cached.response, "STALE"),
|
|
373
|
+
requestCtx,
|
|
374
|
+
);
|
|
296
375
|
}
|
|
297
376
|
|
|
298
377
|
// 2. Cache miss - run handler
|
|
378
|
+
handlerCalled = true;
|
|
299
379
|
const originalResponse = await next();
|
|
300
380
|
|
|
301
381
|
// 3. Cache if response has appropriate headers
|
|
302
382
|
const directives = shouldCacheResponse(originalResponse);
|
|
303
383
|
|
|
304
384
|
if (directives) {
|
|
305
|
-
log(
|
|
385
|
+
log(
|
|
386
|
+
`[DocumentCache] MISS ${typeLabel}: ${url.pathname} (caching with s-maxage=${directives.sMaxAge})`,
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
// If the response has no body (e.g., 200 with empty body), skip caching
|
|
390
|
+
if (!originalResponse.body) {
|
|
391
|
+
return originalResponse;
|
|
392
|
+
}
|
|
306
393
|
|
|
307
394
|
// Tee the body so we can return one stream and cache the other
|
|
308
|
-
const [returnStream, cacheStream] = originalResponse.body
|
|
395
|
+
const [returnStream, cacheStream] = originalResponse.body.tee();
|
|
309
396
|
|
|
310
397
|
// Clone response for caching (non-blocking)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
398
|
+
runBackground(requestCtx, async () => {
|
|
399
|
+
try {
|
|
400
|
+
// Drain the cache copy fully BEFORE snapshotting tags. Tags from
|
|
401
|
+
// Suspense-streamed "use cache"/cacheTag and loaders are recorded as
|
|
402
|
+
// each value resolves during the RSC/HTML render, which completes
|
|
403
|
+
// only when the stream ends - the handler-settlement barrier is too
|
|
404
|
+
// early. Buffering the body (the client streams the other tee branch,
|
|
405
|
+
// unaffected) is the render-complete barrier that keeps the cached
|
|
406
|
+
// body and its tag set consistent.
|
|
407
|
+
const body = await new Response(cacheStream).arrayBuffer();
|
|
408
|
+
await store.putResponse!(
|
|
409
|
+
cacheKey,
|
|
410
|
+
new Response(body, originalResponse),
|
|
411
|
+
directives.sMaxAge!,
|
|
412
|
+
directives.staleWhileRevalidate,
|
|
413
|
+
collectRequestTags(requestCtx),
|
|
414
|
+
);
|
|
415
|
+
} catch (error) {
|
|
416
|
+
// Detached waitUntil task — pass the captured requestCtx so onError
|
|
417
|
+
// fires even though the ALS context is gone (see the revalidation
|
|
418
|
+
// catch above).
|
|
419
|
+
reportCacheError(
|
|
420
|
+
error,
|
|
421
|
+
"cache-write",
|
|
422
|
+
"[DocumentCache] cache write",
|
|
423
|
+
requestCtx,
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
325
427
|
|
|
326
428
|
return addCacheStatusHeader(
|
|
327
429
|
new Response(returnStream, originalResponse),
|
|
@@ -332,8 +434,13 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
332
434
|
// No cache headers - pass through
|
|
333
435
|
return originalResponse;
|
|
334
436
|
} catch (error) {
|
|
335
|
-
|
|
336
|
-
|
|
437
|
+
reportCacheError(error, "cache-read", "[DocumentCache] middleware");
|
|
438
|
+
if (handlerCalled) {
|
|
439
|
+
// Post-handler failure (e.g. body.tee()): do not call next() again
|
|
440
|
+
// as that would re-run handler side effects.
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
// Pre-handler failure (cache lookup): degrade gracefully to origin
|
|
337
444
|
return next();
|
|
338
445
|
}
|
|
339
446
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle Capture
|
|
3
|
+
*
|
|
4
|
+
* Captures handle pushes during cached function execution.
|
|
5
|
+
* Extracted from cache-runtime.ts so tests can import without
|
|
6
|
+
* pulling in @vitejs/plugin-rsc/rsc dependencies.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { HandleStore } from "../server/handle-store.js";
|
|
10
|
+
import type { SegmentHandleData } from "./types.js";
|
|
11
|
+
|
|
12
|
+
export interface HandleCapture {
|
|
13
|
+
data: Record<string, SegmentHandleData>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Active capture tokens per HandleStore.
|
|
18
|
+
*
|
|
19
|
+
* Instead of mutating handleStore.push (which breaks when overlapping
|
|
20
|
+
* captures finish out of order), we install a single interceptor on
|
|
21
|
+
* first use and manage a set of active capture tokens. Each push fans
|
|
22
|
+
* out to every active token. Stopping a capture simply removes the
|
|
23
|
+
* token — order does not matter.
|
|
24
|
+
*/
|
|
25
|
+
const activeCapturesMap = new WeakMap<HandleStore, Set<HandleCapture>>();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* One-time interceptor installation. Wraps the original push so every
|
|
29
|
+
* call fans out to all active capture tokens. Installed once per
|
|
30
|
+
* HandleStore instance; subsequent startHandleCapture calls on the
|
|
31
|
+
* same store just add tokens to the Set.
|
|
32
|
+
*/
|
|
33
|
+
function ensureInterceptorInstalled(handleStore: HandleStore): void {
|
|
34
|
+
if (activeCapturesMap.has(handleStore)) return;
|
|
35
|
+
|
|
36
|
+
const captures = new Set<HandleCapture>();
|
|
37
|
+
activeCapturesMap.set(handleStore, captures);
|
|
38
|
+
|
|
39
|
+
const originalPush = handleStore.push.bind(handleStore);
|
|
40
|
+
handleStore.push = (
|
|
41
|
+
handleName: string,
|
|
42
|
+
segmentId: string,
|
|
43
|
+
value: unknown,
|
|
44
|
+
) => {
|
|
45
|
+
for (const capture of captures) {
|
|
46
|
+
if (!capture.data[segmentId]) {
|
|
47
|
+
capture.data[segmentId] = {};
|
|
48
|
+
}
|
|
49
|
+
if (!capture.data[segmentId][handleName]) {
|
|
50
|
+
capture.data[segmentId][handleName] = [];
|
|
51
|
+
}
|
|
52
|
+
capture.data[segmentId][handleName].push(value);
|
|
53
|
+
}
|
|
54
|
+
originalPush(handleName, segmentId, value);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Start capturing handle pushes for a cached function execution.
|
|
60
|
+
*
|
|
61
|
+
* Concurrency-safe: multiple overlapping captures on the same
|
|
62
|
+
* HandleStore are independent. Each capture registers a token in a
|
|
63
|
+
* Set; stopping removes it. No ordering requirement (LIFO not needed).
|
|
64
|
+
*/
|
|
65
|
+
export function startHandleCapture(handleStore: HandleStore): {
|
|
66
|
+
capture: HandleCapture;
|
|
67
|
+
stop: () => void;
|
|
68
|
+
} {
|
|
69
|
+
ensureInterceptorInstalled(handleStore);
|
|
70
|
+
|
|
71
|
+
const capture: HandleCapture = { data: {} };
|
|
72
|
+
const captures = activeCapturesMap.get(handleStore)!;
|
|
73
|
+
captures.add(capture);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
capture,
|
|
77
|
+
stop() {
|
|
78
|
+
captures.delete(capture);
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle Snapshot
|
|
3
|
+
*
|
|
4
|
+
* Capture and restore handle data for cached segments.
|
|
5
|
+
* Handle data (breadcrumbs, metadata from ctx.use(Handle)) is collected
|
|
6
|
+
* during segment resolution and stored alongside cached segments.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ResolvedSegment } from "../types.js";
|
|
10
|
+
import type { HandleStore } from "../server/handle-store.js";
|
|
11
|
+
import type { SegmentHandleData } from "./types.js";
|
|
12
|
+
// segment-codec eagerly pulls @vitejs/plugin-rsc (a virtual: module unresolvable
|
|
13
|
+
// in plain node/vitest). It is imported LAZILY inside the two async encode/decode
|
|
14
|
+
// helpers below so that modules which import handle-snapshot only for the
|
|
15
|
+
// plugin-rsc-free captureHandles/restoreHandles (e.g. cache-scope, on dispatch's
|
|
16
|
+
// lazy response-route cache path) do not pull plugin-rsc at module load. Behavior
|
|
17
|
+
// is unchanged: both helpers are async and already awaited the codec.
|
|
18
|
+
|
|
19
|
+
const HANDLE_ENCODE_TIMEOUT_MS = 5000;
|
|
20
|
+
|
|
21
|
+
type HandleRecord = Record<string, SegmentHandleData>;
|
|
22
|
+
|
|
23
|
+
function hasHandleData(handles: HandleRecord): boolean {
|
|
24
|
+
for (const segId in handles) {
|
|
25
|
+
for (const _ in handles[segId]) return true;
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function withTimeout<T>(p: Promise<T>, ms: number, onTimeout: T): Promise<T> {
|
|
31
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
32
|
+
const timeout = new Promise<T>((resolve) => {
|
|
33
|
+
timer = setTimeout(() => resolve(onTimeout), ms);
|
|
34
|
+
});
|
|
35
|
+
return Promise.race([
|
|
36
|
+
p.then(
|
|
37
|
+
(v) => {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
return v;
|
|
40
|
+
},
|
|
41
|
+
(e) => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
throw e;
|
|
44
|
+
},
|
|
45
|
+
),
|
|
46
|
+
timeout,
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function encodeHandles(handles: HandleRecord): Promise<string> {
|
|
51
|
+
if (!hasHandleData(handles)) return "";
|
|
52
|
+
return encodeHandleValue(handles);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function decodeHandles(encoded: string): Promise<HandleRecord | null> {
|
|
56
|
+
return decodeHandleValue<HandleRecord>(encoded);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function encodeHandleValue(value: unknown): Promise<string> {
|
|
60
|
+
const { serializeResult } = await import("./segment-codec.js");
|
|
61
|
+
const encoded = await withTimeout(
|
|
62
|
+
serializeResult(value),
|
|
63
|
+
HANDLE_ENCODE_TIMEOUT_MS,
|
|
64
|
+
null,
|
|
65
|
+
);
|
|
66
|
+
return encoded ?? "";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Decode a Flight-encoded handle-data string. Returns null on any decode
|
|
71
|
+
* failure so the caller can skip handle restore without discarding valid
|
|
72
|
+
* cached/prerendered segments.
|
|
73
|
+
*/
|
|
74
|
+
export async function decodeHandleValue<T>(encoded: string): Promise<T | null> {
|
|
75
|
+
try {
|
|
76
|
+
const { deserializeResult } = await import("./segment-codec.js");
|
|
77
|
+
return await deserializeResult<T>(encoded);
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Capture handle data for a set of segments from the handle store.
|
|
85
|
+
* Used when caching segments to preserve their handle data.
|
|
86
|
+
*/
|
|
87
|
+
export function captureHandles(
|
|
88
|
+
segments: ResolvedSegment[],
|
|
89
|
+
handleStore: HandleStore,
|
|
90
|
+
): Record<string, SegmentHandleData> {
|
|
91
|
+
const handles: Record<string, SegmentHandleData> = {};
|
|
92
|
+
for (const seg of segments) {
|
|
93
|
+
handles[seg.id] = handleStore.getDataForSegment(seg.id);
|
|
94
|
+
}
|
|
95
|
+
return handles;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Restore handle data from a cached snapshot into the handle store.
|
|
100
|
+
* Used when serving cached segments to replay their handle data.
|
|
101
|
+
*/
|
|
102
|
+
export function restoreHandles(
|
|
103
|
+
handles: Record<string, SegmentHandleData>,
|
|
104
|
+
handleStore: HandleStore,
|
|
105
|
+
): void {
|
|
106
|
+
for (const [segId, segHandles] of Object.entries(handles)) {
|
|
107
|
+
if (Object.keys(segHandles).length > 0) {
|
|
108
|
+
handleStore.replaySegmentData(segId, segHandles);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/cache/index.ts
CHANGED
|
@@ -1,58 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache Store
|
|
3
|
-
*
|
|
4
|
-
* Server-side caching for RSC segments and loader data.
|
|
5
|
-
*
|
|
6
|
-
* Main exports for users:
|
|
7
|
-
* - SegmentCacheStore - Interface for implementing custom cache stores
|
|
8
|
-
* - MemorySegmentCacheStore - In-memory cache for development/testing
|
|
9
|
-
* - CFCacheStore - Cloudflare edge cache store for production
|
|
10
|
-
* - CacheScope / createCacheScope - Request-scoped cache provider
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
// Generic cache store types (reserved for future extensibility)
|
|
14
|
-
// These types support caching arbitrary values like Response, Stream, etc.
|
|
15
|
-
// Currently unused - segment caching uses SegmentCacheStore directly.
|
|
16
|
-
export type {
|
|
17
|
-
CacheStore,
|
|
18
|
-
CacheEntry,
|
|
19
|
-
CacheValue,
|
|
20
|
-
CacheValueType,
|
|
21
|
-
CachePutOptions,
|
|
22
|
-
CacheMetadata,
|
|
23
|
-
} from "./types.js";
|
|
24
|
-
|
|
25
|
-
// Generic memory cache (reserved for future extensibility)
|
|
26
|
-
export { MemoryCacheStore } from "./memory-store.js";
|
|
27
|
-
|
|
28
|
-
// Segment cache store types and implementations
|
|
29
1
|
export type {
|
|
30
2
|
SegmentCacheStore,
|
|
31
|
-
SegmentCacheProvider,
|
|
32
3
|
CachedEntryData,
|
|
33
|
-
CachedEntryResult,
|
|
34
4
|
CacheGetResult,
|
|
5
|
+
CacheItemResult,
|
|
6
|
+
CacheItemOptions,
|
|
7
|
+
ShellCacheEntry,
|
|
35
8
|
SerializedSegmentData,
|
|
36
9
|
SegmentHandleData,
|
|
37
|
-
CacheConfig,
|
|
38
|
-
CacheConfigOrFactory,
|
|
39
10
|
} from "./types.js";
|
|
40
11
|
|
|
41
12
|
export { MemorySegmentCacheStore } from "./memory-segment-store.js";
|
|
42
13
|
|
|
43
|
-
// Cloudflare cache store
|
|
44
14
|
export {
|
|
45
15
|
CFCacheStore,
|
|
46
16
|
type CFCacheStoreOptions,
|
|
17
|
+
type CFCacheDebug,
|
|
18
|
+
type CFCacheReadDebugEvent,
|
|
19
|
+
type KVNamespace,
|
|
47
20
|
CACHE_STALE_AT_HEADER,
|
|
48
21
|
CACHE_STATUS_HEADER,
|
|
22
|
+
CACHE_REVALIDATING_AT_HEADER,
|
|
23
|
+
EDGE_LOOKUP_TIMEOUT_MS,
|
|
24
|
+
EDGE_READ_TIMEOUT_MS,
|
|
25
|
+
KV_READ_TIMEOUT_MS,
|
|
49
26
|
} from "./cf/index.js";
|
|
50
27
|
|
|
51
|
-
|
|
28
|
+
export {
|
|
29
|
+
VercelCacheStore,
|
|
30
|
+
type VercelCacheStoreOptions,
|
|
31
|
+
type VercelRuntimeCache,
|
|
32
|
+
type VercelCacheDebug,
|
|
33
|
+
type VercelCacheReadDebugEvent,
|
|
34
|
+
type VercelCacheReadOutcome,
|
|
35
|
+
VERCEL_MAX_ITEM_BYTES,
|
|
36
|
+
VERCEL_MAX_TAGS_PER_ITEM,
|
|
37
|
+
VERCEL_MAX_TAG_BYTES,
|
|
38
|
+
} from "./vercel/index.js";
|
|
39
|
+
|
|
52
40
|
export { CacheScope, createCacheScope } from "./cache-scope.js";
|
|
53
41
|
|
|
54
|
-
// Document-level cache middleware
|
|
55
42
|
export {
|
|
56
43
|
createDocumentCacheMiddleware,
|
|
57
44
|
type DocumentCacheOptions,
|
|
58
45
|
} from "./document-cache.js";
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
createShellCacheMiddleware,
|
|
49
|
+
type ShellCacheOptions,
|
|
50
|
+
} from "./shell-cache.js";
|
|
51
|
+
|
|
52
|
+
export type { CacheErrorCategory } from "./cache-error.js";
|