@rangojs/router 0.0.0-experimental.dfa55db4 → 0.0.0-experimental.e16b7c00
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/README.md +120 -25
- package/dist/bin/rango.js +147 -57
- package/dist/vite/index.js +2108 -829
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +13 -8
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +188 -8
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +219 -9
- package/skills/middleware/SKILL.md +47 -12
- package/skills/migrate-nextjs/SKILL.md +582 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +71 -6
- package/skills/prerender/SKILL.md +14 -33
- package/skills/rango/SKILL.md +236 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +57 -4
- package/skills/router-setup/SKILL.md +3 -3
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +319 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/event-controller.ts +86 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +86 -11
- package/src/browser/navigation-client.ts +76 -28
- package/src/browser/navigation-store.ts +32 -9
- package/src/browser/navigation-transaction.ts +10 -28
- package/src/browser/partial-update.ts +64 -26
- package/src/browser/prefetch/cache.ts +129 -21
- package/src/browser/prefetch/fetch.ts +148 -16
- package/src/browser/prefetch/queue.ts +36 -5
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +30 -2
- package/src/browser/react/NavigationProvider.tsx +72 -31
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +17 -9
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +22 -2
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +64 -22
- package/src/browser/scroll-restoration.ts +22 -14
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +23 -30
- package/src/browser/types.ts +21 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -0
- package/src/build/index.ts +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +1 -1
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +55 -14
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +95 -1
- package/src/cache/cache-runtime.ts +79 -13
- package/src/cache/cache-scope.ts +77 -46
- package/src/cache/cache-tag.ts +135 -0
- package/src/cache/cf/cf-cache-store.ts +1067 -176
- package/src/cache/cf/index.ts +4 -1
- package/src/cache/document-cache.ts +59 -7
- package/src/cache/index.ts +6 -0
- package/src/cache/memory-segment-store.ts +158 -14
- package/src/cache/tag-invalidation.ts +206 -0
- package/src/cache/types.ts +27 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +92 -182
- package/src/context-var.ts +5 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +4 -6
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +16 -4
- package/src/index.ts +65 -15
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender.ts +4 -4
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -36
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +384 -257
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +100 -28
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-types.ts +26 -41
- package/src/router/content-negotiation.ts +15 -2
- package/src/router/error-handling.ts +1 -1
- package/src/router/handler-context.ts +21 -38
- package/src/router/intercept-resolution.ts +4 -18
- package/src/router/lazy-includes.ts +8 -8
- package/src/router/loader-resolution.ts +19 -2
- package/src/router/manifest.ts +22 -13
- package/src/router/match-api.ts +4 -3
- package/src/router/match-handlers.ts +1 -0
- package/src/router/match-middleware/cache-lookup.ts +46 -92
- package/src/router/match-middleware/cache-store.ts +3 -2
- package/src/router/match-result.ts +103 -4
- package/src/router/metrics.ts +1 -1
- package/src/router/middleware-types.ts +15 -26
- package/src/router/middleware.ts +99 -84
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +3 -1
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +4 -28
- package/src/router/revalidation.ts +58 -2
- package/src/router/router-interfaces.ts +45 -28
- package/src/router/router-options.ts +25 -1
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +32 -6
- package/src/router/segment-resolution/loader-cache.ts +8 -17
- package/src/router/segment-resolution/revalidation.ts +154 -107
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +8 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +23 -18
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +46 -74
- package/src/rsc/helpers.ts +72 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +4 -0
- package/src/rsc/response-route-handler.ts +54 -54
- package/src/rsc/rsc-rendering.ts +35 -51
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +17 -37
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/types.ts +8 -2
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +132 -116
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +143 -53
- package/src/server/cookie-store.ts +28 -4
- package/src/server/request-context.ts +46 -44
- package/src/ssr/index.tsx +5 -1
- package/src/static-handler.ts +1 -1
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +5 -1
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +68 -50
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +5 -6
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +11 -0
- package/src/types/segments.ts +36 -2
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +41 -7
- package/src/urls/path-helper.ts +17 -52
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +416 -42
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +6 -6
- package/src/vite/discovery/discover-routers.ts +101 -51
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +67 -26
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +33 -0
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +2 -0
- package/src/vite/plugin-types.ts +67 -0
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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 +214 -0
- package/src/vite/plugins/expose-action-id.ts +54 -30
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +8 -61
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +503 -468
- package/src/vite/plugins/performance-tracks.ts +29 -25
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +59 -2
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +116 -29
- package/src/vite/router-discovery.ts +750 -100
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +21 -6
- package/src/vite/utils/shared-utils.ts +107 -26
- package/dist/__internal.d.ts +0 -83
- package/dist/__internal.d.ts.map +0 -1
- package/dist/__internal.js +0 -19
- package/dist/__internal.js.map +0 -1
- package/dist/__mocks__/version.d.ts +0 -7
- package/dist/__mocks__/version.d.ts.map +0 -1
- package/dist/__mocks__/version.js +0 -7
- package/dist/__mocks__/version.js.map +0 -1
- package/dist/__tests__/client-href.test.d.ts +0 -2
- package/dist/__tests__/client-href.test.d.ts.map +0 -1
- package/dist/__tests__/client-href.test.js +0 -74
- package/dist/__tests__/client-href.test.js.map +0 -1
- package/dist/__tests__/component-utils.test.d.ts +0 -2
- package/dist/__tests__/component-utils.test.d.ts.map +0 -1
- package/dist/__tests__/component-utils.test.js +0 -51
- package/dist/__tests__/component-utils.test.js.map +0 -1
- package/dist/__tests__/event-controller.test.d.ts +0 -2
- package/dist/__tests__/event-controller.test.d.ts.map +0 -1
- package/dist/__tests__/event-controller.test.js +0 -538
- package/dist/__tests__/event-controller.test.js.map +0 -1
- package/dist/__tests__/helpers/route-tree.d.ts +0 -118
- package/dist/__tests__/helpers/route-tree.d.ts.map +0 -1
- package/dist/__tests__/helpers/route-tree.js +0 -374
- package/dist/__tests__/helpers/route-tree.js.map +0 -1
- package/dist/__tests__/match-result.test.d.ts +0 -2
- package/dist/__tests__/match-result.test.d.ts.map +0 -1
- package/dist/__tests__/match-result.test.js +0 -154
- package/dist/__tests__/match-result.test.js.map +0 -1
- package/dist/__tests__/navigation-store.test.d.ts +0 -2
- package/dist/__tests__/navigation-store.test.d.ts.map +0 -1
- package/dist/__tests__/navigation-store.test.js +0 -440
- package/dist/__tests__/navigation-store.test.js.map +0 -1
- package/dist/__tests__/partial-update.test.d.ts +0 -2
- package/dist/__tests__/partial-update.test.d.ts.map +0 -1
- package/dist/__tests__/partial-update.test.js +0 -1009
- package/dist/__tests__/partial-update.test.js.map +0 -1
- package/dist/__tests__/reverse-types.test.d.ts +0 -8
- package/dist/__tests__/reverse-types.test.d.ts.map +0 -1
- package/dist/__tests__/reverse-types.test.js +0 -656
- package/dist/__tests__/reverse-types.test.js.map +0 -1
- package/dist/__tests__/route-definition.test.d.ts +0 -2
- package/dist/__tests__/route-definition.test.d.ts.map +0 -1
- package/dist/__tests__/route-definition.test.js +0 -55
- package/dist/__tests__/route-definition.test.js.map +0 -1
- package/dist/__tests__/router-helpers.test.d.ts +0 -2
- package/dist/__tests__/router-helpers.test.d.ts.map +0 -1
- package/dist/__tests__/router-helpers.test.js +0 -377
- package/dist/__tests__/router-helpers.test.js.map +0 -1
- package/dist/__tests__/router-integration-2.test.d.ts +0 -2
- package/dist/__tests__/router-integration-2.test.d.ts.map +0 -1
- package/dist/__tests__/router-integration-2.test.js +0 -426
- package/dist/__tests__/router-integration-2.test.js.map +0 -1
- package/dist/__tests__/router-integration.test.d.ts +0 -2
- package/dist/__tests__/router-integration.test.d.ts.map +0 -1
- package/dist/__tests__/router-integration.test.js +0 -1051
- package/dist/__tests__/router-integration.test.js.map +0 -1
- package/dist/__tests__/search-params.test.d.ts +0 -5
- package/dist/__tests__/search-params.test.d.ts.map +0 -1
- package/dist/__tests__/search-params.test.js +0 -306
- package/dist/__tests__/search-params.test.js.map +0 -1
- package/dist/__tests__/segment-system.test.d.ts +0 -2
- package/dist/__tests__/segment-system.test.d.ts.map +0 -1
- package/dist/__tests__/segment-system.test.js +0 -627
- package/dist/__tests__/segment-system.test.js.map +0 -1
- package/dist/__tests__/static-handler-types.test.d.ts +0 -8
- package/dist/__tests__/static-handler-types.test.d.ts.map +0 -1
- package/dist/__tests__/static-handler-types.test.js +0 -63
- package/dist/__tests__/static-handler-types.test.js.map +0 -1
- package/dist/__tests__/urls.test.d.ts +0 -2
- package/dist/__tests__/urls.test.d.ts.map +0 -1
- package/dist/__tests__/urls.test.js +0 -421
- package/dist/__tests__/urls.test.js.map +0 -1
- package/dist/__tests__/use-mount.test.d.ts +0 -2
- package/dist/__tests__/use-mount.test.d.ts.map +0 -1
- package/dist/__tests__/use-mount.test.js +0 -35
- package/dist/__tests__/use-mount.test.js.map +0 -1
- package/dist/bin/rango.d.ts +0 -2
- package/dist/bin/rango.d.ts.map +0 -1
- package/dist/bin/rango.js.map +0 -1
- package/dist/browser/event-controller.d.ts +0 -191
- package/dist/browser/event-controller.d.ts.map +0 -1
- package/dist/browser/event-controller.js +0 -559
- package/dist/browser/event-controller.js.map +0 -1
- package/dist/browser/index.d.ts +0 -2
- package/dist/browser/index.d.ts.map +0 -1
- package/dist/browser/index.js +0 -14
- package/dist/browser/index.js.map +0 -1
- package/dist/browser/link-interceptor.d.ts +0 -38
- package/dist/browser/link-interceptor.d.ts.map +0 -1
- package/dist/browser/link-interceptor.js +0 -99
- package/dist/browser/link-interceptor.js.map +0 -1
- package/dist/browser/logging.d.ts +0 -10
- package/dist/browser/logging.d.ts.map +0 -1
- package/dist/browser/logging.js +0 -29
- package/dist/browser/logging.js.map +0 -1
- package/dist/browser/lru-cache.d.ts +0 -17
- package/dist/browser/lru-cache.d.ts.map +0 -1
- package/dist/browser/lru-cache.js +0 -50
- package/dist/browser/lru-cache.js.map +0 -1
- package/dist/browser/merge-segment-loaders.d.ts +0 -39
- package/dist/browser/merge-segment-loaders.d.ts.map +0 -1
- package/dist/browser/merge-segment-loaders.js +0 -102
- package/dist/browser/merge-segment-loaders.js.map +0 -1
- package/dist/browser/navigation-bridge.d.ts +0 -102
- package/dist/browser/navigation-bridge.d.ts.map +0 -1
- package/dist/browser/navigation-bridge.js +0 -708
- package/dist/browser/navigation-bridge.js.map +0 -1
- package/dist/browser/navigation-client.d.ts +0 -25
- package/dist/browser/navigation-client.d.ts.map +0 -1
- package/dist/browser/navigation-client.js +0 -157
- package/dist/browser/navigation-client.js.map +0 -1
- package/dist/browser/navigation-store.d.ts +0 -101
- package/dist/browser/navigation-store.d.ts.map +0 -1
- package/dist/browser/navigation-store.js +0 -625
- package/dist/browser/navigation-store.js.map +0 -1
- package/dist/browser/partial-update.d.ts +0 -75
- package/dist/browser/partial-update.d.ts.map +0 -1
- package/dist/browser/partial-update.js +0 -426
- package/dist/browser/partial-update.js.map +0 -1
- package/dist/browser/react/Link.d.ts +0 -86
- package/dist/browser/react/Link.d.ts.map +0 -1
- package/dist/browser/react/Link.js +0 -128
- package/dist/browser/react/Link.js.map +0 -1
- package/dist/browser/react/NavigationProvider.d.ts +0 -63
- package/dist/browser/react/NavigationProvider.d.ts.map +0 -1
- package/dist/browser/react/NavigationProvider.js +0 -216
- package/dist/browser/react/NavigationProvider.js.map +0 -1
- package/dist/browser/react/ScrollRestoration.d.ts +0 -75
- package/dist/browser/react/ScrollRestoration.d.ts.map +0 -1
- package/dist/browser/react/ScrollRestoration.js +0 -57
- package/dist/browser/react/ScrollRestoration.js.map +0 -1
- package/dist/browser/react/context.d.ts +0 -46
- package/dist/browser/react/context.d.ts.map +0 -1
- package/dist/browser/react/context.js +0 -10
- package/dist/browser/react/context.js.map +0 -1
- package/dist/browser/react/index.d.ts +0 -11
- package/dist/browser/react/index.d.ts.map +0 -1
- package/dist/browser/react/index.js +0 -22
- package/dist/browser/react/index.js.map +0 -1
- package/dist/browser/react/location-state-shared.d.ts +0 -63
- package/dist/browser/react/location-state-shared.d.ts.map +0 -1
- package/dist/browser/react/location-state-shared.js +0 -81
- package/dist/browser/react/location-state-shared.js.map +0 -1
- package/dist/browser/react/location-state.d.ts +0 -23
- package/dist/browser/react/location-state.d.ts.map +0 -1
- package/dist/browser/react/location-state.js +0 -29
- package/dist/browser/react/location-state.js.map +0 -1
- package/dist/browser/react/mount-context.d.ts +0 -24
- package/dist/browser/react/mount-context.d.ts.map +0 -1
- package/dist/browser/react/mount-context.js +0 -24
- package/dist/browser/react/mount-context.js.map +0 -1
- package/dist/browser/react/use-action.d.ts +0 -64
- package/dist/browser/react/use-action.d.ts.map +0 -1
- package/dist/browser/react/use-action.js +0 -134
- package/dist/browser/react/use-action.js.map +0 -1
- package/dist/browser/react/use-client-cache.d.ts +0 -41
- package/dist/browser/react/use-client-cache.d.ts.map +0 -1
- package/dist/browser/react/use-client-cache.js +0 -39
- package/dist/browser/react/use-client-cache.js.map +0 -1
- package/dist/browser/react/use-handle.d.ts +0 -31
- package/dist/browser/react/use-handle.d.ts.map +0 -1
- package/dist/browser/react/use-handle.js +0 -144
- package/dist/browser/react/use-handle.js.map +0 -1
- package/dist/browser/react/use-href.d.ts +0 -33
- package/dist/browser/react/use-href.d.ts.map +0 -1
- package/dist/browser/react/use-href.js +0 -39
- package/dist/browser/react/use-href.js.map +0 -1
- package/dist/browser/react/use-link-status.d.ts +0 -37
- package/dist/browser/react/use-link-status.d.ts.map +0 -1
- package/dist/browser/react/use-link-status.js +0 -99
- package/dist/browser/react/use-link-status.js.map +0 -1
- package/dist/browser/react/use-mount.d.ts +0 -25
- package/dist/browser/react/use-mount.d.ts.map +0 -1
- package/dist/browser/react/use-mount.js +0 -30
- package/dist/browser/react/use-mount.js.map +0 -1
- package/dist/browser/react/use-navigation.d.ts +0 -27
- package/dist/browser/react/use-navigation.d.ts.map +0 -1
- package/dist/browser/react/use-navigation.js +0 -87
- package/dist/browser/react/use-navigation.js.map +0 -1
- package/dist/browser/react/use-segments.d.ts +0 -38
- package/dist/browser/react/use-segments.d.ts.map +0 -1
- package/dist/browser/react/use-segments.js +0 -130
- package/dist/browser/react/use-segments.js.map +0 -1
- package/dist/browser/request-controller.d.ts +0 -26
- package/dist/browser/request-controller.d.ts.map +0 -1
- package/dist/browser/request-controller.js +0 -147
- package/dist/browser/request-controller.js.map +0 -1
- package/dist/browser/rsc-router.d.ts +0 -129
- package/dist/browser/rsc-router.d.ts.map +0 -1
- package/dist/browser/rsc-router.js +0 -195
- package/dist/browser/rsc-router.js.map +0 -1
- package/dist/browser/scroll-restoration.d.ts +0 -93
- package/dist/browser/scroll-restoration.d.ts.map +0 -1
- package/dist/browser/scroll-restoration.js +0 -321
- package/dist/browser/scroll-restoration.js.map +0 -1
- package/dist/browser/segment-structure-assert.d.ts +0 -17
- package/dist/browser/segment-structure-assert.d.ts.map +0 -1
- package/dist/browser/segment-structure-assert.js +0 -59
- package/dist/browser/segment-structure-assert.js.map +0 -1
- package/dist/browser/server-action-bridge.d.ts +0 -26
- package/dist/browser/server-action-bridge.d.ts.map +0 -1
- package/dist/browser/server-action-bridge.js +0 -668
- package/dist/browser/server-action-bridge.js.map +0 -1
- package/dist/browser/shallow.d.ts +0 -12
- package/dist/browser/shallow.d.ts.map +0 -1
- package/dist/browser/shallow.js +0 -34
- package/dist/browser/shallow.js.map +0 -1
- package/dist/browser/types.d.ts +0 -369
- package/dist/browser/types.d.ts.map +0 -1
- package/dist/browser/types.js +0 -2
- package/dist/browser/types.js.map +0 -1
- package/dist/build/__tests__/generate-cli.test.d.ts +0 -2
- package/dist/build/__tests__/generate-cli.test.d.ts.map +0 -1
- package/dist/build/__tests__/generate-cli.test.js +0 -237
- package/dist/build/__tests__/generate-cli.test.js.map +0 -1
- package/dist/build/__tests__/generate-manifest.test.d.ts +0 -2
- package/dist/build/__tests__/generate-manifest.test.d.ts.map +0 -1
- package/dist/build/__tests__/generate-manifest.test.js +0 -119
- package/dist/build/__tests__/generate-manifest.test.js.map +0 -1
- package/dist/build/__tests__/generate-route-types.test.d.ts +0 -2
- package/dist/build/__tests__/generate-route-types.test.d.ts.map +0 -1
- package/dist/build/__tests__/generate-route-types.test.js +0 -620
- package/dist/build/__tests__/generate-route-types.test.js.map +0 -1
- package/dist/build/__tests__/per-router-manifest.test.d.ts +0 -2
- package/dist/build/__tests__/per-router-manifest.test.d.ts.map +0 -1
- package/dist/build/__tests__/per-router-manifest.test.js +0 -308
- package/dist/build/__tests__/per-router-manifest.test.js.map +0 -1
- package/dist/build/generate-manifest.d.ts +0 -81
- package/dist/build/generate-manifest.d.ts.map +0 -1
- package/dist/build/generate-manifest.js +0 -276
- package/dist/build/generate-manifest.js.map +0 -1
- package/dist/build/generate-route-types.d.ts +0 -115
- package/dist/build/generate-route-types.d.ts.map +0 -1
- package/dist/build/generate-route-types.js +0 -740
- package/dist/build/generate-route-types.js.map +0 -1
- package/dist/build/index.d.ts +0 -21
- package/dist/build/index.d.ts.map +0 -1
- package/dist/build/index.js +0 -21
- package/dist/build/index.js.map +0 -1
- package/dist/build/route-trie.d.ts +0 -71
- package/dist/build/route-trie.d.ts.map +0 -1
- package/dist/build/route-trie.js +0 -175
- package/dist/build/route-trie.js.map +0 -1
- package/dist/cache/__tests__/cache-scope.test.d.ts +0 -2
- package/dist/cache/__tests__/cache-scope.test.d.ts.map +0 -1
- package/dist/cache/__tests__/cache-scope.test.js +0 -208
- package/dist/cache/__tests__/cache-scope.test.js.map +0 -1
- package/dist/cache/__tests__/document-cache.test.d.ts +0 -2
- package/dist/cache/__tests__/document-cache.test.d.ts.map +0 -1
- package/dist/cache/__tests__/document-cache.test.js +0 -345
- package/dist/cache/__tests__/document-cache.test.js.map +0 -1
- package/dist/cache/__tests__/memory-segment-store.test.d.ts +0 -2
- package/dist/cache/__tests__/memory-segment-store.test.d.ts.map +0 -1
- package/dist/cache/__tests__/memory-segment-store.test.js +0 -425
- package/dist/cache/__tests__/memory-segment-store.test.js.map +0 -1
- package/dist/cache/__tests__/memory-store.test.d.ts +0 -2
- package/dist/cache/__tests__/memory-store.test.d.ts.map +0 -1
- package/dist/cache/__tests__/memory-store.test.js +0 -367
- package/dist/cache/__tests__/memory-store.test.js.map +0 -1
- package/dist/cache/cache-scope.d.ts +0 -102
- package/dist/cache/cache-scope.d.ts.map +0 -1
- package/dist/cache/cache-scope.js +0 -440
- package/dist/cache/cache-scope.js.map +0 -1
- package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts +0 -2
- package/dist/cache/cf/__tests__/cf-cache-store.test.d.ts.map +0 -1
- package/dist/cache/cf/__tests__/cf-cache-store.test.js +0 -330
- package/dist/cache/cf/__tests__/cf-cache-store.test.js.map +0 -1
- package/dist/cache/cf/cf-cache-store.d.ts +0 -165
- package/dist/cache/cf/cf-cache-store.d.ts.map +0 -1
- package/dist/cache/cf/cf-cache-store.js +0 -242
- package/dist/cache/cf/cf-cache-store.js.map +0 -1
- package/dist/cache/cf/index.d.ts +0 -14
- package/dist/cache/cf/index.d.ts.map +0 -1
- package/dist/cache/cf/index.js +0 -17
- package/dist/cache/cf/index.js.map +0 -1
- package/dist/cache/document-cache.d.ts +0 -64
- package/dist/cache/document-cache.d.ts.map +0 -1
- package/dist/cache/document-cache.js +0 -228
- package/dist/cache/document-cache.js.map +0 -1
- package/dist/cache/index.d.ts +0 -19
- package/dist/cache/index.d.ts.map +0 -1
- package/dist/cache/index.js +0 -21
- package/dist/cache/index.js.map +0 -1
- package/dist/cache/memory-segment-store.d.ts +0 -110
- package/dist/cache/memory-segment-store.d.ts.map +0 -1
- package/dist/cache/memory-segment-store.js +0 -117
- package/dist/cache/memory-segment-store.js.map +0 -1
- package/dist/cache/memory-store.d.ts +0 -41
- package/dist/cache/memory-store.d.ts.map +0 -1
- package/dist/cache/memory-store.js +0 -191
- package/dist/cache/memory-store.js.map +0 -1
- package/dist/cache/types.d.ts +0 -317
- package/dist/cache/types.d.ts.map +0 -1
- package/dist/cache/types.js +0 -12
- package/dist/cache/types.js.map +0 -1
- package/dist/client.d.ts +0 -248
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -367
- package/dist/client.js.map +0 -1
- package/dist/client.rsc.d.ts +0 -26
- package/dist/client.rsc.d.ts.map +0 -1
- package/dist/client.rsc.js +0 -46
- package/dist/client.rsc.js.map +0 -1
- package/dist/component-utils.d.ts +0 -36
- package/dist/component-utils.d.ts.map +0 -1
- package/dist/component-utils.js +0 -61
- package/dist/component-utils.js.map +0 -1
- package/dist/components/DefaultDocument.d.ts +0 -13
- package/dist/components/DefaultDocument.d.ts.map +0 -1
- package/dist/components/DefaultDocument.js +0 -15
- package/dist/components/DefaultDocument.js.map +0 -1
- package/dist/debug.d.ts +0 -58
- package/dist/debug.d.ts.map +0 -1
- package/dist/debug.js +0 -157
- package/dist/debug.js.map +0 -1
- package/dist/default-error-boundary.d.ts +0 -11
- package/dist/default-error-boundary.d.ts.map +0 -1
- package/dist/default-error-boundary.js +0 -45
- package/dist/default-error-boundary.js.map +0 -1
- package/dist/deps/browser.d.ts +0 -2
- package/dist/deps/browser.d.ts.map +0 -1
- package/dist/deps/browser.js +0 -3
- package/dist/deps/browser.js.map +0 -1
- package/dist/deps/html-stream-client.d.ts +0 -2
- package/dist/deps/html-stream-client.d.ts.map +0 -1
- package/dist/deps/html-stream-client.js +0 -3
- package/dist/deps/html-stream-client.js.map +0 -1
- package/dist/deps/html-stream-server.d.ts +0 -2
- package/dist/deps/html-stream-server.d.ts.map +0 -1
- package/dist/deps/html-stream-server.js +0 -3
- package/dist/deps/html-stream-server.js.map +0 -1
- package/dist/deps/rsc.d.ts +0 -2
- package/dist/deps/rsc.d.ts.map +0 -1
- package/dist/deps/rsc.js +0 -4
- package/dist/deps/rsc.js.map +0 -1
- package/dist/deps/ssr.d.ts +0 -2
- package/dist/deps/ssr.d.ts.map +0 -1
- package/dist/deps/ssr.js +0 -3
- package/dist/deps/ssr.js.map +0 -1
- package/dist/errors.d.ts +0 -174
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -241
- package/dist/errors.js.map +0 -1
- package/dist/handle.d.ts +0 -78
- package/dist/handle.d.ts.map +0 -1
- package/dist/handle.js +0 -82
- package/dist/handle.js.map +0 -1
- package/dist/handles/MetaTags.d.ts +0 -14
- package/dist/handles/MetaTags.d.ts.map +0 -1
- package/dist/handles/MetaTags.js +0 -136
- package/dist/handles/MetaTags.js.map +0 -1
- package/dist/handles/index.d.ts +0 -6
- package/dist/handles/index.d.ts.map +0 -1
- package/dist/handles/index.js +0 -6
- package/dist/handles/index.js.map +0 -1
- package/dist/handles/meta.d.ts +0 -39
- package/dist/handles/meta.d.ts.map +0 -1
- package/dist/handles/meta.js +0 -202
- package/dist/handles/meta.js.map +0 -1
- package/dist/host/__tests__/errors.test.d.ts +0 -2
- package/dist/host/__tests__/errors.test.d.ts.map +0 -1
- package/dist/host/__tests__/errors.test.js +0 -76
- package/dist/host/__tests__/errors.test.js.map +0 -1
- package/dist/host/__tests__/pattern-comprehensive.test.d.ts +0 -2
- package/dist/host/__tests__/pattern-comprehensive.test.d.ts.map +0 -1
- package/dist/host/__tests__/pattern-comprehensive.test.js +0 -732
- package/dist/host/__tests__/pattern-comprehensive.test.js.map +0 -1
- package/dist/host/__tests__/pattern-matcher.test.d.ts +0 -2
- package/dist/host/__tests__/pattern-matcher.test.d.ts.map +0 -1
- package/dist/host/__tests__/pattern-matcher.test.js +0 -251
- package/dist/host/__tests__/pattern-matcher.test.js.map +0 -1
- package/dist/host/__tests__/router.test.d.ts +0 -2
- package/dist/host/__tests__/router.test.d.ts.map +0 -1
- package/dist/host/__tests__/router.test.js +0 -241
- package/dist/host/__tests__/router.test.js.map +0 -1
- package/dist/host/__tests__/testing.test.d.ts +0 -2
- package/dist/host/__tests__/testing.test.d.ts.map +0 -1
- package/dist/host/__tests__/testing.test.js +0 -64
- package/dist/host/__tests__/testing.test.js.map +0 -1
- package/dist/host/__tests__/utils.test.d.ts +0 -2
- package/dist/host/__tests__/utils.test.d.ts.map +0 -1
- package/dist/host/__tests__/utils.test.js +0 -29
- package/dist/host/__tests__/utils.test.js.map +0 -1
- package/dist/host/cookie-handler.d.ts +0 -34
- package/dist/host/cookie-handler.d.ts.map +0 -1
- package/dist/host/cookie-handler.js +0 -124
- package/dist/host/cookie-handler.js.map +0 -1
- package/dist/host/errors.d.ts +0 -56
- package/dist/host/errors.d.ts.map +0 -1
- package/dist/host/errors.js +0 -79
- package/dist/host/errors.js.map +0 -1
- package/dist/host/index.d.ts +0 -29
- package/dist/host/index.d.ts.map +0 -1
- package/dist/host/index.js +0 -32
- package/dist/host/index.js.map +0 -1
- package/dist/host/pattern-matcher.d.ts +0 -36
- package/dist/host/pattern-matcher.d.ts.map +0 -1
- package/dist/host/pattern-matcher.js +0 -172
- package/dist/host/pattern-matcher.js.map +0 -1
- package/dist/host/router.d.ts +0 -26
- package/dist/host/router.d.ts.map +0 -1
- package/dist/host/router.js +0 -218
- package/dist/host/router.js.map +0 -1
- package/dist/host/testing.d.ts +0 -36
- package/dist/host/testing.d.ts.map +0 -1
- package/dist/host/testing.js +0 -55
- package/dist/host/testing.js.map +0 -1
- package/dist/host/types.d.ts +0 -115
- package/dist/host/types.d.ts.map +0 -1
- package/dist/host/types.js +0 -7
- package/dist/host/types.js.map +0 -1
- package/dist/host/utils.d.ts +0 -21
- package/dist/host/utils.d.ts.map +0 -1
- package/dist/host/utils.js +0 -23
- package/dist/host/utils.js.map +0 -1
- package/dist/href-client.d.ts +0 -131
- package/dist/href-client.d.ts.map +0 -1
- package/dist/href-client.js +0 -64
- package/dist/href-client.js.map +0 -1
- package/dist/href-context.d.ts +0 -29
- package/dist/href-context.d.ts.map +0 -1
- package/dist/href-context.js +0 -21
- package/dist/href-context.js.map +0 -1
- package/dist/index.d.ts +0 -73
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -91
- package/dist/index.js.map +0 -1
- package/dist/index.rsc.d.ts +0 -32
- package/dist/index.rsc.d.ts.map +0 -1
- package/dist/index.rsc.js +0 -40
- package/dist/index.rsc.js.map +0 -1
- package/dist/internal-debug.d.ts +0 -2
- package/dist/internal-debug.d.ts.map +0 -1
- package/dist/internal-debug.js +0 -5
- package/dist/internal-debug.js.map +0 -1
- package/dist/loader.d.ts +0 -14
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -20
- package/dist/loader.js.map +0 -1
- package/dist/loader.rsc.d.ts +0 -19
- package/dist/loader.rsc.d.ts.map +0 -1
- package/dist/loader.rsc.js +0 -99
- package/dist/loader.rsc.js.map +0 -1
- package/dist/network-error-thrower.d.ts +0 -17
- package/dist/network-error-thrower.d.ts.map +0 -1
- package/dist/network-error-thrower.js +0 -14
- package/dist/network-error-thrower.js.map +0 -1
- package/dist/outlet-context.d.ts +0 -13
- package/dist/outlet-context.d.ts.map +0 -1
- package/dist/outlet-context.js +0 -3
- package/dist/outlet-context.js.map +0 -1
- package/dist/prerender/__tests__/param-hash.test.d.ts +0 -2
- package/dist/prerender/__tests__/param-hash.test.d.ts.map +0 -1
- package/dist/prerender/__tests__/param-hash.test.js +0 -148
- package/dist/prerender/__tests__/param-hash.test.js.map +0 -1
- package/dist/prerender/param-hash.d.ts +0 -16
- package/dist/prerender/param-hash.d.ts.map +0 -1
- package/dist/prerender/param-hash.js +0 -36
- package/dist/prerender/param-hash.js.map +0 -1
- package/dist/prerender/store.d.ts +0 -38
- package/dist/prerender/store.d.ts.map +0 -1
- package/dist/prerender/store.js +0 -61
- package/dist/prerender/store.js.map +0 -1
- package/dist/prerender.d.ts +0 -66
- package/dist/prerender.d.ts.map +0 -1
- package/dist/prerender.js +0 -57
- package/dist/prerender.js.map +0 -1
- package/dist/reverse.d.ts +0 -196
- package/dist/reverse.d.ts.map +0 -1
- package/dist/reverse.js +0 -78
- package/dist/reverse.js.map +0 -1
- package/dist/root-error-boundary.d.ts +0 -33
- package/dist/root-error-boundary.d.ts.map +0 -1
- package/dist/root-error-boundary.js +0 -165
- package/dist/root-error-boundary.js.map +0 -1
- package/dist/route-content-wrapper.d.ts +0 -46
- package/dist/route-content-wrapper.d.ts.map +0 -1
- package/dist/route-content-wrapper.js +0 -77
- package/dist/route-content-wrapper.js.map +0 -1
- package/dist/route-definition.d.ts +0 -421
- package/dist/route-definition.d.ts.map +0 -1
- package/dist/route-definition.js +0 -868
- package/dist/route-definition.js.map +0 -1
- package/dist/route-map-builder.d.ts +0 -155
- package/dist/route-map-builder.d.ts.map +0 -1
- package/dist/route-map-builder.js +0 -237
- package/dist/route-map-builder.js.map +0 -1
- package/dist/route-types.d.ts +0 -165
- package/dist/route-types.d.ts.map +0 -1
- package/dist/route-types.js +0 -7
- package/dist/route-types.js.map +0 -1
- package/dist/router/__tests__/handler-context.test.d.ts +0 -2
- package/dist/router/__tests__/handler-context.test.d.ts.map +0 -1
- package/dist/router/__tests__/handler-context.test.js +0 -65
- package/dist/router/__tests__/handler-context.test.js.map +0 -1
- package/dist/router/__tests__/loader-cycle-detection.test.d.ts +0 -2
- package/dist/router/__tests__/loader-cycle-detection.test.d.ts.map +0 -1
- package/dist/router/__tests__/loader-cycle-detection.test.js +0 -221
- package/dist/router/__tests__/loader-cycle-detection.test.js.map +0 -1
- package/dist/router/__tests__/match-context.test.d.ts +0 -2
- package/dist/router/__tests__/match-context.test.d.ts.map +0 -1
- package/dist/router/__tests__/match-context.test.js +0 -92
- package/dist/router/__tests__/match-context.test.js.map +0 -1
- package/dist/router/__tests__/match-pipelines.test.d.ts +0 -2
- package/dist/router/__tests__/match-pipelines.test.d.ts.map +0 -1
- package/dist/router/__tests__/match-pipelines.test.js +0 -417
- package/dist/router/__tests__/match-pipelines.test.js.map +0 -1
- package/dist/router/__tests__/match-result.test.d.ts +0 -2
- package/dist/router/__tests__/match-result.test.d.ts.map +0 -1
- package/dist/router/__tests__/match-result.test.js +0 -457
- package/dist/router/__tests__/match-result.test.js.map +0 -1
- package/dist/router/__tests__/on-error.test.d.ts +0 -2
- package/dist/router/__tests__/on-error.test.d.ts.map +0 -1
- package/dist/router/__tests__/on-error.test.js +0 -678
- package/dist/router/__tests__/on-error.test.js.map +0 -1
- package/dist/router/__tests__/pattern-matching.test.d.ts +0 -2
- package/dist/router/__tests__/pattern-matching.test.d.ts.map +0 -1
- package/dist/router/__tests__/pattern-matching.test.js +0 -629
- package/dist/router/__tests__/pattern-matching.test.js.map +0 -1
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts +0 -2
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.d.ts.map +0 -1
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.js +0 -155
- package/dist/router/__tests__/segment-resolution-parallel-loading.test.js.map +0 -1
- package/dist/router/error-handling.d.ts +0 -77
- package/dist/router/error-handling.d.ts.map +0 -1
- package/dist/router/error-handling.js +0 -202
- package/dist/router/error-handling.js.map +0 -1
- package/dist/router/handler-context.d.ts +0 -20
- package/dist/router/handler-context.d.ts.map +0 -1
- package/dist/router/handler-context.js +0 -198
- package/dist/router/handler-context.js.map +0 -1
- package/dist/router/intercept-resolution.d.ts +0 -66
- package/dist/router/intercept-resolution.d.ts.map +0 -1
- package/dist/router/intercept-resolution.js +0 -246
- package/dist/router/intercept-resolution.js.map +0 -1
- package/dist/router/loader-resolution.d.ts +0 -64
- package/dist/router/loader-resolution.d.ts.map +0 -1
- package/dist/router/loader-resolution.js +0 -284
- package/dist/router/loader-resolution.js.map +0 -1
- package/dist/router/logging.d.ts +0 -15
- package/dist/router/logging.d.ts.map +0 -1
- package/dist/router/logging.js +0 -99
- package/dist/router/logging.js.map +0 -1
- package/dist/router/manifest.d.ts +0 -22
- package/dist/router/manifest.d.ts.map +0 -1
- package/dist/router/manifest.js +0 -181
- package/dist/router/manifest.js.map +0 -1
- package/dist/router/match-api.d.ts +0 -35
- package/dist/router/match-api.d.ts.map +0 -1
- package/dist/router/match-api.js +0 -406
- package/dist/router/match-api.js.map +0 -1
- package/dist/router/match-context.d.ts +0 -206
- package/dist/router/match-context.d.ts.map +0 -1
- package/dist/router/match-context.js +0 -17
- package/dist/router/match-context.js.map +0 -1
- package/dist/router/match-middleware/background-revalidation.d.ts +0 -127
- package/dist/router/match-middleware/background-revalidation.d.ts.map +0 -1
- package/dist/router/match-middleware/background-revalidation.js +0 -75
- package/dist/router/match-middleware/background-revalidation.js.map +0 -1
- package/dist/router/match-middleware/cache-lookup.d.ts +0 -112
- package/dist/router/match-middleware/cache-lookup.d.ts.map +0 -1
- package/dist/router/match-middleware/cache-lookup.js +0 -257
- package/dist/router/match-middleware/cache-lookup.js.map +0 -1
- package/dist/router/match-middleware/cache-store.d.ts +0 -113
- package/dist/router/match-middleware/cache-store.d.ts.map +0 -1
- package/dist/router/match-middleware/cache-store.js +0 -108
- package/dist/router/match-middleware/cache-store.js.map +0 -1
- package/dist/router/match-middleware/index.d.ts +0 -81
- package/dist/router/match-middleware/index.d.ts.map +0 -1
- package/dist/router/match-middleware/index.js +0 -80
- package/dist/router/match-middleware/index.js.map +0 -1
- package/dist/router/match-middleware/intercept-resolution.d.ts +0 -117
- package/dist/router/match-middleware/intercept-resolution.d.ts.map +0 -1
- package/dist/router/match-middleware/intercept-resolution.js +0 -134
- package/dist/router/match-middleware/intercept-resolution.js.map +0 -1
- package/dist/router/match-middleware/segment-resolution.d.ts +0 -99
- package/dist/router/match-middleware/segment-resolution.d.ts.map +0 -1
- package/dist/router/match-middleware/segment-resolution.js +0 -53
- package/dist/router/match-middleware/segment-resolution.js.map +0 -1
- package/dist/router/match-pipelines.d.ts +0 -147
- package/dist/router/match-pipelines.d.ts.map +0 -1
- package/dist/router/match-pipelines.js +0 -82
- package/dist/router/match-pipelines.js.map +0 -1
- package/dist/router/match-result.d.ts +0 -126
- package/dist/router/match-result.d.ts.map +0 -1
- package/dist/router/match-result.js +0 -93
- package/dist/router/match-result.js.map +0 -1
- package/dist/router/metrics.d.ts +0 -20
- package/dist/router/metrics.d.ts.map +0 -1
- package/dist/router/metrics.js +0 -47
- package/dist/router/metrics.js.map +0 -1
- package/dist/router/middleware.d.ts +0 -249
- package/dist/router/middleware.d.ts.map +0 -1
- package/dist/router/middleware.js +0 -434
- package/dist/router/middleware.js.map +0 -1
- package/dist/router/middleware.test.d.ts +0 -2
- package/dist/router/middleware.test.d.ts.map +0 -1
- package/dist/router/middleware.test.js +0 -816
- package/dist/router/middleware.test.js.map +0 -1
- package/dist/router/pattern-matching.d.ts +0 -149
- package/dist/router/pattern-matching.d.ts.map +0 -1
- package/dist/router/pattern-matching.js +0 -349
- package/dist/router/pattern-matching.js.map +0 -1
- package/dist/router/revalidation.d.ts +0 -44
- package/dist/router/revalidation.d.ts.map +0 -1
- package/dist/router/revalidation.js +0 -147
- package/dist/router/revalidation.js.map +0 -1
- package/dist/router/router-context.d.ts +0 -135
- package/dist/router/router-context.d.ts.map +0 -1
- package/dist/router/router-context.js +0 -36
- package/dist/router/router-context.js.map +0 -1
- package/dist/router/segment-resolution.d.ts +0 -127
- package/dist/router/segment-resolution.d.ts.map +0 -1
- package/dist/router/segment-resolution.js +0 -919
- package/dist/router/segment-resolution.js.map +0 -1
- package/dist/router/trie-matching.d.ts +0 -40
- package/dist/router/trie-matching.d.ts.map +0 -1
- package/dist/router/trie-matching.js +0 -127
- package/dist/router/trie-matching.js.map +0 -1
- package/dist/router/types.d.ts +0 -136
- package/dist/router/types.d.ts.map +0 -1
- package/dist/router/types.js +0 -7
- package/dist/router/types.js.map +0 -1
- package/dist/router.d.ts +0 -753
- package/dist/router.d.ts.map +0 -1
- package/dist/router.gen.d.ts +0 -6
- package/dist/router.gen.d.ts.map +0 -1
- package/dist/router.gen.js +0 -6
- package/dist/router.gen.js.map +0 -1
- package/dist/router.js +0 -1304
- package/dist/router.js.map +0 -1
- package/dist/rsc/__tests__/helpers.test.d.ts +0 -2
- package/dist/rsc/__tests__/helpers.test.d.ts.map +0 -1
- package/dist/rsc/__tests__/helpers.test.js +0 -140
- package/dist/rsc/__tests__/helpers.test.js.map +0 -1
- package/dist/rsc/handler.d.ts +0 -45
- package/dist/rsc/handler.d.ts.map +0 -1
- package/dist/rsc/handler.js +0 -1172
- package/dist/rsc/handler.js.map +0 -1
- package/dist/rsc/helpers.d.ts +0 -16
- package/dist/rsc/helpers.d.ts.map +0 -1
- package/dist/rsc/helpers.js +0 -55
- package/dist/rsc/helpers.js.map +0 -1
- package/dist/rsc/index.d.ts +0 -22
- package/dist/rsc/index.d.ts.map +0 -1
- package/dist/rsc/index.js +0 -23
- package/dist/rsc/index.js.map +0 -1
- package/dist/rsc/nonce.d.ts +0 -9
- package/dist/rsc/nonce.d.ts.map +0 -1
- package/dist/rsc/nonce.js +0 -18
- package/dist/rsc/nonce.js.map +0 -1
- package/dist/rsc/types.d.ts +0 -206
- package/dist/rsc/types.d.ts.map +0 -1
- package/dist/rsc/types.js +0 -8
- package/dist/rsc/types.js.map +0 -1
- package/dist/search-params.d.ts +0 -103
- package/dist/search-params.d.ts.map +0 -1
- package/dist/search-params.js +0 -74
- package/dist/search-params.js.map +0 -1
- package/dist/segment-system.d.ts +0 -75
- package/dist/segment-system.d.ts.map +0 -1
- package/dist/segment-system.js +0 -336
- package/dist/segment-system.js.map +0 -1
- package/dist/server/context.d.ts +0 -245
- package/dist/server/context.d.ts.map +0 -1
- package/dist/server/context.js +0 -197
- package/dist/server/context.js.map +0 -1
- package/dist/server/fetchable-loader-store.d.ts +0 -18
- package/dist/server/fetchable-loader-store.d.ts.map +0 -1
- package/dist/server/fetchable-loader-store.js +0 -18
- package/dist/server/fetchable-loader-store.js.map +0 -1
- package/dist/server/handle-store.d.ts +0 -85
- package/dist/server/handle-store.d.ts.map +0 -1
- package/dist/server/handle-store.js +0 -142
- package/dist/server/handle-store.js.map +0 -1
- package/dist/server/loader-registry.d.ts +0 -55
- package/dist/server/loader-registry.d.ts.map +0 -1
- package/dist/server/loader-registry.js +0 -132
- package/dist/server/loader-registry.js.map +0 -1
- package/dist/server/request-context.d.ts +0 -226
- package/dist/server/request-context.d.ts.map +0 -1
- package/dist/server/request-context.js +0 -290
- package/dist/server/request-context.js.map +0 -1
- package/dist/server/root-layout.d.ts +0 -4
- package/dist/server/root-layout.d.ts.map +0 -1
- package/dist/server/root-layout.js +0 -5
- package/dist/server/root-layout.js.map +0 -1
- package/dist/server.d.ts +0 -15
- package/dist/server.d.ts.map +0 -1
- package/dist/server.js +0 -20
- package/dist/server.js.map +0 -1
- package/dist/ssr/__tests__/ssr-handler.test.d.ts +0 -2
- package/dist/ssr/__tests__/ssr-handler.test.d.ts.map +0 -1
- package/dist/ssr/__tests__/ssr-handler.test.js +0 -132
- package/dist/ssr/__tests__/ssr-handler.test.js.map +0 -1
- package/dist/ssr/index.d.ts +0 -98
- package/dist/ssr/index.d.ts.map +0 -1
- package/dist/ssr/index.js +0 -158
- package/dist/ssr/index.js.map +0 -1
- package/dist/static-handler.d.ts +0 -50
- package/dist/static-handler.d.ts.map +0 -1
- package/dist/static-handler.gen.d.ts +0 -5
- package/dist/static-handler.gen.d.ts.map +0 -1
- package/dist/static-handler.gen.js +0 -5
- package/dist/static-handler.gen.js.map +0 -1
- package/dist/static-handler.js +0 -29
- package/dist/static-handler.js.map +0 -1
- package/dist/theme/ThemeProvider.d.ts +0 -20
- package/dist/theme/ThemeProvider.d.ts.map +0 -1
- package/dist/theme/ThemeProvider.js +0 -240
- package/dist/theme/ThemeProvider.js.map +0 -1
- package/dist/theme/ThemeScript.d.ts +0 -48
- package/dist/theme/ThemeScript.d.ts.map +0 -1
- package/dist/theme/ThemeScript.js +0 -13
- package/dist/theme/ThemeScript.js.map +0 -1
- package/dist/theme/__tests__/theme.test.d.ts +0 -2
- package/dist/theme/__tests__/theme.test.d.ts.map +0 -1
- package/dist/theme/__tests__/theme.test.js +0 -103
- package/dist/theme/__tests__/theme.test.js.map +0 -1
- package/dist/theme/constants.d.ts +0 -29
- package/dist/theme/constants.d.ts.map +0 -1
- package/dist/theme/constants.js +0 -48
- package/dist/theme/constants.js.map +0 -1
- package/dist/theme/index.d.ts +0 -31
- package/dist/theme/index.d.ts.map +0 -1
- package/dist/theme/index.js +0 -36
- package/dist/theme/index.js.map +0 -1
- package/dist/theme/theme-context.d.ts +0 -40
- package/dist/theme/theme-context.d.ts.map +0 -1
- package/dist/theme/theme-context.js +0 -60
- package/dist/theme/theme-context.js.map +0 -1
- package/dist/theme/theme-script.d.ts +0 -27
- package/dist/theme/theme-script.d.ts.map +0 -1
- package/dist/theme/theme-script.js +0 -147
- package/dist/theme/theme-script.js.map +0 -1
- package/dist/theme/types.d.ts +0 -163
- package/dist/theme/types.d.ts.map +0 -1
- package/dist/theme/types.js +0 -11
- package/dist/theme/types.js.map +0 -1
- package/dist/theme/use-theme.d.ts +0 -12
- package/dist/theme/use-theme.d.ts.map +0 -1
- package/dist/theme/use-theme.js +0 -40
- package/dist/theme/use-theme.js.map +0 -1
- package/dist/types.d.ts +0 -1479
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -10
- package/dist/types.js.map +0 -1
- package/dist/urls.d.ts +0 -441
- package/dist/urls.d.ts.map +0 -1
- package/dist/urls.gen.d.ts +0 -8
- package/dist/urls.gen.d.ts.map +0 -1
- package/dist/urls.gen.js +0 -8
- package/dist/urls.gen.js.map +0 -1
- package/dist/urls.js +0 -443
- package/dist/urls.js.map +0 -1
- package/dist/use-loader.d.ts +0 -127
- package/dist/use-loader.d.ts.map +0 -1
- package/dist/use-loader.js +0 -237
- package/dist/use-loader.js.map +0 -1
- package/dist/vite/__tests__/ast-handler-extract.test.d.ts +0 -2
- package/dist/vite/__tests__/ast-handler-extract.test.d.ts.map +0 -1
- package/dist/vite/__tests__/ast-handler-extract.test.js +0 -294
- package/dist/vite/__tests__/ast-handler-extract.test.js.map +0 -1
- package/dist/vite/__tests__/expose-id-utils.test.d.ts +0 -2
- package/dist/vite/__tests__/expose-id-utils.test.d.ts.map +0 -1
- package/dist/vite/__tests__/expose-id-utils.test.js +0 -224
- package/dist/vite/__tests__/expose-id-utils.test.js.map +0 -1
- package/dist/vite/__tests__/expose-internal-ids.test.d.ts +0 -2
- package/dist/vite/__tests__/expose-internal-ids.test.d.ts.map +0 -1
- package/dist/vite/__tests__/expose-internal-ids.test.js +0 -647
- package/dist/vite/__tests__/expose-internal-ids.test.js.map +0 -1
- package/dist/vite/__tests__/expose-router-id.test.d.ts +0 -2
- package/dist/vite/__tests__/expose-router-id.test.d.ts.map +0 -1
- package/dist/vite/__tests__/expose-router-id.test.js +0 -39
- package/dist/vite/__tests__/expose-router-id.test.js.map +0 -1
- package/dist/vite/ast-handler-extract.d.ts +0 -49
- package/dist/vite/ast-handler-extract.d.ts.map +0 -1
- package/dist/vite/ast-handler-extract.js +0 -249
- package/dist/vite/ast-handler-extract.js.map +0 -1
- package/dist/vite/expose-action-id.d.ts +0 -19
- package/dist/vite/expose-action-id.d.ts.map +0 -1
- package/dist/vite/expose-action-id.js +0 -250
- package/dist/vite/expose-action-id.js.map +0 -1
- package/dist/vite/expose-id-utils.d.ts +0 -69
- package/dist/vite/expose-id-utils.d.ts.map +0 -1
- package/dist/vite/expose-id-utils.js +0 -289
- package/dist/vite/expose-id-utils.js.map +0 -1
- package/dist/vite/expose-internal-ids.d.ts +0 -22
- package/dist/vite/expose-internal-ids.d.ts.map +0 -1
- package/dist/vite/expose-internal-ids.js +0 -886
- package/dist/vite/expose-internal-ids.js.map +0 -1
- package/dist/vite/index.d.ts +0 -149
- package/dist/vite/index.d.ts.map +0 -1
- package/dist/vite/index.js.bak +0 -5448
- package/dist/vite/index.js.map +0 -1
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/dist/vite/package-resolution.d.ts +0 -43
- package/dist/vite/package-resolution.d.ts.map +0 -1
- package/dist/vite/package-resolution.js +0 -112
- package/dist/vite/package-resolution.js.map +0 -1
- package/dist/vite/virtual-entries.d.ts +0 -25
- package/dist/vite/virtual-entries.d.ts.map +0 -1
- package/dist/vite/virtual-entries.js +0 -110
- package/dist/vite/virtual-entries.js.map +0 -1
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
resolveSwrWindow,
|
|
46
46
|
DEFAULT_FUNCTION_TTL,
|
|
47
47
|
} from "../cache-policy.js";
|
|
48
|
+
import { reportCacheError, reportingAsync } from "../cache-error.js";
|
|
49
|
+
import type { CacheErrorCategory } from "../cache-error.js";
|
|
48
50
|
|
|
49
51
|
// ============================================================================
|
|
50
52
|
// Constants
|
|
@@ -56,6 +58,49 @@ export const CACHE_STALE_AT_HEADER = "x-edge-cache-stale-at";
|
|
|
56
58
|
/** Header storing cache status: HIT | REVALIDATING */
|
|
57
59
|
export const CACHE_STATUS_HEADER = "x-edge-cache-status";
|
|
58
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Header storing this entry's cache tags as a JSON array. JSON-encoded (not the
|
|
63
|
+
* comma-delimited CF `Cache-Tag` format) so tags containing commas round-trip
|
|
64
|
+
* safely; the read paths parse this to run the tag-invalidation check.
|
|
65
|
+
*/
|
|
66
|
+
export const CACHE_TAGS_HEADER = "x-edge-cache-tags";
|
|
67
|
+
|
|
68
|
+
/** Header storing the ms-epoch timestamp when this entry's tags were attached. */
|
|
69
|
+
export const CACHE_TAGGED_AT_HEADER = "x-edge-cache-tagged-at";
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* KV key prefix for tag-invalidation markers. A marker stores the ms-epoch
|
|
73
|
+
* timestamp of the most recent invalidation of a tag; reads treat any entry
|
|
74
|
+
* whose taggedAt is older than its tags' latest marker as invalidated. Markers
|
|
75
|
+
* live in the SAME KV namespace as the cached entries - there is no separate
|
|
76
|
+
* tag-invalidation store.
|
|
77
|
+
*/
|
|
78
|
+
const TAG_MARKER_PREFIX = "__tag__/";
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Cache-API path prefix for the optional per-colo L1 cache of tag-invalidation
|
|
82
|
+
* markers (enabled by tagCacheTtl). Distinct from data keys (doc:/fn:/segment)
|
|
83
|
+
* and from the KV marker prefix so the two never collide.
|
|
84
|
+
*/
|
|
85
|
+
const TAG_MARKER_CACHE_PREFIX = "__tagmarker__/";
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Sentinel body for an L1-cached marker meaning "this tag has no invalidation
|
|
89
|
+
* marker." Distinct from any real ms-epoch timestamp (always a large positive
|
|
90
|
+
* integer). A Cache API miss (match() === undefined) always means "re-read KV",
|
|
91
|
+
* never "no marker" - absence is only ever represented by this cached sentinel.
|
|
92
|
+
*/
|
|
93
|
+
const TAG_MARKER_ABSENT = "none";
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Header stashing the route author's original Cache-Control on L1 document
|
|
97
|
+
* entries. putResponse/promoteResponseToL1 overwrite Cache-Control with a long
|
|
98
|
+
* `max-age` so the CF Cache API retains the entry across the whole SWR window;
|
|
99
|
+
* getResponse restores this original value before serving so the client and any
|
|
100
|
+
* upstream CDN see the author's intended directive, not the internal edge TTL.
|
|
101
|
+
*/
|
|
102
|
+
const CACHE_ORIG_CC_HEADER = "x-edge-cache-orig-cc";
|
|
103
|
+
|
|
59
104
|
/**
|
|
60
105
|
* Maximum age in seconds for REVALIDATING status before allowing new revalidation.
|
|
61
106
|
* After this period, a stale entry in REVALIDATING status will trigger revalidation again.
|
|
@@ -63,18 +108,84 @@ export const CACHE_STATUS_HEADER = "x-edge-cache-status";
|
|
|
63
108
|
*/
|
|
64
109
|
export const MAX_REVALIDATION_INTERVAL = 30;
|
|
65
110
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Per-request memo of tag-invalidation markers (tag -> latest invalidatedAt, or
|
|
113
|
+
* null when no marker exists). Keyed by the request context object so it is
|
|
114
|
+
* naturally request-scoped and garbage-collected with the request.
|
|
115
|
+
*
|
|
116
|
+
* Without it, isGloballyInvalidated() issues a KV read per tag on every tagged
|
|
117
|
+
* cache read, so a page composed of many segments/items sharing a tag pays that
|
|
118
|
+
* cost N times. The memo collapses it to one KV read per distinct tag per
|
|
119
|
+
* request. invalidateTags() writes through so a same-request updateTag() stays
|
|
120
|
+
* read-your-own-writes consistent (the action's own re-render sees its own
|
|
121
|
+
* invalidation from the memo, without a re-read).
|
|
122
|
+
*
|
|
123
|
+
* It does NOT span requests, so a hot single-entry route still pays one KV read
|
|
124
|
+
* per request; that read hits Cloudflare KV's own edge read cache for hot keys.
|
|
125
|
+
*/
|
|
126
|
+
const tagMarkerMemo = new WeakMap<object, Map<string, number | null>>();
|
|
127
|
+
|
|
128
|
+
function getTagMarkerMemo(ctx: object): Map<string, number | null> {
|
|
129
|
+
let memo = tagMarkerMemo.get(ctx);
|
|
130
|
+
if (!memo) {
|
|
131
|
+
memo = new Map();
|
|
132
|
+
tagMarkerMemo.set(ctx, memo);
|
|
133
|
+
}
|
|
134
|
+
return memo;
|
|
135
|
+
}
|
|
69
136
|
|
|
70
137
|
/**
|
|
71
|
-
*
|
|
138
|
+
* Per-request map of IN-FLIGHT marker reads (tag -> the pending read promise).
|
|
139
|
+
* The resolved-value memo above only collapses SEQUENTIAL reads of a tag; the
|
|
140
|
+
* router resolves sibling segments in PARALLEL, so without this several
|
|
141
|
+
* concurrently-resolving segments sharing a tag would each issue their own KV
|
|
142
|
+
* read before any of them populates the memo. Sharing the in-flight promise
|
|
143
|
+
* collapses those to a single KV read. Entries are dropped once resolved (the
|
|
144
|
+
* value is then in the memo), so this only spans the concurrent read window.
|
|
72
145
|
*/
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
const tagMarkerInflight = new WeakMap<
|
|
147
|
+
object,
|
|
148
|
+
Map<string, Promise<number | null>>
|
|
149
|
+
>();
|
|
150
|
+
|
|
151
|
+
function getTagMarkerInflight(
|
|
152
|
+
ctx: object,
|
|
153
|
+
): Map<string, Promise<number | null>> {
|
|
154
|
+
let inflight = tagMarkerInflight.get(ctx);
|
|
155
|
+
if (!inflight) {
|
|
156
|
+
inflight = new Map();
|
|
157
|
+
tagMarkerInflight.set(ctx, inflight);
|
|
158
|
+
}
|
|
159
|
+
return inflight;
|
|
76
160
|
}
|
|
77
161
|
|
|
162
|
+
/** KV key byte-length ceiling. Cloudflare KV rejects keys larger than this. */
|
|
163
|
+
const KV_MAX_KEY_BYTES = 512;
|
|
164
|
+
|
|
165
|
+
const kvKeyEncoder = new TextEncoder();
|
|
166
|
+
|
|
167
|
+
/** UTF-8 byte length of a KV key (multibyte tags can exceed the char count). */
|
|
168
|
+
function kvKeyByteLength(key: string): number {
|
|
169
|
+
return kvKeyEncoder.encode(key).length;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Stores (by namespace) already warned about tag machinery configured without a
|
|
174
|
+
* KV namespace, so the warning fires once per process rather than per request
|
|
175
|
+
* (CFCacheStore is constructed per request).
|
|
176
|
+
*/
|
|
177
|
+
const warnedNoKvReadInvalidation = new Set<string>();
|
|
178
|
+
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// Types
|
|
181
|
+
// ============================================================================
|
|
182
|
+
|
|
183
|
+
// Re-exported from the canonical home so cf-cache-store consumers keep
|
|
184
|
+
// importing `ExecutionContext` from this module without a second interface
|
|
185
|
+
// drifting over time.
|
|
186
|
+
export type { ExecutionContext } from "../../types/request-scope.js";
|
|
187
|
+
import type { ExecutionContext } from "../../types/request-scope.js";
|
|
188
|
+
|
|
78
189
|
/**
|
|
79
190
|
* Minimal Cloudflare KV Namespace interface.
|
|
80
191
|
* Avoids hard dependency on @cloudflare/workers-types.
|
|
@@ -115,6 +226,10 @@ interface KVItemEnvelope {
|
|
|
115
226
|
s: number;
|
|
116
227
|
/** When entry hard-expires (ms epoch) */
|
|
117
228
|
e: number;
|
|
229
|
+
/** Cache tags (for distributed tag invalidation) */
|
|
230
|
+
t?: string[];
|
|
231
|
+
/** Timestamp when tags were attached (ms epoch) */
|
|
232
|
+
ta?: number;
|
|
118
233
|
}
|
|
119
234
|
|
|
120
235
|
/**
|
|
@@ -128,12 +243,16 @@ interface KVResponseEnvelope {
|
|
|
128
243
|
st: number;
|
|
129
244
|
/** HTTP status text */
|
|
130
245
|
stx: string;
|
|
131
|
-
/** Serialized headers as key-value pairs */
|
|
246
|
+
/** Serialized headers as key-value pairs (client-facing; no internal headers) */
|
|
132
247
|
hd: [string, string][];
|
|
133
248
|
/** When entry becomes stale (ms epoch) */
|
|
134
249
|
s: number;
|
|
135
250
|
/** When entry hard-expires (ms epoch) */
|
|
136
251
|
e: number;
|
|
252
|
+
/** Cache tags (for distributed tag invalidation) */
|
|
253
|
+
t?: string[];
|
|
254
|
+
/** Timestamp when tags were attached (ms epoch) */
|
|
255
|
+
ta?: number;
|
|
137
256
|
}
|
|
138
257
|
|
|
139
258
|
export interface CFCacheStoreOptions<TEnv = unknown> {
|
|
@@ -177,14 +296,89 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
|
|
|
177
296
|
* ```typescript
|
|
178
297
|
* new CFCacheStore({ ctx: env.ctx, kv: env.CACHE_KV })
|
|
179
298
|
* ```
|
|
299
|
+
*
|
|
300
|
+
* Tag-based invalidation (updateTag/revalidateTag) requires KV: the
|
|
301
|
+
* tag-invalidation markers are stored in this same namespace. There is no
|
|
302
|
+
* separate tag-invalidation store to configure.
|
|
180
303
|
*/
|
|
181
304
|
kv?: KVNamespace;
|
|
182
305
|
|
|
306
|
+
/**
|
|
307
|
+
* Optional eager-purge hook, called ONCE per updateTag()/revalidateTag() with
|
|
308
|
+
* the namespaced Cloudflare Cache-Tags to purge (one batched call for the
|
|
309
|
+
* whole invalidation, not one per tag). These exactly match the `Cache-Tag`
|
|
310
|
+
* header this store writes on its tag-lookup marker entries
|
|
311
|
+
* (`rg:{namespace}:lk:{encodedTag}`), so forwarding them to Cloudflare's
|
|
312
|
+
* purge-by-tag API evicts the cached lookups in every colo - making
|
|
313
|
+
* cross-colo invalidation prompt instead of waiting out `tagCacheTtl`.
|
|
314
|
+
*
|
|
315
|
+
* Only meaningful with `tagCacheTtl > 0` (otherwise there are no cached
|
|
316
|
+
* lookups to purge). The values are pre-encoded, so commas in tag names are
|
|
317
|
+
* safe to pass straight to the purge API.
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* ```ts
|
|
321
|
+
* onRevalidateTag: async (cacheTags) => {
|
|
322
|
+
* await fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE}/purge_cache`, {
|
|
323
|
+
* method: "POST",
|
|
324
|
+
* headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
|
|
325
|
+
* body: JSON.stringify({ tags: cacheTags }),
|
|
326
|
+
* });
|
|
327
|
+
* }
|
|
328
|
+
* ```
|
|
329
|
+
*/
|
|
330
|
+
onRevalidateTag?: (cacheTags: string[]) => Promise<void>;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Optional expiration (seconds) for tag-invalidation markers in KV. A marker
|
|
334
|
+
* must outlive every entry tagged before the invalidation, so this MUST
|
|
335
|
+
* exceed your largest entry TTL+SWR. Defaults to no expiration (markers
|
|
336
|
+
* persist; they are tiny - one timestamp per distinct invalidated tag).
|
|
337
|
+
*
|
|
338
|
+
* Note the opposite sizing from `tagCacheTtl` below: `tagInvalidationTtl` must
|
|
339
|
+
* be LARGE (outlive data); `tagCacheTtl` should be SMALL (a staleness ceiling).
|
|
340
|
+
*
|
|
341
|
+
* Cardinality matters: each DISTINCT invalidated tag writes one permanent KV
|
|
342
|
+
* marker (with the no-expiry default). Keep tags LOW-cardinality and never
|
|
343
|
+
* derive an invalidation tag from untrusted input (e.g.
|
|
344
|
+
* `revalidateTag(req.query.tag)`) - an attacker could otherwise grow your KV
|
|
345
|
+
* namespace without bound. Set a `tagInvalidationTtl` only if your tags are
|
|
346
|
+
* unavoidably high-cardinality AND it can still safely exceed your max entry
|
|
347
|
+
* TTL+SWR.
|
|
348
|
+
*/
|
|
349
|
+
tagInvalidationTtl?: number;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Optional TTL (seconds) for caching tag-invalidation markers in the per-colo
|
|
353
|
+
* Cache API (L1), to avoid a KV marker read on every tagged cache read.
|
|
354
|
+
*
|
|
355
|
+
* Default `0` = disabled: the marker is read from KV on every tagged read
|
|
356
|
+
* (today's behavior), giving the strongest cross-colo invalidation latency
|
|
357
|
+
* (~KV consistency). A positive value caches each marker (including the
|
|
358
|
+
* "no marker yet" state) in L1 for that many seconds, so within the window a
|
|
359
|
+
* colo answers from L1 with no KV read.
|
|
360
|
+
*
|
|
361
|
+
* The colo that runs `updateTag`/`revalidateTag` writes the fresh marker
|
|
362
|
+
* straight into its own L1 (write-through), so it observes the invalidation
|
|
363
|
+
* immediately. By default OTHER colos only converge when their cached marker
|
|
364
|
+
* expires, so `tagCacheTtl` is the MAXIMUM extra cross-colo invalidation
|
|
365
|
+
* latency for them. Recommended 30-60 for high-read, low-mutation tags; leave
|
|
366
|
+
* at 0 when prompt global invalidation matters and you cannot wire a purge.
|
|
367
|
+
*
|
|
368
|
+
* To make other colos prompt WITHOUT a short TTL, wire `onRevalidateTag` to a
|
|
369
|
+
* Cloudflare purge-by-tag call: each marker entry carries a namespaced
|
|
370
|
+
* `Cache-Tag`, and `onRevalidateTag` is handed exactly those tags to purge, so
|
|
371
|
+
* the cached lookups are evicted everywhere on invalidation. With a purge
|
|
372
|
+
* wired, `tagCacheTtl` becomes purely a read-cost reducer + fallback window
|
|
373
|
+
* (safe to set large) rather than the invalidation-latency ceiling.
|
|
374
|
+
*/
|
|
375
|
+
tagCacheTtl?: number;
|
|
376
|
+
|
|
183
377
|
/**
|
|
184
378
|
* Cache version string override. When this changes, all cached entries are
|
|
185
379
|
* effectively invalidated (new keys won't match old entries).
|
|
186
380
|
*
|
|
187
|
-
* Defaults to the auto-generated VERSION from
|
|
381
|
+
* Defaults to the auto-generated VERSION from the `@rangojs/router:version` virtual module.
|
|
188
382
|
* Only set this if you need a custom versioning strategy.
|
|
189
383
|
*/
|
|
190
384
|
version?: string;
|
|
@@ -194,6 +388,12 @@ export interface CFCacheStoreOptions<TEnv = unknown> {
|
|
|
194
388
|
* Receives the full RequestContext (including env) and the default-generated key.
|
|
195
389
|
* Return value becomes the final cache key (unless route overrides with `key` option).
|
|
196
390
|
*
|
|
391
|
+
* Reserved prefixes: tag-invalidation markers live in the SAME KV namespace as
|
|
392
|
+
* data, keyed `__tag__/<tag>` (and `__tagmarker__/<tag>` for the L1 cache). A
|
|
393
|
+
* returned key must NOT begin with `__tag__/` or `__tagmarker__/`, or it can
|
|
394
|
+
* collide with a tag marker and corrupt invalidation. The documented
|
|
395
|
+
* prepend-style generators below are safe.
|
|
396
|
+
*
|
|
197
397
|
* @example Using headers for user segmentation
|
|
198
398
|
* ```typescript
|
|
199
399
|
* keyGenerator: (ctx, defaultKey) => {
|
|
@@ -246,6 +446,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
246
446
|
private readonly waitUntil?: (fn: () => Promise<void>) => void;
|
|
247
447
|
private readonly version?: string;
|
|
248
448
|
private readonly kv?: KVNamespace;
|
|
449
|
+
private readonly onRevalidateTag?: (tags: string[]) => Promise<void>;
|
|
450
|
+
private readonly tagInvalidationTtl?: number;
|
|
451
|
+
private readonly tagCacheTtl: number;
|
|
249
452
|
|
|
250
453
|
constructor(options: CFCacheStoreOptions<TEnv>) {
|
|
251
454
|
if (!options.ctx) {
|
|
@@ -263,6 +466,28 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
263
466
|
this.keyGenerator = options.keyGenerator;
|
|
264
467
|
this.waitUntil = (fn) => options.ctx.waitUntil(fn());
|
|
265
468
|
this.kv = options.kv;
|
|
469
|
+
this.onRevalidateTag = options.onRevalidateTag;
|
|
470
|
+
this.tagInvalidationTtl = options.tagInvalidationTtl;
|
|
471
|
+
this.tagCacheTtl = options.tagCacheTtl ?? 0;
|
|
472
|
+
|
|
473
|
+
// Read-side tag invalidation requires KV: isGloballyInvalidated() compares an
|
|
474
|
+
// entry's taggedAt against the per-tag KV marker and short-circuits to "not
|
|
475
|
+
// invalidated" when no KV namespace is configured. A consumer who wires the
|
|
476
|
+
// tag machinery (tagCacheTtl for L1 markers, or onRevalidateTag for CDN purge)
|
|
477
|
+
// but omits kv gets markers written and the purge fired, yet every tagged read
|
|
478
|
+
// still serves stale data with no other signal. Surface that misconfiguration.
|
|
479
|
+
if (!this.kv && (this.tagCacheTtl > 0 || this.onRevalidateTag)) {
|
|
480
|
+
const id = this.namespace ?? "default";
|
|
481
|
+
if (!warnedNoKvReadInvalidation.has(id)) {
|
|
482
|
+
warnedNoKvReadInvalidation.add(id);
|
|
483
|
+
console.warn(
|
|
484
|
+
`[CFCacheStore] tagCacheTtl/onRevalidateTag is configured without a KV ` +
|
|
485
|
+
`namespace, so tag invalidation has NO read-side effect: tagged reads ` +
|
|
486
|
+
`are never treated as invalidated and serve stale data. Configure ` +
|
|
487
|
+
`{ kv } for distributed tag invalidation.`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
266
491
|
}
|
|
267
492
|
|
|
268
493
|
/**
|
|
@@ -320,6 +545,39 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
320
545
|
// Segment Cache Methods
|
|
321
546
|
// ============================================================================
|
|
322
547
|
|
|
548
|
+
/**
|
|
549
|
+
* Guard the segment tier against a `keyGenerator` that returns a key colliding
|
|
550
|
+
* with a reserved tag-marker namespace: `__tag__/` (the KV marker key) or
|
|
551
|
+
* `__tagmarker__/` (the L1 Cache API marker request). The item/doc tiers are
|
|
552
|
+
* internally prefixed (`fn:`/`doc:`) so only the bare segment key can collide;
|
|
553
|
+
* a collision would let a segment write clobber - or a segment read/delete
|
|
554
|
+
* evict - a live tag marker, silently breaking invalidation. Report loudly
|
|
555
|
+
* (so a misconfigured keyGenerator surfaces immediately) and treat the segment
|
|
556
|
+
* operation as a miss/no-op rather than corrupting the marker namespace.
|
|
557
|
+
* @internal
|
|
558
|
+
*/
|
|
559
|
+
private isReservedSegmentKey(
|
|
560
|
+
key: string,
|
|
561
|
+
category: CacheErrorCategory,
|
|
562
|
+
): boolean {
|
|
563
|
+
const reserved = key.startsWith(TAG_MARKER_PREFIX)
|
|
564
|
+
? TAG_MARKER_PREFIX
|
|
565
|
+
: key.startsWith(TAG_MARKER_CACHE_PREFIX)
|
|
566
|
+
? TAG_MARKER_CACHE_PREFIX
|
|
567
|
+
: null;
|
|
568
|
+
if (!reserved) return false;
|
|
569
|
+
reportCacheError(
|
|
570
|
+
new Error(
|
|
571
|
+
`segment key "${key}" collides with the reserved "${reserved}" ` +
|
|
572
|
+
`tag-marker namespace; the operation is ignored. Fix the store ` +
|
|
573
|
+
`keyGenerator so it does not produce keys with this prefix.`,
|
|
574
|
+
),
|
|
575
|
+
category,
|
|
576
|
+
"[CFCacheStore] reserved key",
|
|
577
|
+
);
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
323
581
|
/**
|
|
324
582
|
* Get cached entry data by key.
|
|
325
583
|
*
|
|
@@ -332,6 +590,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
332
590
|
* KV hits are promoted to L1 in the background.
|
|
333
591
|
*/
|
|
334
592
|
async get(key: string): Promise<CacheGetResult | null> {
|
|
593
|
+
if (this.isReservedSegmentKey(key, "cache-read")) return null;
|
|
335
594
|
try {
|
|
336
595
|
const cache = await this.getCache();
|
|
337
596
|
const request = this.keyToRequest(key);
|
|
@@ -341,6 +600,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
341
600
|
return this.kvGetSegment(key);
|
|
342
601
|
}
|
|
343
602
|
|
|
603
|
+
// Tag invalidation: an entry whose tags were invalidated after it was
|
|
604
|
+
// cached is treated as a miss, so the next render re-populates it.
|
|
605
|
+
const tagInfo = this.readTagInfo(response.headers);
|
|
606
|
+
if (await this.isGloballyInvalidated(tagInfo.tags, tagInfo.taggedAt)) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
|
|
344
610
|
// Read status headers
|
|
345
611
|
const status = response.headers.get(CACHE_STATUS_HEADER);
|
|
346
612
|
const age = Number(response.headers.get("age") ?? "0");
|
|
@@ -354,8 +620,12 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
354
620
|
|
|
355
621
|
// Case 1: Fresh or already being revalidated - just return data
|
|
356
622
|
if (!isStale || isRevalidating) {
|
|
357
|
-
const data =
|
|
358
|
-
|
|
623
|
+
const data = await this.parseOrEvict<CachedEntryData>(
|
|
624
|
+
() => response.json() as Promise<CachedEntryData>,
|
|
625
|
+
() => cache.delete(request),
|
|
626
|
+
"get",
|
|
627
|
+
);
|
|
628
|
+
return data === null ? null : { data, shouldRevalidate: false };
|
|
359
629
|
}
|
|
360
630
|
|
|
361
631
|
// Case 2: Stale and needs revalidation - atomically mark REVALIDATING
|
|
@@ -370,10 +640,14 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
370
640
|
new Response(b1, { status: response.status, headers }),
|
|
371
641
|
);
|
|
372
642
|
|
|
373
|
-
const data =
|
|
374
|
-
|
|
643
|
+
const data = await this.parseOrEvict<CachedEntryData>(
|
|
644
|
+
() => new Response(b2).json() as Promise<CachedEntryData>,
|
|
645
|
+
() => cache.delete(request),
|
|
646
|
+
"get(revalidating)",
|
|
647
|
+
);
|
|
648
|
+
return data === null ? null : { data, shouldRevalidate: true };
|
|
375
649
|
} catch (error) {
|
|
376
|
-
|
|
650
|
+
reportCacheError(error, "cache-read", "[CFCacheStore] get");
|
|
377
651
|
return null;
|
|
378
652
|
}
|
|
379
653
|
}
|
|
@@ -389,6 +663,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
389
663
|
ttl: number,
|
|
390
664
|
swr?: number,
|
|
391
665
|
): Promise<void> {
|
|
666
|
+
if (this.isReservedSegmentKey(key, "cache-write")) return;
|
|
392
667
|
try {
|
|
393
668
|
const cache = await this.getCache();
|
|
394
669
|
const request = this.keyToRequest(key);
|
|
@@ -398,32 +673,51 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
398
673
|
const totalTtl = ttl + swrWindow;
|
|
399
674
|
const staleAt = Date.now() + ttl * 1000;
|
|
400
675
|
|
|
401
|
-
|
|
676
|
+
// Stamp the tag timestamp at write time and carry it (with the tags)
|
|
677
|
+
// into both the L1 body and the KV envelope so reads can run the
|
|
678
|
+
// invalidation check.
|
|
679
|
+
const taggedAt =
|
|
680
|
+
data.tags && data.tags.length > 0 ? Date.now() : undefined;
|
|
681
|
+
const dataToStore: CachedEntryData = taggedAt
|
|
682
|
+
? { ...data, taggedAt }
|
|
683
|
+
: data;
|
|
684
|
+
|
|
685
|
+
const body = JSON.stringify(dataToStore);
|
|
402
686
|
const response = new Response(body, {
|
|
403
687
|
headers: {
|
|
404
688
|
"Content-Type": "application/json",
|
|
405
689
|
"Cache-Control": `public, max-age=${totalTtl}`,
|
|
406
690
|
[CACHE_STALE_AT_HEADER]: String(staleAt),
|
|
407
691
|
[CACHE_STATUS_HEADER]: "HIT",
|
|
692
|
+
...this.tagHeaderEntries(dataToStore.tags, taggedAt),
|
|
408
693
|
},
|
|
409
694
|
});
|
|
410
695
|
|
|
411
696
|
const putPromise = cache.put(request, response);
|
|
412
697
|
|
|
413
698
|
if (this.waitUntil) {
|
|
414
|
-
// Non-blocking write
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
699
|
+
// Non-blocking write. These store-level background tasks intentionally
|
|
700
|
+
// omit the reportingAsync ctx argument: the store is a request-agnostic
|
|
701
|
+
// singleton and this.waitUntil is the execution context's, not a single
|
|
702
|
+
// request's, so a failure is reported console-loud only (it cannot be
|
|
703
|
+
// attributed to one request's onError). The request-scoped tag verbs
|
|
704
|
+
// (revalidateTag / stale-revalidation) DO thread their captured ctx.
|
|
705
|
+
this.waitUntil(() =>
|
|
706
|
+
reportingAsync(
|
|
707
|
+
() => putPromise,
|
|
708
|
+
"cache-write",
|
|
709
|
+
"[CFCacheStore] L1 write",
|
|
710
|
+
),
|
|
711
|
+
);
|
|
418
712
|
} else {
|
|
419
713
|
// Blocking fallback
|
|
420
714
|
await putPromise;
|
|
421
715
|
}
|
|
422
716
|
|
|
423
717
|
// L2: persist to KV
|
|
424
|
-
this.kvSetSegment(key,
|
|
718
|
+
this.kvSetSegment(key, dataToStore, staleAt, totalTtl, swrWindow);
|
|
425
719
|
} catch (error) {
|
|
426
|
-
|
|
720
|
+
reportCacheError(error, "cache-write", "[CFCacheStore] set");
|
|
427
721
|
}
|
|
428
722
|
}
|
|
429
723
|
|
|
@@ -431,6 +725,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
431
725
|
* Delete a cached entry from L1 and L2.
|
|
432
726
|
*/
|
|
433
727
|
async delete(key: string): Promise<boolean> {
|
|
728
|
+
if (this.isReservedSegmentKey(key, "cache-delete")) return false;
|
|
434
729
|
try {
|
|
435
730
|
const cache = await this.getCache();
|
|
436
731
|
const result = await cache.delete(this.keyToRequest(key));
|
|
@@ -438,18 +733,18 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
438
733
|
// L2: delete from KV
|
|
439
734
|
if (this.kv && this.waitUntil) {
|
|
440
735
|
const kvKey = this.toKVKey(key);
|
|
441
|
-
this.waitUntil(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
736
|
+
this.waitUntil(() =>
|
|
737
|
+
reportingAsync(
|
|
738
|
+
() => this.kv!.delete(kvKey),
|
|
739
|
+
"cache-delete",
|
|
740
|
+
"[CFCacheStore] delete L2",
|
|
741
|
+
),
|
|
742
|
+
);
|
|
448
743
|
}
|
|
449
744
|
|
|
450
745
|
return result;
|
|
451
746
|
} catch (error) {
|
|
452
|
-
|
|
747
|
+
reportCacheError(error, "cache-delete", "[CFCacheStore] delete");
|
|
453
748
|
return false;
|
|
454
749
|
}
|
|
455
750
|
}
|
|
@@ -475,20 +770,59 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
475
770
|
return this.kvGetResponse(key);
|
|
476
771
|
}
|
|
477
772
|
|
|
773
|
+
// Tag invalidation check (treat invalidated entry as a miss).
|
|
774
|
+
const tagInfo = this.readTagInfo(response.headers);
|
|
775
|
+
if (await this.isGloballyInvalidated(tagInfo.tags, tagInfo.taggedAt)) {
|
|
776
|
+
return null;
|
|
777
|
+
}
|
|
778
|
+
|
|
478
779
|
// Check staleness
|
|
479
780
|
const staleAt = Number(response.headers.get(CACHE_STALE_AT_HEADER) || 0);
|
|
480
781
|
const isStale = staleAt > 0 && Date.now() > staleAt;
|
|
481
782
|
|
|
783
|
+
// L1 document bodies are streamed through verbatim - unlike the segment/
|
|
784
|
+
// item tiers (which JSON-parse and so structurally detect corruption) and
|
|
785
|
+
// the KV doc tier (validated in kvGetResponse, KV being the real partial-
|
|
786
|
+
// read vector). Integrity here relies on the Cache API: cache.put stores a
|
|
787
|
+
// response atomically or fails, so a truncated body is not served back. We
|
|
788
|
+
// deliberately do NOT buffer+hash the body to re-verify it: that would
|
|
789
|
+
// defeat streaming the document and add a full read to every cache hit.
|
|
482
790
|
return {
|
|
483
|
-
response,
|
|
791
|
+
response: this.toClientResponse(response),
|
|
484
792
|
shouldRevalidate: isStale,
|
|
485
793
|
};
|
|
486
794
|
} catch (error) {
|
|
487
|
-
|
|
795
|
+
reportCacheError(error, "cache-read", "[CFCacheStore] getResponse");
|
|
488
796
|
return null;
|
|
489
797
|
}
|
|
490
798
|
}
|
|
491
799
|
|
|
800
|
+
/**
|
|
801
|
+
* Strip internal edge headers and restore the author's Cache-Control before a
|
|
802
|
+
* cached document Response is served to a client. L1 entries carry the
|
|
803
|
+
* internal staleness/status headers and a rewritten Cache-Control; none of
|
|
804
|
+
* those should reach the browser or an upstream CDN.
|
|
805
|
+
*/
|
|
806
|
+
private toClientResponse(response: Response): Response {
|
|
807
|
+
const headers = new Headers(response.headers);
|
|
808
|
+
const originalCacheControl = headers.get(CACHE_ORIG_CC_HEADER);
|
|
809
|
+
if (originalCacheControl !== null) {
|
|
810
|
+
headers.set("Cache-Control", originalCacheControl);
|
|
811
|
+
} else {
|
|
812
|
+
headers.delete("Cache-Control");
|
|
813
|
+
}
|
|
814
|
+
headers.delete(CACHE_ORIG_CC_HEADER);
|
|
815
|
+
headers.delete(CACHE_STALE_AT_HEADER);
|
|
816
|
+
headers.delete(CACHE_STATUS_HEADER);
|
|
817
|
+
headers.delete(CACHE_TAGS_HEADER);
|
|
818
|
+
headers.delete(CACHE_TAGGED_AT_HEADER);
|
|
819
|
+
return new Response(response.body, {
|
|
820
|
+
status: response.status,
|
|
821
|
+
statusText: response.statusText,
|
|
822
|
+
headers,
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
|
|
492
826
|
/**
|
|
493
827
|
* Store a Response with TTL and optional SWR window (for document-level caching).
|
|
494
828
|
* When KV is configured, also persists to L2.
|
|
@@ -498,6 +832,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
498
832
|
response: Response,
|
|
499
833
|
ttl: number,
|
|
500
834
|
swr?: number,
|
|
835
|
+
tags?: string[],
|
|
501
836
|
): Promise<void> {
|
|
502
837
|
try {
|
|
503
838
|
const cache = await this.getCache();
|
|
@@ -507,6 +842,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
507
842
|
const swrWindow = resolveSwrWindow(swr, this.defaults);
|
|
508
843
|
const totalTtl = ttl + swrWindow;
|
|
509
844
|
const staleAt = Date.now() + ttl * 1000;
|
|
845
|
+
const taggedAt = tags && tags.length > 0 ? Date.now() : undefined;
|
|
510
846
|
|
|
511
847
|
// Clone body for potential KV write before consuming it for L1
|
|
512
848
|
const [l1Body, kvBody] = this.kv
|
|
@@ -515,10 +851,21 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
515
851
|
: [null, null]
|
|
516
852
|
: [response.body, null];
|
|
517
853
|
|
|
518
|
-
// Clone and add cache headers
|
|
854
|
+
// Clone and add cache headers. The author's Cache-Control is stashed and
|
|
855
|
+
// replaced with a long max-age so the CF Cache API holds the entry across
|
|
856
|
+
// the SWR window; getResponse restores the original before serving.
|
|
519
857
|
const headers = new Headers(response.headers);
|
|
858
|
+
const originalCacheControl = response.headers.get("Cache-Control");
|
|
859
|
+
if (originalCacheControl !== null) {
|
|
860
|
+
headers.set(CACHE_ORIG_CC_HEADER, originalCacheControl);
|
|
861
|
+
}
|
|
520
862
|
headers.set("Cache-Control", `public, max-age=${totalTtl}`);
|
|
521
863
|
headers.set(CACHE_STALE_AT_HEADER, String(staleAt));
|
|
864
|
+
// Internal tag headers (stripped by toClientResponse before serving).
|
|
865
|
+
const tagHeaders = this.tagHeaderEntries(tags, taggedAt);
|
|
866
|
+
for (const [name, value] of Object.entries(tagHeaders)) {
|
|
867
|
+
headers.set(name, value);
|
|
868
|
+
}
|
|
522
869
|
|
|
523
870
|
const toCache = new Response(l1Body, {
|
|
524
871
|
status: response.status,
|
|
@@ -530,9 +877,13 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
530
877
|
|
|
531
878
|
if (this.waitUntil) {
|
|
532
879
|
// Non-blocking write
|
|
533
|
-
this.waitUntil(
|
|
534
|
-
|
|
535
|
-
|
|
880
|
+
this.waitUntil(() =>
|
|
881
|
+
reportingAsync(
|
|
882
|
+
() => putPromise,
|
|
883
|
+
"cache-write",
|
|
884
|
+
"[CFCacheStore] L1 write",
|
|
885
|
+
),
|
|
886
|
+
);
|
|
536
887
|
} else {
|
|
537
888
|
// Blocking fallback
|
|
538
889
|
await putPromise;
|
|
@@ -549,26 +900,30 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
549
900
|
: new ArrayBuffer(0);
|
|
550
901
|
const bodyBase64 = bufferToBase64(bodyBuf);
|
|
551
902
|
|
|
552
|
-
this.waitUntil(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
903
|
+
this.waitUntil(() =>
|
|
904
|
+
reportingAsync(
|
|
905
|
+
() => {
|
|
906
|
+
const envelope: KVResponseEnvelope = {
|
|
907
|
+
b: bodyBase64,
|
|
908
|
+
st: response.status,
|
|
909
|
+
stx: response.statusText,
|
|
910
|
+
hd: headersArray,
|
|
911
|
+
s: staleAt,
|
|
912
|
+
e: staleAt + swrWindow * 1000,
|
|
913
|
+
t: tags,
|
|
914
|
+
ta: taggedAt,
|
|
915
|
+
};
|
|
916
|
+
return this.kv!.put(kvKey, JSON.stringify(envelope), {
|
|
917
|
+
expirationTtl: totalTtl,
|
|
918
|
+
});
|
|
919
|
+
},
|
|
920
|
+
"cache-write",
|
|
921
|
+
"[CFCacheStore] kvPutResponse",
|
|
922
|
+
),
|
|
923
|
+
);
|
|
569
924
|
}
|
|
570
925
|
} catch (error) {
|
|
571
|
-
|
|
926
|
+
reportCacheError(error, "cache-write", "[CFCacheStore] putResponse");
|
|
572
927
|
}
|
|
573
928
|
}
|
|
574
929
|
|
|
@@ -589,6 +944,12 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
589
944
|
|
|
590
945
|
if (!response) return this.kvGetItem(key);
|
|
591
946
|
|
|
947
|
+
// Tag invalidation check (treat invalidated entry as a miss).
|
|
948
|
+
const tagInfo = this.readTagInfo(response.headers);
|
|
949
|
+
if (await this.isGloballyInvalidated(tagInfo.tags, tagInfo.taggedAt)) {
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
952
|
+
|
|
592
953
|
const staleAt = Number(
|
|
593
954
|
response.headers.get(CACHE_STALE_AT_HEADER) ?? "0",
|
|
594
955
|
);
|
|
@@ -599,16 +960,26 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
599
960
|
const isRevalidating =
|
|
600
961
|
status === "REVALIDATING" && age < MAX_REVALIDATION_INTERVAL;
|
|
601
962
|
|
|
602
|
-
const data =
|
|
963
|
+
const data = await this.parseOrEvict<{
|
|
603
964
|
value: string;
|
|
604
965
|
handles?: Record<string, Record<string, unknown[]>>;
|
|
605
|
-
}
|
|
966
|
+
}>(
|
|
967
|
+
() =>
|
|
968
|
+
response.json() as Promise<{
|
|
969
|
+
value: string;
|
|
970
|
+
handles?: Record<string, Record<string, unknown[]>>;
|
|
971
|
+
}>,
|
|
972
|
+
() => cache.delete(request),
|
|
973
|
+
"getItem",
|
|
974
|
+
);
|
|
975
|
+
if (data === null) return null;
|
|
606
976
|
|
|
607
977
|
if (!isStale || isRevalidating) {
|
|
608
978
|
return {
|
|
609
979
|
value: data.value,
|
|
610
980
|
handles: data.handles,
|
|
611
981
|
shouldRevalidate: false,
|
|
982
|
+
tags: tagInfo.tags,
|
|
612
983
|
};
|
|
613
984
|
}
|
|
614
985
|
|
|
@@ -624,9 +995,10 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
624
995
|
value: data.value,
|
|
625
996
|
handles: data.handles,
|
|
626
997
|
shouldRevalidate: true,
|
|
998
|
+
tags: tagInfo.tags,
|
|
627
999
|
};
|
|
628
1000
|
} catch (error) {
|
|
629
|
-
|
|
1001
|
+
reportCacheError(error, "cache-read", "[CFCacheStore] getItem");
|
|
630
1002
|
return null;
|
|
631
1003
|
}
|
|
632
1004
|
}
|
|
@@ -649,6 +1021,9 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
649
1021
|
const totalTtl = ttl + swrWindow;
|
|
650
1022
|
const staleAt = Date.now() + ttl * 1000;
|
|
651
1023
|
|
|
1024
|
+
const tags = options?.tags;
|
|
1025
|
+
const taggedAt = tags && tags.length > 0 ? Date.now() : undefined;
|
|
1026
|
+
|
|
652
1027
|
const body = JSON.stringify({ value, handles: options?.handles });
|
|
653
1028
|
const response = new Response(body, {
|
|
654
1029
|
headers: {
|
|
@@ -656,15 +1031,20 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
656
1031
|
"Cache-Control": `public, max-age=${totalTtl}`,
|
|
657
1032
|
[CACHE_STALE_AT_HEADER]: String(staleAt),
|
|
658
1033
|
[CACHE_STATUS_HEADER]: "HIT",
|
|
1034
|
+
...this.tagHeaderEntries(tags, taggedAt),
|
|
659
1035
|
},
|
|
660
1036
|
});
|
|
661
1037
|
|
|
662
1038
|
const putPromise = cache.put(request, response);
|
|
663
1039
|
|
|
664
1040
|
if (this.waitUntil) {
|
|
665
|
-
this.waitUntil(
|
|
666
|
-
|
|
667
|
-
|
|
1041
|
+
this.waitUntil(() =>
|
|
1042
|
+
reportingAsync(
|
|
1043
|
+
() => putPromise,
|
|
1044
|
+
"cache-write",
|
|
1045
|
+
"[CFCacheStore] L1 write",
|
|
1046
|
+
),
|
|
1047
|
+
);
|
|
668
1048
|
} else {
|
|
669
1049
|
await putPromise;
|
|
670
1050
|
}
|
|
@@ -672,24 +1052,28 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
672
1052
|
// L2: persist to KV (KV requires expirationTtl >= 60s)
|
|
673
1053
|
if (this.kv && this.waitUntil && totalTtl >= 60) {
|
|
674
1054
|
const kvKey = this.toKVKey(`fn:${key}`);
|
|
675
|
-
this.waitUntil(
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
1055
|
+
this.waitUntil(() =>
|
|
1056
|
+
reportingAsync(
|
|
1057
|
+
() => {
|
|
1058
|
+
const envelope: KVItemEnvelope = {
|
|
1059
|
+
v: value,
|
|
1060
|
+
h: options?.handles,
|
|
1061
|
+
s: staleAt,
|
|
1062
|
+
e: staleAt + swrWindow * 1000,
|
|
1063
|
+
t: tags,
|
|
1064
|
+
ta: taggedAt,
|
|
1065
|
+
};
|
|
1066
|
+
return this.kv!.put(kvKey, JSON.stringify(envelope), {
|
|
1067
|
+
expirationTtl: totalTtl,
|
|
1068
|
+
});
|
|
1069
|
+
},
|
|
1070
|
+
"cache-write",
|
|
1071
|
+
"[CFCacheStore] kvSetItem",
|
|
1072
|
+
),
|
|
1073
|
+
);
|
|
690
1074
|
}
|
|
691
1075
|
} catch (error) {
|
|
692
|
-
|
|
1076
|
+
reportCacheError(error, "cache-write", "[CFCacheStore] setItem");
|
|
693
1077
|
}
|
|
694
1078
|
}
|
|
695
1079
|
|
|
@@ -721,6 +1105,426 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
721
1105
|
return `${versionPath}${key}`;
|
|
722
1106
|
}
|
|
723
1107
|
|
|
1108
|
+
/**
|
|
1109
|
+
* Parse a stored entry, EVICTING it if the parse fails. A parse failure means
|
|
1110
|
+
* the entry is corrupt or partial (truncated Cache API body, malformed KV
|
|
1111
|
+
* envelope/base64) - it would deterministically fail every future read - so it
|
|
1112
|
+
* is deleted to self-heal (the next read misses and re-renders) and reported as
|
|
1113
|
+
* cache-corrupt. This is distinct from a transient infra error (the caller's
|
|
1114
|
+
* outer catch reports cache-read and does NOT delete a still-good entry).
|
|
1115
|
+
* Returns null on corruption; the caller treats null as a miss.
|
|
1116
|
+
* @internal
|
|
1117
|
+
*/
|
|
1118
|
+
private async parseOrEvict<T>(
|
|
1119
|
+
parse: () => Promise<T> | T,
|
|
1120
|
+
evict: () => Promise<unknown>,
|
|
1121
|
+
label: string,
|
|
1122
|
+
): Promise<T | null> {
|
|
1123
|
+
try {
|
|
1124
|
+
return await parse();
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
reportCacheError(
|
|
1127
|
+
error,
|
|
1128
|
+
"cache-corrupt",
|
|
1129
|
+
`[CFCacheStore] ${label}: corrupt/partial entry, evicting`,
|
|
1130
|
+
);
|
|
1131
|
+
try {
|
|
1132
|
+
await evict();
|
|
1133
|
+
} catch (evictError) {
|
|
1134
|
+
reportCacheError(
|
|
1135
|
+
evictError,
|
|
1136
|
+
"cache-delete",
|
|
1137
|
+
`[CFCacheStore] ${label}: evicting corrupt entry failed`,
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/**
|
|
1145
|
+
* Best-effort delete of a single KV key, reporting (not swallowing) a delete
|
|
1146
|
+
* failure as cache-delete. Used by the corrupt-entry self-heal paths.
|
|
1147
|
+
* @internal
|
|
1148
|
+
*/
|
|
1149
|
+
private async evictKvKey(kvKey: string, label: string): Promise<void> {
|
|
1150
|
+
try {
|
|
1151
|
+
await this.kv!.delete(kvKey);
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
reportCacheError(
|
|
1154
|
+
error,
|
|
1155
|
+
"cache-delete",
|
|
1156
|
+
`[CFCacheStore] ${label}: evict failed`,
|
|
1157
|
+
);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* KV-get a JSON envelope, EVICTING the key only when it is genuinely corrupt.
|
|
1163
|
+
*
|
|
1164
|
+
* Reads as { type: "text" }, NOT { type: "json" }, on purpose: the "json" form
|
|
1165
|
+
* fuses the network read and the JSON parse, so a transient KV outage (5xx/429/
|
|
1166
|
+
* network blip) is indistinguishable from a malformed body and would delete a
|
|
1167
|
+
* still-good cross-colo entry - a self-inflicted miss storm. Reading text lets a
|
|
1168
|
+
* transient read error propagate to the caller's outer catch (reported
|
|
1169
|
+
* cache-read, the entry left intact); only a JSON.parse failure on a body that
|
|
1170
|
+
* WAS successfully read - or an envelope that parses but fails `validate`
|
|
1171
|
+
* (fields missing from a truncated write) - is true corruption that evicts +
|
|
1172
|
+
* reports cache-corrupt. A MISSING key (kv.get -> null) is a normal miss.
|
|
1173
|
+
* @internal
|
|
1174
|
+
*/
|
|
1175
|
+
private async kvGetOrEvict<T>(
|
|
1176
|
+
kvKey: string,
|
|
1177
|
+
validate: (envelope: T) => boolean,
|
|
1178
|
+
label: string,
|
|
1179
|
+
): Promise<T | null> {
|
|
1180
|
+
// A transient error here throws and is reported cache-read (no eviction) by
|
|
1181
|
+
// the caller's outer catch - deliberately NOT caught as corruption.
|
|
1182
|
+
const text = await this.kv!.get(kvKey, { type: "text" });
|
|
1183
|
+
if (text == null) return null; // missing key = miss, not corruption
|
|
1184
|
+
|
|
1185
|
+
let raw: T;
|
|
1186
|
+
try {
|
|
1187
|
+
raw = JSON.parse(text) as T;
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
reportCacheError(
|
|
1190
|
+
error,
|
|
1191
|
+
"cache-corrupt",
|
|
1192
|
+
`[CFCacheStore] ${label}: corrupt JSON in KV, evicting`,
|
|
1193
|
+
);
|
|
1194
|
+
await this.evictKvKey(kvKey, label);
|
|
1195
|
+
return null;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
if (!validate(raw)) {
|
|
1199
|
+
reportCacheError(
|
|
1200
|
+
new Error("malformed/partial KV envelope"),
|
|
1201
|
+
"cache-corrupt",
|
|
1202
|
+
`[CFCacheStore] ${label}: malformed envelope, evicting`,
|
|
1203
|
+
);
|
|
1204
|
+
await this.evictKvKey(kvKey, label);
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
return raw;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// ============================================================================
|
|
1211
|
+
// Tag Invalidation (single-store: markers live in this.kv)
|
|
1212
|
+
// ============================================================================
|
|
1213
|
+
|
|
1214
|
+
/** KV key for a tag's invalidation marker. */
|
|
1215
|
+
private tagMarkerKey(tag: string): string {
|
|
1216
|
+
return this.toKVKey(`${TAG_MARKER_PREFIX}${tag}`);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
/**
|
|
1220
|
+
* Header entries carrying an entry's tags (JSON-encoded, comma-safe) and the
|
|
1221
|
+
* timestamp they were attached. Returns an empty object when there are no
|
|
1222
|
+
* tags so untagged entries stay header-free and skip the invalidation check.
|
|
1223
|
+
*/
|
|
1224
|
+
private tagHeaderEntries(
|
|
1225
|
+
tags: string[] | undefined,
|
|
1226
|
+
taggedAt: number | undefined,
|
|
1227
|
+
): Record<string, string> {
|
|
1228
|
+
if (!tags || tags.length === 0 || !taggedAt) return {};
|
|
1229
|
+
return {
|
|
1230
|
+
// encodeURIComponent so the value is pure ASCII: HTTP header values are
|
|
1231
|
+
// ByteStrings, but JSON.stringify leaves codepoints > U+00FF (emoji/CJK)
|
|
1232
|
+
// verbatim, which makes new Response({ headers }) throw and the outer
|
|
1233
|
+
// try/catch silently drop the whole entry from cache. Decoded in
|
|
1234
|
+
// readTagInfo. The L1 marker Cache-Tag path encodes for the same reason.
|
|
1235
|
+
[CACHE_TAGS_HEADER]: encodeURIComponent(JSON.stringify(tags)),
|
|
1236
|
+
[CACHE_TAGGED_AT_HEADER]: String(taggedAt),
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/** Read an entry's tags/taggedAt back from its headers. */
|
|
1241
|
+
private readTagInfo(headers: Headers): {
|
|
1242
|
+
tags?: string[];
|
|
1243
|
+
taggedAt?: number;
|
|
1244
|
+
} {
|
|
1245
|
+
const rawTags = headers.get(CACHE_TAGS_HEADER);
|
|
1246
|
+
const rawTaggedAt = headers.get(CACHE_TAGGED_AT_HEADER);
|
|
1247
|
+
if (!rawTags || !rawTaggedAt) return {};
|
|
1248
|
+
try {
|
|
1249
|
+
return {
|
|
1250
|
+
tags: JSON.parse(decodeURIComponent(rawTags)) as string[],
|
|
1251
|
+
taggedAt: Number(rawTaggedAt),
|
|
1252
|
+
};
|
|
1253
|
+
} catch {
|
|
1254
|
+
return {};
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
/**
|
|
1259
|
+
* Whether an entry tagged at `taggedAt` with `tags` has been invalidated since.
|
|
1260
|
+
* Reads the per-tag invalidation markers from KV and returns true if any tag's
|
|
1261
|
+
* latest invalidation is at or after taggedAt (>= so a same-millisecond
|
|
1262
|
+
* invalidate wins, favouring freshness over staleness). Fails open: KV errors
|
|
1263
|
+
* never turn a hit into a wrongful miss-storm beyond this single read.
|
|
1264
|
+
*/
|
|
1265
|
+
private async isGloballyInvalidated(
|
|
1266
|
+
tags: string[] | undefined,
|
|
1267
|
+
taggedAt: number | undefined,
|
|
1268
|
+
): Promise<boolean> {
|
|
1269
|
+
if (!this.kv || !tags || tags.length === 0 || !taggedAt) return false;
|
|
1270
|
+
const ctx = _getRequestContext();
|
|
1271
|
+
const memo = ctx ? getTagMarkerMemo(ctx) : undefined;
|
|
1272
|
+
const inflight = ctx ? getTagMarkerInflight(ctx) : undefined;
|
|
1273
|
+
try {
|
|
1274
|
+
const markers = await Promise.all(
|
|
1275
|
+
tags.map((tag) => this.readTagMarker(tag, memo, inflight)),
|
|
1276
|
+
);
|
|
1277
|
+
for (const marker of markers) {
|
|
1278
|
+
if (marker != null && marker >= taggedAt) return true;
|
|
1279
|
+
}
|
|
1280
|
+
return false;
|
|
1281
|
+
} catch (error) {
|
|
1282
|
+
reportCacheError(
|
|
1283
|
+
error,
|
|
1284
|
+
"cache-read",
|
|
1285
|
+
"[CFCacheStore] tag invalidation check",
|
|
1286
|
+
);
|
|
1287
|
+
return false;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/** Synthetic Cache API request for a tag's L1-cached invalidation marker. */
|
|
1292
|
+
private tagMarkerRequest(tag: string): Request {
|
|
1293
|
+
return this.keyToRequest(`${TAG_MARKER_CACHE_PREFIX}${tag}`);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
/**
|
|
1297
|
+
* Read a tag's latest invalidation timestamp (or null if never invalidated)
|
|
1298
|
+
* through the cascade: per-request memo -> per-colo L1 cache (only when
|
|
1299
|
+
* tagCacheTtl > 0) -> KV (the global truth). The memo is always consulted
|
|
1300
|
+
* first so it stays authoritative within a request (read-your-own-writes),
|
|
1301
|
+
* and every KV/L1 result is written back into the memo. A Cache API miss
|
|
1302
|
+
* always falls through to KV; absence is represented by a cached sentinel,
|
|
1303
|
+
* never by a miss.
|
|
1304
|
+
*
|
|
1305
|
+
* Concurrent reads of the same tag within a request share one in-flight read
|
|
1306
|
+
* (the resolved-value memo only collapses sequential reads; parallel segment
|
|
1307
|
+
* loading would otherwise issue one KV read per concurrent reader).
|
|
1308
|
+
* @internal
|
|
1309
|
+
*/
|
|
1310
|
+
private async readTagMarker(
|
|
1311
|
+
tag: string,
|
|
1312
|
+
memo: Map<string, number | null> | undefined,
|
|
1313
|
+
inflight: Map<string, Promise<number | null>> | undefined,
|
|
1314
|
+
): Promise<number | null> {
|
|
1315
|
+
if (memo && memo.has(tag)) return memo.get(tag) ?? null;
|
|
1316
|
+
|
|
1317
|
+
// Collapse concurrent (not-yet-resolved) reads of this tag onto one promise.
|
|
1318
|
+
if (inflight) {
|
|
1319
|
+
const pending = inflight.get(tag);
|
|
1320
|
+
if (pending) return pending;
|
|
1321
|
+
const read = this.fetchTagMarker(tag, memo);
|
|
1322
|
+
inflight.set(tag, read);
|
|
1323
|
+
try {
|
|
1324
|
+
return await read;
|
|
1325
|
+
} finally {
|
|
1326
|
+
// Resolved values now live in the memo; drop the in-flight entry.
|
|
1327
|
+
inflight.delete(tag);
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
return this.fetchTagMarker(tag, memo);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Uncached body of readTagMarker: L1 (per-colo Cache API, opt-in via
|
|
1336
|
+
* tagCacheTtl) -> KV. Writes the resolved value back into the memo.
|
|
1337
|
+
* @internal
|
|
1338
|
+
*/
|
|
1339
|
+
private async fetchTagMarker(
|
|
1340
|
+
tag: string,
|
|
1341
|
+
memo: Map<string, number | null> | undefined,
|
|
1342
|
+
): Promise<number | null> {
|
|
1343
|
+
// L1 (per-colo) marker cache - opt-in via tagCacheTtl.
|
|
1344
|
+
if (this.tagCacheTtl > 0) {
|
|
1345
|
+
try {
|
|
1346
|
+
const cache = await this.getCache();
|
|
1347
|
+
const hit = await cache.match(this.tagMarkerRequest(tag));
|
|
1348
|
+
if (hit) {
|
|
1349
|
+
const body = await hit.text();
|
|
1350
|
+
const value = body === TAG_MARKER_ABSENT ? null : Number(body);
|
|
1351
|
+
memo?.set(tag, value);
|
|
1352
|
+
return value;
|
|
1353
|
+
}
|
|
1354
|
+
} catch {
|
|
1355
|
+
// Fall through to KV on any L1 read error.
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// KV (global truth).
|
|
1360
|
+
const raw = await this.kv!.get(this.tagMarkerKey(tag), { type: "text" });
|
|
1361
|
+
const value = raw != null ? Number(raw) : null;
|
|
1362
|
+
memo?.set(tag, value);
|
|
1363
|
+
|
|
1364
|
+
// Populate L1 for subsequent reads in this colo (non-blocking).
|
|
1365
|
+
if (this.tagCacheTtl > 0) {
|
|
1366
|
+
const put = () => this.putTagMarkerL1(tag, value);
|
|
1367
|
+
if (this.waitUntil) this.waitUntil(put);
|
|
1368
|
+
else void put();
|
|
1369
|
+
}
|
|
1370
|
+
return value;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* Cloudflare Cache-Tags written on a tag's L1 marker entry, namespaced per
|
|
1375
|
+
* store so purges never collide with other Cache-Tags in the zone. Three
|
|
1376
|
+
* tiers, broad to specific:
|
|
1377
|
+
* rg:{ns} - everything this store cached (deploy/nuclear reset)
|
|
1378
|
+
* rg:{ns}:lk - all tag-lookup markers
|
|
1379
|
+
* rg:{ns}:lk:{tag} - this tag's lookup (the normal updateTag purge target)
|
|
1380
|
+
* The tag value is encodeURIComponent'd so commas/spaces can't corrupt the
|
|
1381
|
+
* comma-delimited Cache-Tag header.
|
|
1382
|
+
* @internal
|
|
1383
|
+
*/
|
|
1384
|
+
private lookupCacheTags(tag: string): string[] {
|
|
1385
|
+
const ns = this.namespace ?? "default";
|
|
1386
|
+
return [`rg:${ns}`, `rg:${ns}:lk`, this.lookupPurgeTag(tag)];
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/** The specific Cache-Tag a consumer purges to evict tag `tag`'s lookup. */
|
|
1390
|
+
private lookupPurgeTag(tag: string): string {
|
|
1391
|
+
const ns = this.namespace ?? "default";
|
|
1392
|
+
return `rg:${ns}:lk:${encodeURIComponent(tag)}`;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* Write a tag marker value into the per-colo L1 Cache API with tagCacheTtl.
|
|
1397
|
+
* `null` is stored as the TAG_MARKER_ABSENT sentinel so "no marker yet" is
|
|
1398
|
+
* cacheable (most tags are never invalidated - that is where the read savings
|
|
1399
|
+
* come from). The entry also carries a namespaced Cache-Tag so an external
|
|
1400
|
+
* purge-by-tag (via onRevalidateTag) can evict it across colos promptly,
|
|
1401
|
+
* rather than waiting out tagCacheTtl. Best-effort.
|
|
1402
|
+
* @internal
|
|
1403
|
+
*/
|
|
1404
|
+
private async putTagMarkerL1(
|
|
1405
|
+
tag: string,
|
|
1406
|
+
value: number | null,
|
|
1407
|
+
): Promise<void> {
|
|
1408
|
+
if (this.tagCacheTtl <= 0) return;
|
|
1409
|
+
try {
|
|
1410
|
+
const cache = await this.getCache();
|
|
1411
|
+
const body = value != null ? String(value) : TAG_MARKER_ABSENT;
|
|
1412
|
+
await cache.put(
|
|
1413
|
+
this.tagMarkerRequest(tag),
|
|
1414
|
+
new Response(body, {
|
|
1415
|
+
headers: {
|
|
1416
|
+
"Cache-Control": `public, max-age=${this.tagCacheTtl}`,
|
|
1417
|
+
"Cache-Tag": this.lookupCacheTags(tag).join(","),
|
|
1418
|
+
},
|
|
1419
|
+
}),
|
|
1420
|
+
);
|
|
1421
|
+
} catch {
|
|
1422
|
+
// Best-effort: a failed L1 populate just means the next read consults KV.
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
/**
|
|
1427
|
+
* Invalidate every entry tagged with any of `tags`. Receives the whole batch
|
|
1428
|
+
* from one updateTag()/revalidateTag() call so the eager-purge hook fires
|
|
1429
|
+
* ONCE (one CDN purge request, not one per tag). For each tag: records the KV
|
|
1430
|
+
* marker (the durable cross-colo truth that reads compare taggedAt against),
|
|
1431
|
+
* writes the fresh marker straight into this colo's L1 (write-through, NOT
|
|
1432
|
+
* delete - a delete would let the next read re-read a not-yet-converged KV
|
|
1433
|
+
* value and re-arm the stale window), and memoizes it for same-request
|
|
1434
|
+
* read-your-own-writes. Finally fires onRevalidateTag with the namespaced
|
|
1435
|
+
* lookup Cache-Tags so a consumer purge evicts the cached lookups in other
|
|
1436
|
+
* colos promptly (otherwise they converge within tagCacheTtl).
|
|
1437
|
+
*
|
|
1438
|
+
* Durable-write integrity: the in-memory write-through (memo + L1) for a tag
|
|
1439
|
+
* runs ONLY after that tag's KV marker write is confirmed. If any KV write
|
|
1440
|
+
* fails (transient error, or an over-512-byte key), this rejects with the
|
|
1441
|
+
* failed tags so an awaiting updateTag() surfaces the failure instead of
|
|
1442
|
+
* silently reporting success while other requests/colos serve stale data. The
|
|
1443
|
+
* eager purge still fires for the whole batch first (it is additive).
|
|
1444
|
+
*/
|
|
1445
|
+
async invalidateTags(tags: string[]): Promise<void> {
|
|
1446
|
+
if (tags.length === 0) return;
|
|
1447
|
+
const invalidatedAt = Date.now();
|
|
1448
|
+
const ctx = _getRequestContext();
|
|
1449
|
+
const memo = ctx ? getTagMarkerMemo(ctx) : undefined;
|
|
1450
|
+
|
|
1451
|
+
if (!this.kv && !this.onRevalidateTag) {
|
|
1452
|
+
console.warn(
|
|
1453
|
+
`[CFCacheStore] invalidateTags had no effect: configure a KV namespace ` +
|
|
1454
|
+
`for distributed invalidation, or an onRevalidateTag hook.`,
|
|
1455
|
+
);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const failedTags = new Set<string>();
|
|
1459
|
+
const errors: unknown[] = [];
|
|
1460
|
+
if (this.kv) {
|
|
1461
|
+
await Promise.all(
|
|
1462
|
+
tags.map(async (tag) => {
|
|
1463
|
+
const markerKey = this.tagMarkerKey(tag);
|
|
1464
|
+
if (kvKeyByteLength(markerKey) > KV_MAX_KEY_BYTES) {
|
|
1465
|
+
failedTags.add(tag);
|
|
1466
|
+
errors.push(
|
|
1467
|
+
new Error(
|
|
1468
|
+
`tag "${tag}" produces a ${kvKeyByteLength(markerKey)}-byte KV ` +
|
|
1469
|
+
`marker key, over the ${KV_MAX_KEY_BYTES}-byte limit`,
|
|
1470
|
+
),
|
|
1471
|
+
);
|
|
1472
|
+
return;
|
|
1473
|
+
}
|
|
1474
|
+
try {
|
|
1475
|
+
await this.kv!.put(markerKey, String(invalidatedAt), {
|
|
1476
|
+
...(this.tagInvalidationTtl
|
|
1477
|
+
? { expirationTtl: this.tagInvalidationTtl }
|
|
1478
|
+
: {}),
|
|
1479
|
+
});
|
|
1480
|
+
} catch (error) {
|
|
1481
|
+
failedTags.add(tag);
|
|
1482
|
+
errors.push(error);
|
|
1483
|
+
}
|
|
1484
|
+
}),
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// Write-through memo + L1 only for tags with a confirmed durable marker (or
|
|
1489
|
+
// for every tag when there is no KV at all - a purge-only/dev config, where
|
|
1490
|
+
// the in-memory write-through is the only invalidation signal there is). The
|
|
1491
|
+
// memo write is synchronous (read-your-own-writes); the L1 Cache API writes
|
|
1492
|
+
// are independent, so fan them out in parallel rather than awaiting each.
|
|
1493
|
+
const l1Writes: Promise<void>[] = [];
|
|
1494
|
+
for (const tag of tags) {
|
|
1495
|
+
if (failedTags.has(tag)) continue;
|
|
1496
|
+
memo?.set(tag, invalidatedAt);
|
|
1497
|
+
if (this.tagCacheTtl > 0) {
|
|
1498
|
+
l1Writes.push(this.putTagMarkerL1(tag, invalidatedAt));
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
if (l1Writes.length > 0) await Promise.all(l1Writes);
|
|
1502
|
+
|
|
1503
|
+
// One batched eager purge of the lookup markers for the whole call. Fired
|
|
1504
|
+
// regardless of KV write outcome (it is additive and uses pure string ops).
|
|
1505
|
+
if (this.onRevalidateTag) {
|
|
1506
|
+
try {
|
|
1507
|
+
await this.onRevalidateTag(tags.map((tag) => this.lookupPurgeTag(tag)));
|
|
1508
|
+
} catch (error) {
|
|
1509
|
+
reportCacheError(
|
|
1510
|
+
error,
|
|
1511
|
+
"cache-invalidate",
|
|
1512
|
+
"[CFCacheStore] onRevalidateTag hook",
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
if (failedTags.size > 0) {
|
|
1518
|
+
const err = new Error(
|
|
1519
|
+
`[CFCacheStore] ${failedTags.size}/${tags.length} tag marker write(s) ` +
|
|
1520
|
+
`failed: ${[...failedTags].join(", ")}. Those tags may still serve ` +
|
|
1521
|
+
`stale data across requests/colos; retry the invalidation.`,
|
|
1522
|
+
);
|
|
1523
|
+
(err as Error & { cause?: unknown }).cause = errors[0];
|
|
1524
|
+
throw err;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
|
|
724
1528
|
// ============================================================================
|
|
725
1529
|
// KV L2 Helpers
|
|
726
1530
|
// ============================================================================
|
|
@@ -736,15 +1540,26 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
736
1540
|
|
|
737
1541
|
try {
|
|
738
1542
|
const kvKey = this.toKVKey(key);
|
|
739
|
-
const
|
|
740
|
-
|
|
1543
|
+
const envelope = await this.kvGetOrEvict<KVSegmentEnvelope>(
|
|
1544
|
+
kvKey,
|
|
1545
|
+
(e) =>
|
|
1546
|
+
typeof e.e === "number" && typeof e.s === "number" && e.d != null,
|
|
1547
|
+
"kvGetSegment",
|
|
1548
|
+
);
|
|
1549
|
+
if (!envelope) return null;
|
|
741
1550
|
|
|
742
|
-
const envelope = raw as KVSegmentEnvelope;
|
|
743
1551
|
const now = Date.now();
|
|
744
1552
|
|
|
745
1553
|
// Hard-expired — treat as miss
|
|
746
1554
|
if (now > envelope.e) return null;
|
|
747
1555
|
|
|
1556
|
+
// Tag invalidation check (also covers the KV tier, not just L1).
|
|
1557
|
+
if (
|
|
1558
|
+
await this.isGloballyInvalidated(envelope.d.tags, envelope.d.taggedAt)
|
|
1559
|
+
) {
|
|
1560
|
+
return null;
|
|
1561
|
+
}
|
|
1562
|
+
|
|
748
1563
|
const shouldRevalidate = now > envelope.s;
|
|
749
1564
|
|
|
750
1565
|
// Promote to L1 in background
|
|
@@ -752,7 +1567,7 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
752
1567
|
|
|
753
1568
|
return { data: envelope.d, shouldRevalidate };
|
|
754
1569
|
} catch (error) {
|
|
755
|
-
|
|
1570
|
+
reportCacheError(error, "cache-read", "[CFCacheStore] kvGetSegment");
|
|
756
1571
|
return null;
|
|
757
1572
|
}
|
|
758
1573
|
}
|
|
@@ -766,28 +1581,30 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
766
1581
|
data: CachedEntryData,
|
|
767
1582
|
staleAt: number,
|
|
768
1583
|
totalTtl: number,
|
|
1584
|
+
swrWindow: number,
|
|
769
1585
|
): void {
|
|
770
1586
|
// KV requires expirationTtl >= 60s. Skip write for short-lived entries.
|
|
771
1587
|
if (!this.kv || !this.waitUntil || totalTtl < 60) return;
|
|
772
1588
|
|
|
773
1589
|
const kvKey = this.toKVKey(key);
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1590
|
+
const expiresAt = staleAt + swrWindow * 1000;
|
|
1591
|
+
|
|
1592
|
+
this.waitUntil(() =>
|
|
1593
|
+
reportingAsync(
|
|
1594
|
+
() => {
|
|
1595
|
+
const envelope: KVSegmentEnvelope = {
|
|
1596
|
+
d: data,
|
|
1597
|
+
s: staleAt,
|
|
1598
|
+
e: expiresAt,
|
|
1599
|
+
};
|
|
1600
|
+
return this.kv!.put(kvKey, JSON.stringify(envelope), {
|
|
1601
|
+
expirationTtl: totalTtl,
|
|
1602
|
+
});
|
|
1603
|
+
},
|
|
1604
|
+
"cache-write",
|
|
1605
|
+
"[CFCacheStore] kvSetSegment",
|
|
1606
|
+
),
|
|
1607
|
+
);
|
|
791
1608
|
}
|
|
792
1609
|
|
|
793
1610
|
/**
|
|
@@ -797,27 +1614,35 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
797
1614
|
private promoteSegmentToL1(key: string, envelope: KVSegmentEnvelope): void {
|
|
798
1615
|
if (!this.waitUntil) return;
|
|
799
1616
|
|
|
800
|
-
this.waitUntil(
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1617
|
+
this.waitUntil(() =>
|
|
1618
|
+
reportingAsync(
|
|
1619
|
+
async () => {
|
|
1620
|
+
const now = Date.now();
|
|
1621
|
+
const remainingTtl = Math.max(
|
|
1622
|
+
1,
|
|
1623
|
+
Math.floor((envelope.e - now) / 1000),
|
|
1624
|
+
);
|
|
1625
|
+
const cache = await this.getCache();
|
|
1626
|
+
const request = this.keyToRequest(key);
|
|
1627
|
+
|
|
1628
|
+
const response = new Response(JSON.stringify(envelope.d), {
|
|
1629
|
+
headers: {
|
|
1630
|
+
"Content-Type": "application/json",
|
|
1631
|
+
"Cache-Control": `public, max-age=${remainingTtl}`,
|
|
1632
|
+
[CACHE_STALE_AT_HEADER]: String(envelope.s),
|
|
1633
|
+
[CACHE_STATUS_HEADER]: "HIT",
|
|
1634
|
+
// Preserve tags across KV->L1 promotion so the promoted entry
|
|
1635
|
+
// stays tag-invalidatable.
|
|
1636
|
+
...this.tagHeaderEntries(envelope.d.tags, envelope.d.taggedAt),
|
|
1637
|
+
},
|
|
1638
|
+
});
|
|
1639
|
+
|
|
1640
|
+
await cache.put(request, response);
|
|
1641
|
+
},
|
|
1642
|
+
"cache-write",
|
|
1643
|
+
"[CFCacheStore] promoteSegmentToL1",
|
|
1644
|
+
),
|
|
1645
|
+
);
|
|
821
1646
|
}
|
|
822
1647
|
|
|
823
1648
|
/**
|
|
@@ -829,14 +1654,25 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
829
1654
|
|
|
830
1655
|
try {
|
|
831
1656
|
const kvKey = this.toKVKey(`fn:${key}`);
|
|
832
|
-
const
|
|
833
|
-
|
|
1657
|
+
const envelope = await this.kvGetOrEvict<KVItemEnvelope>(
|
|
1658
|
+
kvKey,
|
|
1659
|
+
(e) =>
|
|
1660
|
+
typeof e.v === "string" &&
|
|
1661
|
+
typeof e.e === "number" &&
|
|
1662
|
+
typeof e.s === "number",
|
|
1663
|
+
"kvGetItem",
|
|
1664
|
+
);
|
|
1665
|
+
if (!envelope) return null;
|
|
834
1666
|
|
|
835
|
-
const envelope = raw as KVItemEnvelope;
|
|
836
1667
|
const now = Date.now();
|
|
837
1668
|
|
|
838
1669
|
if (now > envelope.e) return null;
|
|
839
1670
|
|
|
1671
|
+
// Tag invalidation check (also covers the KV tier, not just L1).
|
|
1672
|
+
if (await this.isGloballyInvalidated(envelope.t, envelope.ta)) {
|
|
1673
|
+
return null;
|
|
1674
|
+
}
|
|
1675
|
+
|
|
840
1676
|
const shouldRevalidate = now > envelope.s;
|
|
841
1677
|
|
|
842
1678
|
// Promote to L1
|
|
@@ -846,9 +1682,10 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
846
1682
|
value: envelope.v,
|
|
847
1683
|
handles: envelope.h,
|
|
848
1684
|
shouldRevalidate,
|
|
1685
|
+
tags: envelope.t,
|
|
849
1686
|
};
|
|
850
1687
|
} catch (error) {
|
|
851
|
-
|
|
1688
|
+
reportCacheError(error, "cache-read", "[CFCacheStore] kvGetItem");
|
|
852
1689
|
return null;
|
|
853
1690
|
}
|
|
854
1691
|
}
|
|
@@ -860,28 +1697,39 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
860
1697
|
private promoteItemToL1(key: string, envelope: KVItemEnvelope): void {
|
|
861
1698
|
if (!this.waitUntil) return;
|
|
862
1699
|
|
|
863
|
-
this.waitUntil(
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1700
|
+
this.waitUntil(() =>
|
|
1701
|
+
reportingAsync(
|
|
1702
|
+
async () => {
|
|
1703
|
+
const now = Date.now();
|
|
1704
|
+
const remainingTtl = Math.max(
|
|
1705
|
+
1,
|
|
1706
|
+
Math.floor((envelope.e - now) / 1000),
|
|
1707
|
+
);
|
|
1708
|
+
const cache = await this.getCache();
|
|
1709
|
+
const request = this.keyToRequest(`fn:${key}`);
|
|
1710
|
+
|
|
1711
|
+
const body = JSON.stringify({
|
|
1712
|
+
value: envelope.v,
|
|
1713
|
+
handles: envelope.h,
|
|
1714
|
+
});
|
|
1715
|
+
const response = new Response(body, {
|
|
1716
|
+
headers: {
|
|
1717
|
+
"Content-Type": "application/json",
|
|
1718
|
+
"Cache-Control": `public, max-age=${remainingTtl}`,
|
|
1719
|
+
[CACHE_STALE_AT_HEADER]: String(envelope.s),
|
|
1720
|
+
[CACHE_STATUS_HEADER]: "HIT",
|
|
1721
|
+
// Preserve tags across KV->L1 promotion (the item tier previously
|
|
1722
|
+
// dropped them, permanently disabling tag invalidation here).
|
|
1723
|
+
...this.tagHeaderEntries(envelope.t, envelope.ta),
|
|
1724
|
+
},
|
|
1725
|
+
});
|
|
1726
|
+
|
|
1727
|
+
await cache.put(request, response);
|
|
1728
|
+
},
|
|
1729
|
+
"cache-write",
|
|
1730
|
+
"[CFCacheStore] promoteItemToL1",
|
|
1731
|
+
),
|
|
1732
|
+
);
|
|
885
1733
|
}
|
|
886
1734
|
|
|
887
1735
|
/**
|
|
@@ -895,31 +1743,58 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
895
1743
|
|
|
896
1744
|
try {
|
|
897
1745
|
const kvKey = this.toKVKey(`doc:${key}`);
|
|
898
|
-
const
|
|
899
|
-
|
|
1746
|
+
const envelope = await this.kvGetOrEvict<KVResponseEnvelope>(
|
|
1747
|
+
kvKey,
|
|
1748
|
+
(e) =>
|
|
1749
|
+
typeof e.b === "string" &&
|
|
1750
|
+
typeof e.st === "number" &&
|
|
1751
|
+
typeof e.e === "number" &&
|
|
1752
|
+
typeof e.s === "number" &&
|
|
1753
|
+
Array.isArray(e.hd),
|
|
1754
|
+
"kvGetResponse",
|
|
1755
|
+
);
|
|
1756
|
+
if (!envelope) return null;
|
|
900
1757
|
|
|
901
|
-
const envelope = raw as KVResponseEnvelope;
|
|
902
1758
|
const now = Date.now();
|
|
903
1759
|
|
|
904
1760
|
if (now > envelope.e) return null;
|
|
905
1761
|
|
|
1762
|
+
// Tag invalidation check (also covers the KV tier, not just L1).
|
|
1763
|
+
if (await this.isGloballyInvalidated(envelope.t, envelope.ta)) {
|
|
1764
|
+
return null;
|
|
1765
|
+
}
|
|
1766
|
+
|
|
906
1767
|
const shouldRevalidate = now > envelope.s;
|
|
907
1768
|
|
|
908
|
-
// Reconstruct Response
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1769
|
+
// Reconstruct Response: decode base64 -> binary, rebuild headers/status.
|
|
1770
|
+
// Corrupt/partial base64 throws in atob; malformed `hd` or an out-of-range
|
|
1771
|
+
// `st` throws in new Headers/new Response. Any of these is a faulty entry,
|
|
1772
|
+
// so evict it and miss rather than re-failing every read until TTL.
|
|
1773
|
+
let response: Response;
|
|
1774
|
+
try {
|
|
1775
|
+
const bodyBuffer = base64ToBuffer(envelope.b);
|
|
1776
|
+
const headers = new Headers(envelope.hd);
|
|
1777
|
+
response = new Response(bodyBuffer, {
|
|
1778
|
+
status: envelope.st,
|
|
1779
|
+
statusText: envelope.stx,
|
|
1780
|
+
headers,
|
|
1781
|
+
});
|
|
1782
|
+
} catch (error) {
|
|
1783
|
+
reportCacheError(
|
|
1784
|
+
error,
|
|
1785
|
+
"cache-corrupt",
|
|
1786
|
+
"[CFCacheStore] kvGetResponse: corrupt response envelope, evicting",
|
|
1787
|
+
);
|
|
1788
|
+
await this.evictKvKey(kvKey, "kvGetResponse");
|
|
1789
|
+
return null;
|
|
1790
|
+
}
|
|
916
1791
|
|
|
917
1792
|
// Promote to L1
|
|
918
1793
|
this.promoteResponseToL1(key, envelope);
|
|
919
1794
|
|
|
920
1795
|
return { response, shouldRevalidate };
|
|
921
1796
|
} catch (error) {
|
|
922
|
-
|
|
1797
|
+
reportCacheError(error, "cache-read", "[CFCacheStore] kvGetResponse");
|
|
923
1798
|
return null;
|
|
924
1799
|
}
|
|
925
1800
|
}
|
|
@@ -931,29 +1806,45 @@ export class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
|
931
1806
|
private promoteResponseToL1(key: string, envelope: KVResponseEnvelope): void {
|
|
932
1807
|
if (!this.waitUntil) return;
|
|
933
1808
|
|
|
934
|
-
this.waitUntil(
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1809
|
+
this.waitUntil(() =>
|
|
1810
|
+
reportingAsync(
|
|
1811
|
+
async () => {
|
|
1812
|
+
const now = Date.now();
|
|
1813
|
+
const remainingTtl = Math.max(
|
|
1814
|
+
1,
|
|
1815
|
+
Math.floor((envelope.e - now) / 1000),
|
|
1816
|
+
);
|
|
1817
|
+
const cache = await this.getCache();
|
|
1818
|
+
const request = this.keyToRequest(`doc:${key}`);
|
|
1819
|
+
|
|
1820
|
+
const headers = new Headers(envelope.hd);
|
|
1821
|
+
const originalCacheControl = headers.get("Cache-Control");
|
|
1822
|
+
if (originalCacheControl !== null) {
|
|
1823
|
+
headers.set(CACHE_ORIG_CC_HEADER, originalCacheControl);
|
|
1824
|
+
}
|
|
1825
|
+
headers.set("Cache-Control", `public, max-age=${remainingTtl}`);
|
|
1826
|
+
headers.set(CACHE_STALE_AT_HEADER, String(envelope.s));
|
|
1827
|
+
// Re-attach the internal tag headers (envelope.hd is client-facing
|
|
1828
|
+
// and intentionally excludes them) so the promoted entry stays
|
|
1829
|
+
// invalidatable.
|
|
1830
|
+
const tagHeaders = this.tagHeaderEntries(envelope.t, envelope.ta);
|
|
1831
|
+
for (const [name, value] of Object.entries(tagHeaders)) {
|
|
1832
|
+
headers.set(name, value);
|
|
1833
|
+
}
|
|
940
1834
|
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1835
|
+
const bodyBuffer = base64ToBuffer(envelope.b);
|
|
1836
|
+
const response = new Response(bodyBuffer, {
|
|
1837
|
+
status: envelope.st,
|
|
1838
|
+
statusText: envelope.stx,
|
|
1839
|
+
headers,
|
|
1840
|
+
});
|
|
944
1841
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
await cache.put(request, response);
|
|
953
|
-
} catch (error) {
|
|
954
|
-
console.error("[CFCacheStore] L1 response promote failed:", error);
|
|
955
|
-
}
|
|
956
|
-
});
|
|
1842
|
+
await cache.put(request, response);
|
|
1843
|
+
},
|
|
1844
|
+
"cache-write",
|
|
1845
|
+
"[CFCacheStore] promoteResponseToL1",
|
|
1846
|
+
),
|
|
1847
|
+
);
|
|
957
1848
|
}
|
|
958
1849
|
}
|
|
959
1850
|
|