@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- 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 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +117 -23
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +36 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +96 -12
- package/src/index.ts +94 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -85
- 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 +96 -68
- 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 +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +109 -118
- package/src/vite/router-discovery.ts +718 -119
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cache
|
|
2
|
+
* Cache profile resolution.
|
|
3
3
|
*
|
|
4
|
-
* Named cache profiles for "use cache" directive
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Named cache profiles for the "use cache" directive define TTL, SWR, and
|
|
5
|
+
* optional default tags. createRouter() resolves the user's profiles once via
|
|
6
|
+
* resolveCacheProfiles() and threads the resulting map onto each request
|
|
7
|
+
* context; the "use cache: <profile>" runtime path reads it from there
|
|
8
|
+
* (request-scoped) — there is no global registry.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
export interface CacheProfile {
|
|
@@ -13,14 +15,25 @@ export interface CacheProfile {
|
|
|
13
15
|
swr?: number;
|
|
14
16
|
/** Default cache tags for invalidation */
|
|
15
17
|
tags?: string[];
|
|
18
|
+
/**
|
|
19
|
+
* When true, a stale entry encountered during a server action's revalidation
|
|
20
|
+
* render is re-executed in the FOREGROUND (the action response reflects the
|
|
21
|
+
* refreshed value) instead of being served stale + revalidated in the
|
|
22
|
+
* background. Only the store write is deferred. Use this for mutation-related
|
|
23
|
+
* cached data that an action should refresh immediately; ordinary navigations
|
|
24
|
+
* always keep SWR. Default false: incidental TTL staleness must not turn an
|
|
25
|
+
* unrelated action into a synchronous cache-refresh barrier — for strong
|
|
26
|
+
* read-your-own-writes after a mutation, prefer updateTag() (a hard purge, so
|
|
27
|
+
* the action's own re-render is a fresh foreground miss).
|
|
28
|
+
*
|
|
29
|
+
* Honored only by `"use cache"` functions; route-level `cache()` and cached
|
|
30
|
+
* loaders ignore it (use updateTag() there).
|
|
31
|
+
*/
|
|
32
|
+
foregroundOnAction?: boolean;
|
|
16
33
|
}
|
|
17
34
|
|
|
18
35
|
const DEFAULT_PROFILE: CacheProfile = { ttl: 900, swr: 1800 };
|
|
19
36
|
|
|
20
|
-
let _profiles: Record<string, CacheProfile> = {
|
|
21
|
-
default: DEFAULT_PROFILE,
|
|
22
|
-
};
|
|
23
|
-
|
|
24
37
|
const PROFILE_NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
25
38
|
|
|
26
39
|
/**
|
|
@@ -44,30 +57,32 @@ export function resolveCacheProfiles(
|
|
|
44
57
|
`Profile names must match [a-zA-Z0-9_-]+.`,
|
|
45
58
|
);
|
|
46
59
|
}
|
|
47
|
-
|
|
60
|
+
const profile = profiles[name];
|
|
61
|
+
// Validate ttl/swr VALUES, not just the name. An unvalidated NaN/Infinity
|
|
62
|
+
// ttl flows into computeExpiration -> staleAt/expiresAt = NaN, and every
|
|
63
|
+
// expiry check (`now > NaN`) is false, so the entry never evicts and never
|
|
64
|
+
// revalidates: it is served fresh forever and accumulates unbounded. A
|
|
65
|
+
// negative ttl makes every read a guaranteed miss. This guard's policy is
|
|
66
|
+
// to FAIL FAST at config time on any non-finite/negative ttl — distinct
|
|
67
|
+
// from prefetch-cache-ttl.ts (falls back to the default) and defer.ts
|
|
68
|
+
// (treats Infinity as an intentional disable). Do not conflate them.
|
|
69
|
+
if (!Number.isFinite(profile.ttl) || profile.ttl < 0) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Invalid cache profile "${name}": ttl must be a finite non-negative ` +
|
|
72
|
+
`number (got ${profile.ttl}).`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (
|
|
76
|
+
profile.swr !== undefined &&
|
|
77
|
+
(!Number.isFinite(profile.swr) || profile.swr < 0)
|
|
78
|
+
) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`Invalid cache profile "${name}": swr must be a finite non-negative ` +
|
|
81
|
+
`number (got ${profile.swr}).`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
merged[name] = profile;
|
|
48
85
|
}
|
|
49
86
|
}
|
|
50
87
|
return merged;
|
|
51
88
|
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Set all cache profiles in the global registry.
|
|
55
|
-
* Called by createRouter() at startup for DSL-time resolution
|
|
56
|
-
* (cache("profileName") reads from this during route definition).
|
|
57
|
-
*
|
|
58
|
-
* WARNING: This is global mutable state. It exists only for DSL-time
|
|
59
|
-
* reads. Runtime resolution (registerCachedFunction) uses request-scoped
|
|
60
|
-
* profiles and does NOT read from this registry.
|
|
61
|
-
*/
|
|
62
|
-
export function setCacheProfiles(profiles: Record<string, CacheProfile>): void {
|
|
63
|
-
_profiles = resolveCacheProfiles(profiles);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Get a cache profile by name from the global registry.
|
|
68
|
-
* Used only at DSL-time (cache("profileName") inside urls() evaluation).
|
|
69
|
-
* Runtime code uses request-scoped profiles instead.
|
|
70
|
-
*/
|
|
71
|
-
export function getCacheProfile(name: string): CacheProfile | undefined {
|
|
72
|
-
return _profiles[name];
|
|
73
|
-
}
|
|
@@ -13,10 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
import type { CacheItemResult, CacheItemOptions } from "./types.js";
|
|
15
15
|
import { runBackground } from "./background-task.js";
|
|
16
|
+
import { reportCacheError } from "./cache-error.js";
|
|
17
|
+
import type { CacheErrorReporter } from "./cache-error.js";
|
|
16
18
|
|
|
17
|
-
|
|
19
|
+
// The host carries both the optional waitUntil (for background scheduling) and
|
|
20
|
+
// the CacheErrorReporter seam (for routing degradation errors through onError).
|
|
21
|
+
// loader-cache.ts passes the request context, which provides both.
|
|
22
|
+
type WaitUntilHost = {
|
|
18
23
|
waitUntil?: (fn: () => Promise<void>) => void;
|
|
19
|
-
}
|
|
24
|
+
} & CacheErrorReporter;
|
|
20
25
|
|
|
21
26
|
export interface ReadThroughItemConfig<T> {
|
|
22
27
|
/** Retrieve a cached item by key */
|
|
@@ -31,6 +36,14 @@ export interface ReadThroughItemConfig<T> {
|
|
|
31
36
|
key: string;
|
|
32
37
|
/** Execute the underlying function/loader on miss or revalidation */
|
|
33
38
|
execute: () => Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Optional wrapper applied to execute() ONLY on the background
|
|
41
|
+
* stale-revalidation path (not the foreground miss, where the caller's context
|
|
42
|
+
* is already established). Used to re-establish the request-context ALS, which
|
|
43
|
+
* a detached waitUntil task loses on workerd. Defaults to calling execute()
|
|
44
|
+
* directly.
|
|
45
|
+
*/
|
|
46
|
+
wrapBackground?: (run: () => Promise<T>) => Promise<T>;
|
|
34
47
|
/** Serialize result for storage. Return null to skip caching. */
|
|
35
48
|
serialize: (data: T) => Promise<string | null>;
|
|
36
49
|
/** Deserialize cached value back to the original type */
|
|
@@ -72,13 +85,23 @@ export async function readThroughItem<T>(
|
|
|
72
85
|
onMiss,
|
|
73
86
|
onCached,
|
|
74
87
|
host,
|
|
88
|
+
wrapBackground,
|
|
75
89
|
} = config;
|
|
76
90
|
|
|
77
|
-
// Cache lookup
|
|
91
|
+
// Cache lookup. An infra read failure (getItem) is reported by the store
|
|
92
|
+
// itself, so here we just degrade to a miss. A deserialize failure is a
|
|
93
|
+
// corrupt/truncated stored entry, which this layer owns: report it LOUD as
|
|
94
|
+
// cache-corrupt, then fall through to a fresh execution (the miss-path write
|
|
95
|
+
// self-heals the bad entry).
|
|
96
|
+
let cached: CacheItemResult | null = null;
|
|
78
97
|
try {
|
|
79
|
-
|
|
98
|
+
cached = await getItem(key);
|
|
99
|
+
} catch {
|
|
100
|
+
cached = null;
|
|
101
|
+
}
|
|
80
102
|
|
|
81
|
-
|
|
103
|
+
if (cached) {
|
|
104
|
+
try {
|
|
82
105
|
const data = await deserialize(cached.value);
|
|
83
106
|
|
|
84
107
|
if (!cached.shouldRevalidate) {
|
|
@@ -92,21 +115,37 @@ export async function readThroughItem<T>(
|
|
|
92
115
|
host,
|
|
93
116
|
async () => {
|
|
94
117
|
try {
|
|
95
|
-
|
|
118
|
+
// Re-establish the caller's context (request-context ALS) for the
|
|
119
|
+
// detached background execution; the foreground miss below calls
|
|
120
|
+
// execute() directly since its context is already established.
|
|
121
|
+
const fresh = await (wrapBackground
|
|
122
|
+
? wrapBackground(execute)
|
|
123
|
+
: execute());
|
|
96
124
|
const serialized = await serialize(fresh);
|
|
97
125
|
if (serialized !== null) {
|
|
98
126
|
await setItem(key, serialized, storeOptions);
|
|
99
127
|
}
|
|
100
|
-
} catch {
|
|
101
|
-
|
|
128
|
+
} catch (error) {
|
|
129
|
+
reportCacheError(
|
|
130
|
+
error,
|
|
131
|
+
"stale-revalidation",
|
|
132
|
+
"[read-through] background revalidation",
|
|
133
|
+
host ?? undefined,
|
|
134
|
+
);
|
|
102
135
|
}
|
|
103
136
|
},
|
|
104
137
|
true,
|
|
105
138
|
);
|
|
106
139
|
return data;
|
|
140
|
+
} catch (error) {
|
|
141
|
+
reportCacheError(
|
|
142
|
+
error,
|
|
143
|
+
"cache-corrupt",
|
|
144
|
+
"[read-through] deserialize stored entry",
|
|
145
|
+
host ?? undefined,
|
|
146
|
+
);
|
|
147
|
+
// fall through to fresh execution
|
|
107
148
|
}
|
|
108
|
-
} catch {
|
|
109
|
-
// Cache lookup failed, fall through to fresh execution
|
|
110
149
|
}
|
|
111
150
|
|
|
112
151
|
// Cache miss
|
|
@@ -123,8 +162,13 @@ export async function readThroughItem<T>(
|
|
|
123
162
|
await setItem(key, serialized, storeOptions);
|
|
124
163
|
onCached?.();
|
|
125
164
|
}
|
|
126
|
-
} catch {
|
|
127
|
-
|
|
165
|
+
} catch (error) {
|
|
166
|
+
reportCacheError(
|
|
167
|
+
error,
|
|
168
|
+
"cache-write",
|
|
169
|
+
"[read-through] cache write",
|
|
170
|
+
host ?? undefined,
|
|
171
|
+
);
|
|
128
172
|
}
|
|
129
173
|
},
|
|
130
174
|
true,
|
|
@@ -10,16 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ResolvedSegment } from "../types.js";
|
|
12
12
|
import type { SerializedSegmentData } from "./types.js";
|
|
13
|
+
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
13
14
|
import {
|
|
14
15
|
renderToReadableStream,
|
|
15
16
|
createTemporaryReferenceSet,
|
|
16
17
|
} from "@vitejs/plugin-rsc/rsc";
|
|
17
18
|
import { createFromReadableStream } from "@vitejs/plugin-rsc/rsc";
|
|
18
19
|
|
|
19
|
-
// ============================================================================
|
|
20
|
-
// Stream Utilities (internal)
|
|
21
|
-
// ============================================================================
|
|
22
|
-
|
|
23
20
|
/**
|
|
24
21
|
* Convert a ReadableStream to a string.
|
|
25
22
|
*/
|
|
@@ -55,10 +52,6 @@ export function stringToStream(str: string): ReadableStream<Uint8Array> {
|
|
|
55
52
|
});
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
// ============================================================================
|
|
59
|
-
// RSC Serialization Primitives (internal)
|
|
60
|
-
// ============================================================================
|
|
61
|
-
|
|
62
55
|
/**
|
|
63
56
|
* RSC-serialize a value using React Server Components stream.
|
|
64
57
|
* Used for serializing loaderData, layout, loading components etc.
|
|
@@ -90,10 +83,6 @@ export async function rscDeserialize<T>(
|
|
|
90
83
|
return createFromReadableStream<T>(stream, { temporaryReferences });
|
|
91
84
|
}
|
|
92
85
|
|
|
93
|
-
// ============================================================================
|
|
94
|
-
// Null-Preserving RSC Serialization (for caching)
|
|
95
|
-
// ============================================================================
|
|
96
|
-
|
|
97
86
|
/**
|
|
98
87
|
* RSC-serialize any value including null.
|
|
99
88
|
* Unlike rscSerialize(), this does NOT skip null — it serializes it through
|
|
@@ -107,7 +96,14 @@ export async function serializeResult(value: unknown): Promise<string | null> {
|
|
|
107
96
|
const temporaryReferences = createTemporaryReferenceSet();
|
|
108
97
|
const stream = renderToReadableStream(value, { temporaryReferences });
|
|
109
98
|
return await streamToString(stream);
|
|
110
|
-
} catch {
|
|
99
|
+
} catch (error) {
|
|
100
|
+
// Returning null silently turns a non-serializable cache value into a
|
|
101
|
+
// permanent miss with no trace. Surface it on the internal debug channel so
|
|
102
|
+
// a failed serialize is diagnosable on wrangler tail, but keep returning
|
|
103
|
+
// null so the caller falls through to an uncached render rather than throws.
|
|
104
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
105
|
+
console.warn("[segment-codec] serializeResult failed:", error);
|
|
106
|
+
}
|
|
111
107
|
return null;
|
|
112
108
|
}
|
|
113
109
|
}
|
|
@@ -122,10 +118,6 @@ export async function deserializeResult<T>(encoded: string): Promise<T> {
|
|
|
122
118
|
return createFromReadableStream<T>(stream, { temporaryReferences });
|
|
123
119
|
}
|
|
124
120
|
|
|
125
|
-
// ============================================================================
|
|
126
|
-
// Public API
|
|
127
|
-
// ============================================================================
|
|
128
|
-
|
|
129
121
|
/**
|
|
130
122
|
* RSC-deserialize a single encoded component string back to a React element.
|
|
131
123
|
* Used by the static handler runtime to revive pre-rendered components.
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Tag Invalidation API
|
|
3
|
+
*
|
|
4
|
+
* Two on-demand invalidation verbs, mirroring the distinction popularized by
|
|
5
|
+
* Next.js so consumers can pick the right consistency model:
|
|
6
|
+
*
|
|
7
|
+
* - updateTag(...tags): read-your-own-writes. Awaitable - resolves only after
|
|
8
|
+
* in-process invalidation across every configured store completes. Use in a
|
|
9
|
+
* Server Action and `await` it before the action re-renders, so the action's
|
|
10
|
+
* own response reflects the mutation.
|
|
11
|
+
*
|
|
12
|
+
* - revalidateTag(...tags): fire-and-forget via waitUntil - the response is not
|
|
13
|
+
* blocked. Use in Route Handlers / webhooks. NOTE: both verbs hard-purge; the
|
|
14
|
+
* only difference is awaitability. revalidateTag does NOT serve stale content -
|
|
15
|
+
* the next read after the invalidation lands is a hard miss that re-renders.
|
|
16
|
+
* (The name mirrors Next.js, where it is SWR; here it is background-purge.)
|
|
17
|
+
*
|
|
18
|
+
* Both fan out across the app-level store (ctx._cacheStore) and every explicit
|
|
19
|
+
* per-scope store from cache({ store }) registered for this handler
|
|
20
|
+
* (ctx._explicitTaggedStores), calling the store-level invalidateTags()
|
|
21
|
+
* primitive for each tag. A single configured store (the common case) owns its
|
|
22
|
+
* own tag index and distributed invalidation - there is no separate
|
|
23
|
+
* tag-invalidation store.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
27
|
+
import { reportingAsync } from "./cache-error.js";
|
|
28
|
+
import { normalizeTags } from "./cache-tag.js";
|
|
29
|
+
import type { SegmentCacheStore } from "./types.js";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Collect every store that may hold entries tagged for this request's handler:
|
|
33
|
+
* the app-level store plus all explicit per-scope stores (deduplicated). Splits
|
|
34
|
+
* them into tag-capable (implement invalidateTags()) and not, so callers can
|
|
35
|
+
* warn about configured stores whose tagged entries will NOT be invalidated.
|
|
36
|
+
*
|
|
37
|
+
* `hasContext` reports whether an ALS request context existed at all. Without one
|
|
38
|
+
* (e.g. a queue consumer or cron job calling updateTag/revalidateTag) no stores
|
|
39
|
+
* are reachable, and the empty-capable case is a missing-context problem, not a
|
|
40
|
+
* store-config problem - callers branch on this to warn about the right cause.
|
|
41
|
+
*/
|
|
42
|
+
function collectStores(): {
|
|
43
|
+
capable: SegmentCacheStore[];
|
|
44
|
+
incapable: number;
|
|
45
|
+
hasContext: boolean;
|
|
46
|
+
} {
|
|
47
|
+
const ctx = _getRequestContext();
|
|
48
|
+
const stores = new Set<SegmentCacheStore>();
|
|
49
|
+
if (ctx?._cacheStore) stores.add(ctx._cacheStore);
|
|
50
|
+
if (ctx?._explicitTaggedStores) {
|
|
51
|
+
for (const store of ctx._explicitTaggedStores) stores.add(store);
|
|
52
|
+
}
|
|
53
|
+
const capable: SegmentCacheStore[] = [];
|
|
54
|
+
let incapable = 0;
|
|
55
|
+
for (const store of stores) {
|
|
56
|
+
if (typeof store.invalidateTags === "function") capable.push(store);
|
|
57
|
+
else incapable++;
|
|
58
|
+
}
|
|
59
|
+
return { capable, incapable, hasContext: ctx != null };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Production-visible warning. A misconfigured store silently dropping
|
|
64
|
+
* invalidations is a data-correctness footgun, so this surfaces in every
|
|
65
|
+
* environment (not dev-only).
|
|
66
|
+
*/
|
|
67
|
+
function warnNoTagStore(fn: string, tags: string[]): void {
|
|
68
|
+
console.warn(
|
|
69
|
+
`[${fn}] No tag-capable cache store is configured; tags ` +
|
|
70
|
+
`[${tags.join(", ")}] were not invalidated. The configured store must ` +
|
|
71
|
+
`implement invalidateTags() (the built-in MemorySegmentCacheStore and ` +
|
|
72
|
+
`CFCacheStore do).`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Production-visible warning for the no-request-context case. Distinct from
|
|
78
|
+
* warnNoTagStore: the stores are not unreachable because they are misconfigured,
|
|
79
|
+
* but because there is no ALS request context to reach them through (e.g. a queue
|
|
80
|
+
* consumer or scheduled job). Naming the real cause keeps consumers from chasing
|
|
81
|
+
* a store-config red herring.
|
|
82
|
+
*/
|
|
83
|
+
function warnNoRequestContext(fn: string, tags: string[]): void {
|
|
84
|
+
console.warn(
|
|
85
|
+
`[${fn}] Called outside a request context (e.g. from a queue consumer or ` +
|
|
86
|
+
`scheduled job); no cache stores are reachable and tags ` +
|
|
87
|
+
`[${tags.join(", ")}] were not invalidated. Invoke it within a request ` +
|
|
88
|
+
`(Server Action or route handler).`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Production-visible warning for mixed-store configs: at least one configured
|
|
94
|
+
* store does not support tag invalidation, so its tagged entries (if any) are
|
|
95
|
+
* left stale even though other stores were invalidated.
|
|
96
|
+
*/
|
|
97
|
+
function warnPartialTagStore(fn: string, incapable: number): void {
|
|
98
|
+
console.warn(
|
|
99
|
+
`[${fn}] ${incapable} configured cache store(s) do not implement ` +
|
|
100
|
+
`invalidateTags(); their tagged entries were NOT invalidated. Use a ` +
|
|
101
|
+
`tag-capable store (e.g. MemorySegmentCacheStore / CFCacheStore) for any ` +
|
|
102
|
+
`cache({ store }) boundary whose entries you invalidate by tag.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function invalidateAcross(
|
|
107
|
+
stores: SegmentCacheStore[],
|
|
108
|
+
tags: string[],
|
|
109
|
+
): Promise<void> {
|
|
110
|
+
// One invalidateTags(tags) call per store: the store receives the whole tag
|
|
111
|
+
// batch so it can do a single CDN purge request rather than one per tag.
|
|
112
|
+
//
|
|
113
|
+
// allSettled, not all: a store's invalidateTags() can reject (e.g. CFCacheStore
|
|
114
|
+
// surfaces a failed durable KV marker write). With Promise.all, the first
|
|
115
|
+
// rejection would short-circuit and the other stores' outcomes would go
|
|
116
|
+
// unobserved. Attempt every store, then surface a combined error so an awaited
|
|
117
|
+
// updateTag() still rejects (read-your-own-writes honesty) without masking the
|
|
118
|
+
// stores that did succeed.
|
|
119
|
+
const results = await Promise.allSettled(
|
|
120
|
+
stores.map((store) => store.invalidateTags!(tags)),
|
|
121
|
+
);
|
|
122
|
+
const errors = results
|
|
123
|
+
.filter((r): r is PromiseRejectedResult => r.status === "rejected")
|
|
124
|
+
.map((r) => r.reason);
|
|
125
|
+
if (errors.length > 0) {
|
|
126
|
+
const err = new Error(
|
|
127
|
+
`[tag invalidation] ${errors.length}/${stores.length} store(s) failed to ` +
|
|
128
|
+
`invalidate tags [${tags.join(", ")}]; their entries may still serve ` +
|
|
129
|
+
`stale data. Retry the invalidation.`,
|
|
130
|
+
);
|
|
131
|
+
(err as Error & { cause?: unknown }).cause = errors[0];
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Immediately expire every cache entry tagged with any of `tags`, resolving
|
|
138
|
+
* once in-process invalidation across all configured stores completes.
|
|
139
|
+
*
|
|
140
|
+
* Read-your-own-writes: because the returned promise resolves before you return
|
|
141
|
+
* from a Server Action, awaiting it guarantees the action's own re-render (and
|
|
142
|
+
* any subsequent read) sees fresh data.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* async function updateProduct(formData: FormData) {
|
|
147
|
+
* "use server";
|
|
148
|
+
* await db.updateProduct(formData);
|
|
149
|
+
* await updateTag("products"); // next render is fresh
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export async function updateTag(...tags: string[]): Promise<void> {
|
|
154
|
+
const valid = normalizeTags(tags);
|
|
155
|
+
if (valid.length === 0) return;
|
|
156
|
+
|
|
157
|
+
const { capable, incapable, hasContext } = collectStores();
|
|
158
|
+
if (capable.length === 0) {
|
|
159
|
+
if (hasContext) warnNoTagStore("updateTag", valid);
|
|
160
|
+
else warnNoRequestContext("updateTag", valid);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (incapable > 0) warnPartialTagStore("updateTag", incapable);
|
|
164
|
+
|
|
165
|
+
await invalidateAcross(capable, valid);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Invalidate every cache entry tagged with any of `tags` in the background,
|
|
170
|
+
* without blocking the current response (fire-and-forget via waitUntil).
|
|
171
|
+
*
|
|
172
|
+
* This is NOT stale-while-revalidate: like updateTag() it hard-purges, so the
|
|
173
|
+
* next read after the invalidation lands is a miss that re-renders fresh. The
|
|
174
|
+
* only difference from updateTag() is awaitability - revalidateTag() defers the
|
|
175
|
+
* purge off the response path and is not awaited.
|
|
176
|
+
*
|
|
177
|
+
* Use in Route Handlers / webhooks. For read-your-own-writes inside a Server
|
|
178
|
+
* Action, use updateTag() instead so the action's own response is fresh.
|
|
179
|
+
*
|
|
180
|
+
* Fire-and-forget: because this returns void and runs in the background, a
|
|
181
|
+
* failed durable marker write (e.g. a transient KV outage) is NOT surfaced to
|
|
182
|
+
* the caller. It IS reported - logged loudly and routed through the router's
|
|
183
|
+
* `onError` callback (phase `cache`, `metadata.category === "cache-invalidate"`)
|
|
184
|
+
* via reportingAsync - so the failure is observable in telemetry even though it
|
|
185
|
+
* cannot be awaited. If you need the invalidation to be CONFIRMED (and to retry
|
|
186
|
+
* on failure), use `await updateTag()` instead, which rejects when a store's
|
|
187
|
+
* durable write fails.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```typescript
|
|
191
|
+
* // route handler invoked by an external webhook
|
|
192
|
+
* export async function POST() {
|
|
193
|
+
* "use server";
|
|
194
|
+
* revalidateTag("products");
|
|
195
|
+
* return new Response("ok");
|
|
196
|
+
* }
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export function revalidateTag(...tags: string[]): void {
|
|
200
|
+
const valid = normalizeTags(tags);
|
|
201
|
+
if (valid.length === 0) return;
|
|
202
|
+
|
|
203
|
+
const { capable, incapable, hasContext } = collectStores();
|
|
204
|
+
if (capable.length === 0) {
|
|
205
|
+
if (hasContext) warnNoTagStore("revalidateTag", valid);
|
|
206
|
+
else warnNoRequestContext("revalidateTag", valid);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (incapable > 0) warnPartialTagStore("revalidateTag", incapable);
|
|
210
|
+
|
|
211
|
+
const ctx = _getRequestContext();
|
|
212
|
+
// reportingAsync never rejects: it catches a failed durable write and routes
|
|
213
|
+
// it through reportCacheError (loud log + onError). This is the only place a
|
|
214
|
+
// revalidateTag failure can be observed, since it is not awaitable. Pass ctx
|
|
215
|
+
// explicitly - the run executes in a detached waitUntil where the ALS context
|
|
216
|
+
// is gone, so onError fires only if we hand it the captured context.
|
|
217
|
+
const run = () =>
|
|
218
|
+
reportingAsync(
|
|
219
|
+
() => invalidateAcross(capable, valid),
|
|
220
|
+
"cache-invalidate",
|
|
221
|
+
"[revalidateTag] background invalidation",
|
|
222
|
+
ctx,
|
|
223
|
+
);
|
|
224
|
+
if (ctx?.waitUntil) {
|
|
225
|
+
ctx.waitUntil(run);
|
|
226
|
+
} else {
|
|
227
|
+
// No request context (e.g. called outside ALS): best-effort background run.
|
|
228
|
+
void run();
|
|
229
|
+
}
|
|
230
|
+
}
|