@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -12,23 +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";
|
|
16
21
|
import { mayNeedSSR } from "../rsc/ssr-setup.js";
|
|
17
|
-
import {
|
|
22
|
+
import { cacheKeyBase } from "./cache-key-utils.js";
|
|
18
23
|
import { runBackground } from "./background-task.js";
|
|
24
|
+
import { reportCacheError } from "./cache-error.js";
|
|
19
25
|
|
|
20
|
-
// ============================================================================
|
|
21
|
-
// Constants
|
|
22
|
-
// ============================================================================
|
|
23
|
-
|
|
24
|
-
/** Header indicating cache status for debugging */
|
|
25
26
|
const CACHE_STATUS_HEADER = "x-document-cache-status";
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
+
|
|
32
35
|
function hashSegmentIds(segmentIds: string): string {
|
|
33
36
|
if (!segmentIds) return "";
|
|
34
37
|
|
|
@@ -37,12 +40,9 @@ function hashSegmentIds(segmentIds: string): string {
|
|
|
37
40
|
const char = segmentIds.charCodeAt(i);
|
|
38
41
|
hash = ((hash << 5) - hash + char) | 0;
|
|
39
42
|
}
|
|
40
|
-
// Convert to base36 for shorter string, take absolute value
|
|
41
43
|
return Math.abs(hash).toString(36);
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
// ============================================================================
|
|
45
|
-
// Cache Control Parsing
|
|
46
46
|
// ============================================================================
|
|
47
47
|
|
|
48
48
|
interface CacheDirectives {
|
|
@@ -56,6 +56,27 @@ interface CacheDirectives {
|
|
|
56
56
|
function parseCacheControl(header: string | null): CacheDirectives | null {
|
|
57
57
|
if (!header) return null;
|
|
58
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
|
+
|
|
59
80
|
const directives: CacheDirectives = {};
|
|
60
81
|
|
|
61
82
|
// Parse s-maxage
|
|
@@ -87,6 +108,16 @@ function shouldCacheResponse(response: Response): CacheDirectives | null {
|
|
|
87
108
|
return null;
|
|
88
109
|
}
|
|
89
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
|
+
|
|
90
121
|
const cacheControl = response.headers.get("Cache-Control");
|
|
91
122
|
return parseCacheControl(cacheControl);
|
|
92
123
|
}
|
|
@@ -96,20 +127,31 @@ function shouldCacheResponse(response: Response): CacheDirectives | null {
|
|
|
96
127
|
// ============================================================================
|
|
97
128
|
|
|
98
129
|
/**
|
|
99
|
-
* Add cache
|
|
130
|
+
* Add the cache-status header (HIT/STALE/MISS) to a response.
|
|
131
|
+
*
|
|
132
|
+
* The response we get here is always a fresh instance — the store rebuilds a
|
|
133
|
+
* new Response per getResponse(), and the miss path wraps a fresh Response
|
|
134
|
+
* around the tee'd body — so mutating its headers in place is safe and avoids
|
|
135
|
+
* cloning every header + allocating a new Response on every cache hit. Only when
|
|
136
|
+
* the headers are immutable (a guarded Response rejects set() with a TypeError)
|
|
137
|
+
* do we fall back to rebuilding the Response with a mutable Headers.
|
|
100
138
|
*/
|
|
101
139
|
function addCacheStatusHeader(
|
|
102
140
|
response: Response,
|
|
103
141
|
status: "HIT" | "STALE" | "MISS",
|
|
104
142
|
): Response {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
143
|
+
try {
|
|
144
|
+
response.headers.set(CACHE_STATUS_HEADER, status);
|
|
145
|
+
return response;
|
|
146
|
+
} catch {
|
|
147
|
+
const headers = new Headers(response.headers);
|
|
148
|
+
headers.set(CACHE_STATUS_HEADER, status);
|
|
149
|
+
return new Response(response.body, {
|
|
150
|
+
status: response.status,
|
|
151
|
+
statusText: response.statusText,
|
|
152
|
+
headers,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
113
155
|
}
|
|
114
156
|
|
|
115
157
|
/**
|
|
@@ -146,7 +188,13 @@ export interface DocumentCacheOptions<TEnv = any> {
|
|
|
146
188
|
skipPaths?: string[];
|
|
147
189
|
|
|
148
190
|
/**
|
|
149
|
-
* Custom cache key generator
|
|
191
|
+
* Custom cache key generator.
|
|
192
|
+
*
|
|
193
|
+
* Replaces the default `host + pathname + search` key entirely. On a
|
|
194
|
+
* multi-domain deployment served by one function you MUST include `url.host`
|
|
195
|
+
* (or an equivalent tenant discriminator) yourself — the default key is
|
|
196
|
+
* host-namespaced, but a custom generator's output is used verbatim, so
|
|
197
|
+
* omitting host bleeds one hostname's cached response to another.
|
|
150
198
|
*/
|
|
151
199
|
keyGenerator?: (url: URL) => string;
|
|
152
200
|
|
|
@@ -269,17 +317,17 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
269
317
|
isPartial && clientSegments ? `:${hashSegmentIds(clientSegments)}` : "";
|
|
270
318
|
const typeSuffix = isRscRequest ? ":rsc" : ":html";
|
|
271
319
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
279
|
-
|
|
320
|
+
// Default key rides the shared host-namespaced base (cacheKeyBase) so the
|
|
321
|
+
// segment tier (cache-scope.ts) and this document tier cannot drift on the
|
|
322
|
+
// host-namespacing rule -- see the contract on cacheKeyBase.
|
|
323
|
+
// The keyGenerator branch is left untouched: a consumer-supplied generator
|
|
324
|
+
// owns its own namespacing (auto-prefixing host would silently change their
|
|
325
|
+
// existing keys and double any host they already include).
|
|
280
326
|
const cacheKey = keyGenerator
|
|
281
327
|
? keyGenerator(url) + segmentHash + typeSuffix
|
|
282
|
-
:
|
|
328
|
+
: cacheKeyBase(url.host, url.pathname, url.searchParams) +
|
|
329
|
+
segmentHash +
|
|
330
|
+
typeSuffix;
|
|
283
331
|
// 1. Check cache
|
|
284
332
|
const cached = await store.getResponse(cacheKey);
|
|
285
333
|
|
|
@@ -300,20 +348,40 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
300
348
|
|
|
301
349
|
runBackground(requestCtx, async () => {
|
|
302
350
|
try {
|
|
303
|
-
|
|
351
|
+
// Re-establish the request-context ALS around the background
|
|
352
|
+
// re-render: next() re-runs the full handler pipeline, and on
|
|
353
|
+
// workerd a waitUntil task runs detached from the request's I/O
|
|
354
|
+
// context, so a handler/component reading getRequestContext() would
|
|
355
|
+
// otherwise throw. Same fix as the route-level/use-cache background
|
|
356
|
+
// revalidation paths.
|
|
357
|
+
const fresh = await runWithRequestContext(requestCtx, () => next());
|
|
304
358
|
const directives = shouldCacheResponse(fresh);
|
|
305
359
|
|
|
306
|
-
if (directives) {
|
|
360
|
+
if (directives && fresh.body) {
|
|
361
|
+
// Background revalidation: nothing streams to a client, so drain
|
|
362
|
+
// the fresh render fully before snapshotting tags (same
|
|
363
|
+
// render-complete barrier as the miss path).
|
|
364
|
+
const body = await new Response(fresh.body).arrayBuffer();
|
|
307
365
|
await store.putResponse!(
|
|
308
366
|
cacheKey,
|
|
309
|
-
fresh,
|
|
367
|
+
new Response(body, fresh),
|
|
310
368
|
directives.sMaxAge!,
|
|
311
369
|
directives.staleWhileRevalidate,
|
|
370
|
+
collectRequestTags(requestCtx),
|
|
312
371
|
);
|
|
313
372
|
log(`[DocumentCache] REVALIDATED ${typeLabel}: ${url.pathname}`);
|
|
314
373
|
}
|
|
315
374
|
} catch (error) {
|
|
316
|
-
|
|
375
|
+
// Pass requestCtx explicitly: this runs in a detached waitUntil task
|
|
376
|
+
// where the ALS context is gone, so onError only fires if we hand it
|
|
377
|
+
// the captured context (reportCacheError falls back to _getRequestContext
|
|
378
|
+
// otherwise, which is null here).
|
|
379
|
+
reportCacheError(
|
|
380
|
+
error,
|
|
381
|
+
"cache-write",
|
|
382
|
+
"[DocumentCache] revalidation",
|
|
383
|
+
requestCtx,
|
|
384
|
+
);
|
|
317
385
|
}
|
|
318
386
|
});
|
|
319
387
|
|
|
@@ -346,14 +414,31 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
346
414
|
// Clone response for caching (non-blocking)
|
|
347
415
|
runBackground(requestCtx, async () => {
|
|
348
416
|
try {
|
|
417
|
+
// Drain the cache copy fully BEFORE snapshotting tags. Tags from
|
|
418
|
+
// Suspense-streamed "use cache"/cacheTag and loaders are recorded as
|
|
419
|
+
// each value resolves during the RSC/HTML render, which completes
|
|
420
|
+
// only when the stream ends - the handler-settlement barrier is too
|
|
421
|
+
// early. Buffering the body (the client streams the other tee branch,
|
|
422
|
+
// unaffected) is the render-complete barrier that keeps the cached
|
|
423
|
+
// body and its tag set consistent.
|
|
424
|
+
const body = await new Response(cacheStream).arrayBuffer();
|
|
349
425
|
await store.putResponse!(
|
|
350
426
|
cacheKey,
|
|
351
|
-
new Response(
|
|
427
|
+
new Response(body, originalResponse),
|
|
352
428
|
directives.sMaxAge!,
|
|
353
429
|
directives.staleWhileRevalidate,
|
|
430
|
+
collectRequestTags(requestCtx),
|
|
354
431
|
);
|
|
355
432
|
} catch (error) {
|
|
356
|
-
|
|
433
|
+
// Detached waitUntil task — pass the captured requestCtx so onError
|
|
434
|
+
// fires even though the ALS context is gone (see the revalidation
|
|
435
|
+
// catch above).
|
|
436
|
+
reportCacheError(
|
|
437
|
+
error,
|
|
438
|
+
"cache-write",
|
|
439
|
+
"[DocumentCache] cache write",
|
|
440
|
+
requestCtx,
|
|
441
|
+
);
|
|
357
442
|
}
|
|
358
443
|
});
|
|
359
444
|
|
|
@@ -366,7 +451,7 @@ export function createDocumentCacheMiddleware<TEnv = any>(
|
|
|
366
451
|
// No cache headers - pass through
|
|
367
452
|
return originalResponse;
|
|
368
453
|
} catch (error) {
|
|
369
|
-
|
|
454
|
+
reportCacheError(error, "cache-read", "[DocumentCache] middleware");
|
|
370
455
|
if (handlerCalled) {
|
|
371
456
|
// Post-handler failure (e.g. body.tee()): do not call next() again
|
|
372
457
|
// as that would re-run handler side effects.
|
|
@@ -9,6 +9,76 @@
|
|
|
9
9
|
import type { ResolvedSegment } from "../types.js";
|
|
10
10
|
import type { HandleStore } from "../server/handle-store.js";
|
|
11
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
|
+
}
|
|
12
82
|
|
|
13
83
|
/**
|
|
14
84
|
* Capture handle data for a set of segments from the handle store.
|
package/src/cache/index.ts
CHANGED
|
@@ -1,44 +1,47 @@
|
|
|
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
|
-
// Segment cache store types and implementations
|
|
14
1
|
export type {
|
|
15
2
|
SegmentCacheStore,
|
|
16
|
-
SegmentCacheProvider,
|
|
17
3
|
CachedEntryData,
|
|
18
|
-
CachedEntryResult,
|
|
19
4
|
CacheGetResult,
|
|
5
|
+
CacheItemResult,
|
|
6
|
+
CacheItemOptions,
|
|
7
|
+
ShellCacheEntry,
|
|
20
8
|
SerializedSegmentData,
|
|
21
9
|
SegmentHandleData,
|
|
22
|
-
CacheConfig,
|
|
23
|
-
CacheConfigOrFactory,
|
|
24
10
|
} from "./types.js";
|
|
25
11
|
|
|
26
12
|
export { MemorySegmentCacheStore } from "./memory-segment-store.js";
|
|
27
13
|
|
|
28
|
-
// Cloudflare cache store
|
|
29
14
|
export {
|
|
30
15
|
CFCacheStore,
|
|
31
16
|
type CFCacheStoreOptions,
|
|
17
|
+
type CFCacheDebug,
|
|
18
|
+
type CFCacheReadDebugEvent,
|
|
32
19
|
type KVNamespace,
|
|
33
20
|
CACHE_STALE_AT_HEADER,
|
|
34
21
|
CACHE_STATUS_HEADER,
|
|
22
|
+
CACHE_REVALIDATING_AT_HEADER,
|
|
23
|
+
EDGE_LOOKUP_TIMEOUT_MS,
|
|
24
|
+
EDGE_READ_TIMEOUT_MS,
|
|
25
|
+
KV_READ_TIMEOUT_MS,
|
|
35
26
|
} from "./cf/index.js";
|
|
36
27
|
|
|
37
|
-
|
|
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
|
+
|
|
38
40
|
export { CacheScope, createCacheScope } from "./cache-scope.js";
|
|
39
41
|
|
|
40
|
-
// Document-level cache middleware
|
|
41
42
|
export {
|
|
42
43
|
createDocumentCacheMiddleware,
|
|
43
44
|
type DocumentCacheOptions,
|
|
44
45
|
} from "./document-cache.js";
|
|
46
|
+
|
|
47
|
+
export type { CacheErrorCategory } from "./cache-error.js";
|