@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136
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 +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3386 -1111
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +78 -25
- 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 +247 -23
- package/skills/caching/SKILL.md +281 -11
- 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 +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +289 -53
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +914 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +144 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +101 -5
- package/skills/router-setup/SKILL.md +117 -10
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -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 +332 -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 +1 -66
- 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/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +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 +131 -30
- package/src/browser/navigation-client.ts +199 -86
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +174 -109
- package/src/browser/prefetch/cache.ts +222 -69
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +113 -32
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +300 -122
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- 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 +23 -69
- 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 +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- 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 +125 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -61
- package/src/browser/types.ts +115 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- 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 +125 -24
- 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 +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- 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 +239 -52
- package/src/cache/cache-scope.ts +234 -87
- 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/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- 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 +84 -2
- 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 +70 -22
- 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 +99 -13
- package/src/index.ts +139 -19
- 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 +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +149 -74
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +54 -25
- package/src/router/match-middleware/cache-lookup.ts +205 -271
- package/src/router/match-middleware/cache-store.ts +81 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +93 -39
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +268 -171
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -27
- package/src/router/router-interfaces.ts +129 -35
- package/src/router/router-options.ts +202 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +197 -92
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +392 -338
- 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 +163 -59
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +174 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +660 -518
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +133 -13
- 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 +99 -189
- package/src/rsc/rsc-rendering.ts +115 -73
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -113
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +36 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +269 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +235 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +442 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- 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 +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +234 -82
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -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 +214 -132
- 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 +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- 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-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- 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 +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- 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 +119 -111
- package/src/vite/router-discovery.ts +941 -142
- 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 +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- 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
|
@@ -20,7 +20,11 @@ import {
|
|
|
20
20
|
encodeReply,
|
|
21
21
|
createClientTemporaryReferenceSet,
|
|
22
22
|
} from "@vitejs/plugin-rsc/rsc";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
getRequestContext,
|
|
25
|
+
runWithRequestContext,
|
|
26
|
+
} from "../server/request-context.js";
|
|
27
|
+
import { isUnderTestRunner } from "../runtime-env.js";
|
|
24
28
|
import {
|
|
25
29
|
isTainted,
|
|
26
30
|
CACHED_FN_SYMBOL,
|
|
@@ -32,22 +36,97 @@ import {
|
|
|
32
36
|
export { isCachedFunction };
|
|
33
37
|
import { serializeResult, deserializeResult } from "./segment-codec.js";
|
|
34
38
|
import { createHandleStore } from "../server/handle-store.js";
|
|
35
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
restoreHandles,
|
|
41
|
+
encodeHandles,
|
|
42
|
+
decodeHandles,
|
|
43
|
+
} from "./handle-snapshot.js";
|
|
36
44
|
import { startHandleCapture, type HandleCapture } from "./handle-capture.js";
|
|
37
45
|
import { sortedSearchString } from "./cache-key-utils.js";
|
|
46
|
+
import { encodeKV } from "../encode-kv.js";
|
|
38
47
|
import { runBackground } from "./background-task.js";
|
|
48
|
+
import {
|
|
49
|
+
normalizeTags,
|
|
50
|
+
recordRequestTags,
|
|
51
|
+
runWithCacheTagScope,
|
|
52
|
+
} from "./cache-tag.js";
|
|
53
|
+
import { reportCacheError } from "./cache-error.js";
|
|
54
|
+
import type { CacheItemResult } from "./types.js";
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* DJB2 hash returning an 8-char hex string. Deterministic across runtimes
|
|
58
|
+
* (no crypto import — cache-runtime runs on the edge). Mirrors prerender's
|
|
59
|
+
* param-hash djb2Hex so binary key parts hash consistently.
|
|
60
|
+
*/
|
|
61
|
+
function djb2HexBytes(bytes: Uint8Array): string {
|
|
62
|
+
let hash = 5381;
|
|
63
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
64
|
+
hash = ((hash << 5) + hash + bytes[i]!) >>> 0;
|
|
65
|
+
}
|
|
66
|
+
return hash.toString(16).padStart(8, "0");
|
|
67
|
+
}
|
|
39
68
|
|
|
40
69
|
/**
|
|
41
70
|
* Convert encodeReply result to a stable string key.
|
|
42
|
-
*
|
|
71
|
+
*
|
|
72
|
+
* encodeReply may return a string or FormData. A plain string is already
|
|
73
|
+
* deterministic for a given arg set, so return it verbatim. FormData (emitted
|
|
74
|
+
* whenever a key arg is a typed array / Blob / File / a large object React
|
|
75
|
+
* lazily chunks) carries a per-call RANDOM multipart boundary
|
|
76
|
+
* (`formdata-undici-<random>`); stringifying the whole body via
|
|
77
|
+
* `new Response(formData).text()` would therefore produce a DIFFERENT key on
|
|
78
|
+
* every call, so the cached function would always miss and the store would
|
|
79
|
+
* accumulate one duplicate entry per call (unbounded growth).
|
|
80
|
+
*
|
|
81
|
+
* Instead derive the key from the entries themselves, independent of the
|
|
82
|
+
* boundary: iterate in sorted-key order and, for each value, emit a
|
|
83
|
+
* boundary-free token — `s:<value>` for strings, `b:<size>:<type>:<name>:<hash>`
|
|
84
|
+
* for Blob/File (bytes folded via djb2 so distinct payloads of equal
|
|
85
|
+
* size/type/name still differ). Strings carry an `s:` type tag so a string whose
|
|
86
|
+
* value happens to equal a blob token (e.g. the literal `b:4::a:b:<hash>`) cannot
|
|
87
|
+
* collide with an actual Blob/File entry under the same FormData key. The
|
|
88
|
+
* user-controlled `type`/`name` are percent-encoded before joining so an embedded
|
|
89
|
+
* `:` cannot shift the field boundaries and collide two distinct files (e.g.
|
|
90
|
+
* {name:"a:b",type:""} vs {name:"b",type:":a"}). The result is stable across
|
|
91
|
+
* identical arg sets.
|
|
43
92
|
*/
|
|
44
|
-
async function replyToCacheKey(
|
|
93
|
+
export async function replyToCacheKey(
|
|
94
|
+
encoded: string | FormData,
|
|
95
|
+
): Promise<string> {
|
|
45
96
|
if (typeof encoded === "string") return encoded;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
97
|
+
|
|
98
|
+
// Snapshot entries synchronously (forEach avoids relying on FormData's
|
|
99
|
+
// iterator typings), then fold any Blob/File bytes asynchronously.
|
|
100
|
+
const raw: [string, FormDataEntryValue][] = [];
|
|
101
|
+
encoded.forEach((value, key) => {
|
|
102
|
+
raw.push([key, value]);
|
|
103
|
+
});
|
|
104
|
+
const pairs: [string, string][] = [];
|
|
105
|
+
for (const [key, value] of raw) {
|
|
106
|
+
if (typeof value === "string") {
|
|
107
|
+
// Type-tag strings with `s:` so a string equal to a blob token (e.g.
|
|
108
|
+
// `b:4::a:b:<hash>`) cannot collide with a Blob/File entry under the same
|
|
109
|
+
// key (which carries the `b:` tag below).
|
|
110
|
+
pairs.push([key, "s:" + value]);
|
|
111
|
+
} else {
|
|
112
|
+
// Blob/File: fold the bytes into a deterministic, boundary-free token.
|
|
113
|
+
// Percent-encode the user-controlled type/name so an embedded `:` cannot
|
|
114
|
+
// shift the `:`-delimited field boundaries and collide distinct files.
|
|
115
|
+
const buf = await value.arrayBuffer();
|
|
116
|
+
const hash = djb2HexBytes(new Uint8Array(buf));
|
|
117
|
+
const name = "name" in value ? value.name : "";
|
|
118
|
+
const encType = encodeURIComponent(value.type);
|
|
119
|
+
const encName = encodeURIComponent(name);
|
|
120
|
+
pairs.push([key, `b:${value.size}:${encType}:${encName}:${hash}`]);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return encodeKV(pairs, { sort: true });
|
|
49
124
|
}
|
|
50
125
|
|
|
126
|
+
// Cached-fn ids already warned about running uncached under a test runner, so
|
|
127
|
+
// the test-ergonomics warning fires once per fn rather than once per call.
|
|
128
|
+
const warnedUncachedUnderTest = new Set<string>();
|
|
129
|
+
|
|
51
130
|
// ============================================================================
|
|
52
131
|
// Core: registerCachedFunction
|
|
53
132
|
// ============================================================================
|
|
@@ -70,9 +149,38 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
70
149
|
const store = requestCtx?._cacheStore;
|
|
71
150
|
const resolvedProfileName = profileName || "default";
|
|
72
151
|
|
|
73
|
-
// Bypass: no store or no getItem support
|
|
152
|
+
// Bypass: no store or no getItem support. Still run inside a tag scope so a
|
|
153
|
+
// cacheTag() call inside the function degrades to a no-op rather than
|
|
154
|
+
// throwing "must be called inside a use cache function" - adopting cacheTag()
|
|
155
|
+
// must not hard-fail in apps/tests without an item-capable cache configured.
|
|
156
|
+
// Note: the INSIDE_CACHE_EXEC guard (cookies()/headers()/ctx.set() rejection)
|
|
157
|
+
// is intentionally NOT stamped here. It is a cached-path-only check; in the
|
|
158
|
+
// bypass the body actually executes, so the guarded side effects take effect
|
|
159
|
+
// and nothing is lost on a (non-existent) hit. Same applies to the
|
|
160
|
+
// non-serializable-args bypass below.
|
|
74
161
|
if (!store?.getItem) {
|
|
75
|
-
|
|
162
|
+
// Test-ergonomics guard: under a test runner, a "use cache" function that
|
|
163
|
+
// executes with no item-capable store seeded is exercising the UNCACHED
|
|
164
|
+
// path — a green test that proves nothing about caching. Warn once per fn
|
|
165
|
+
// id so the author knows to seed a cacheStore. Advisory (never throws), so
|
|
166
|
+
// a test that DELIBERATELY runs uncached is unaffected. Gated on the test
|
|
167
|
+
// runner (process.env.VITEST, not folded) so production never evaluates it.
|
|
168
|
+
if (isUnderTestRunner() && !warnedUncachedUnderTest.has(id)) {
|
|
169
|
+
warnedUncachedUnderTest.add(id);
|
|
170
|
+
console.warn(
|
|
171
|
+
`[rango] "use cache" function "${id}" executed but no cacheStore was ` +
|
|
172
|
+
`seeded; the cached path is NOT under test (it ran uncached). Pass ` +
|
|
173
|
+
`{ cacheStore, cacheProfiles } to runLoader/runMiddleware/renderHandler/` +
|
|
174
|
+
`runInRequestContext (or configure createRouter({ cache }) for dispatch) ` +
|
|
175
|
+
`to exercise it.`,
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
const scoped = runWithCacheTagScope(() => fn.apply(this, args));
|
|
179
|
+
const result = await scoped.result;
|
|
180
|
+
// Still record the runtime tags into the request set so a cacheTag() in an
|
|
181
|
+
// uncached function tags the document, even with no item-capable store.
|
|
182
|
+
recordRequestTags(scoped.tags, requestCtx);
|
|
183
|
+
return result;
|
|
76
184
|
}
|
|
77
185
|
|
|
78
186
|
// Resolve profile strictly from request-scoped config (set by the
|
|
@@ -155,47 +263,74 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
155
263
|
cacheKey = `use-cache:${id}`;
|
|
156
264
|
}
|
|
157
265
|
} catch {
|
|
158
|
-
// Non-serializable args: run uncached
|
|
159
|
-
|
|
266
|
+
// Non-serializable args: run uncached (within a tag scope so cacheTag()
|
|
267
|
+
// still does not throw). Record runtime tags so the document union still
|
|
268
|
+
// sees them even though this call is not itself cached.
|
|
269
|
+
const scoped = runWithCacheTagScope(() => fn.apply(this, args));
|
|
270
|
+
const result = await scoped.result;
|
|
271
|
+
recordRequestTags(scoped.tags, requestCtx);
|
|
272
|
+
return result;
|
|
160
273
|
}
|
|
161
274
|
|
|
162
275
|
// Cache lookup
|
|
163
276
|
const cached = await store.getItem(cacheKey);
|
|
164
277
|
|
|
278
|
+
// Serve a cached entry on the hit path: deserialize the stored value,
|
|
279
|
+
// replay handle data (gated on tainted args), and surface the entry's tags
|
|
280
|
+
// to the request set (the function did not re-run, so its runtime cacheTag()
|
|
281
|
+
// tags are only available from the stored entry). Shared by the fresh-hit
|
|
282
|
+
// and stale-hit branches; the only divergence is the stale branch scheduling
|
|
283
|
+
// background revalidation, which it does after this returns.
|
|
284
|
+
const serveCached = async (entry: CacheItemResult): Promise<any> => {
|
|
285
|
+
const result = await deserializeResult(entry.value);
|
|
286
|
+
if (entry.handles && hasTaintedArgs) {
|
|
287
|
+
const handleStore = requestCtx?._handleStore;
|
|
288
|
+
if (handleStore) {
|
|
289
|
+
const r = await decodeHandles(entry.handles);
|
|
290
|
+
if (r) restoreHandles(r, handleStore);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
recordRequestTags(entry.tags, requestCtx);
|
|
294
|
+
return result;
|
|
295
|
+
};
|
|
296
|
+
|
|
165
297
|
if (cached && !cached.shouldRevalidate) {
|
|
166
298
|
// Fresh hit: deserialize and return
|
|
167
299
|
try {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
// Deserialization failed, fall through to fresh execution
|
|
300
|
+
return await serveCached(cached);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
// The stored value is corrupt/partial (failed RSC deserialize). Report
|
|
303
|
+
// it, then fall through to fresh execution - the miss path below re-runs
|
|
304
|
+
// and setItem() overwrites the faulty entry under the same key (self-heal).
|
|
305
|
+
reportCacheError(
|
|
306
|
+
error,
|
|
307
|
+
"cache-corrupt",
|
|
308
|
+
`[use cache] "${id}" fresh-hit`,
|
|
309
|
+
);
|
|
179
310
|
}
|
|
180
311
|
}
|
|
181
312
|
|
|
182
|
-
|
|
313
|
+
// foregroundOnAction (opt-in; see CacheProfile.foregroundOnAction): during an
|
|
314
|
+
// action's revalidation render, a stale entry falls through to the foreground
|
|
315
|
+
// miss path below instead of SWR. The flag is set by revalidateAfterAction.
|
|
316
|
+
const foregroundOnActionRevalidation =
|
|
317
|
+
requestCtx?._inActionRevalidation === true &&
|
|
318
|
+
profile.foregroundOnAction === true;
|
|
319
|
+
if (cached?.shouldRevalidate && !foregroundOnActionRevalidation) {
|
|
183
320
|
// Stale hit: return stale value, revalidate in background
|
|
184
321
|
try {
|
|
185
|
-
const result = await
|
|
186
|
-
if (cached.handles && hasTaintedArgs) {
|
|
187
|
-
const handleStore = requestCtx?._handleStore;
|
|
188
|
-
if (handleStore) {
|
|
189
|
-
restoreHandles(cached.handles, handleStore);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
322
|
+
const result = await serveCached(cached);
|
|
192
323
|
// Background revalidation — must capture handles if tainted args present.
|
|
193
324
|
// Use an isolated handle store so background pushes don't pollute the
|
|
194
325
|
// live response or throw LateHandlePushError on the completed store.
|
|
195
326
|
// Same isolation pattern as route-level background-revalidation.ts.
|
|
196
327
|
runBackground(requestCtx, async () => {
|
|
197
|
-
//
|
|
198
|
-
//
|
|
328
|
+
// The closure-captured requestCtx is reused for the framework's own
|
|
329
|
+
// reads (handle store swap, error reporting) AND, below, to
|
|
330
|
+
// re-establish the request-context ALS around the user fn. ALS context
|
|
331
|
+
// may be gone inside waitUntil: on workerd a waitUntil task runs
|
|
332
|
+
// detached from the request's I/O context, so getRequestContext()
|
|
333
|
+
// inside the cached body would otherwise throw.
|
|
199
334
|
let originalHandleStore:
|
|
200
335
|
| ReturnType<typeof createHandleStore>
|
|
201
336
|
| undefined;
|
|
@@ -214,11 +349,21 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
214
349
|
bgStopCapture = c.stop;
|
|
215
350
|
}
|
|
216
351
|
|
|
217
|
-
// Stamp tainted
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
352
|
+
// Stamp tainted ARGS only — not requestCtx. The args stamp guards
|
|
353
|
+
// direct ctx method calls (ctx.set, ctx.header, ctx.onResponse, etc.)
|
|
354
|
+
// which is sufficient for correctness.
|
|
355
|
+
//
|
|
356
|
+
// We intentionally skip stamping requestCtx here because:
|
|
357
|
+
// 1. runBackground starts the async task synchronously (before the
|
|
358
|
+
// first await), so stampCacheExec would pollute the shared
|
|
359
|
+
// requestCtx while the foreground pipeline is still running.
|
|
360
|
+
// This causes assertNotInsideCacheExec to fire when cache-store
|
|
361
|
+
// later calls requestCtx.onResponse().
|
|
362
|
+
// 2. requestCtx methods are closure-bound to the original ctx, so
|
|
363
|
+
// neither Object.create() nor a proxy can isolate the stamp.
|
|
364
|
+
// 3. The foreground miss path already stamps requestCtx and catches
|
|
365
|
+
// cookies()/headers() misuse on first execution. The background
|
|
366
|
+
// re-runs the same function with the same request.
|
|
222
367
|
const bgTaintedArgs: unknown[] = [];
|
|
223
368
|
for (const arg of args) {
|
|
224
369
|
if (isTainted(arg)) {
|
|
@@ -226,32 +371,55 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
226
371
|
bgTaintedArgs.push(arg);
|
|
227
372
|
}
|
|
228
373
|
}
|
|
229
|
-
if (requestCtx) {
|
|
230
|
-
stampCacheExec(requestCtx as object);
|
|
231
|
-
}
|
|
232
374
|
|
|
233
375
|
try {
|
|
234
|
-
|
|
376
|
+
// Re-establish the request-context ALS so a "use cache" body that
|
|
377
|
+
// reads the ambient getRequestContext() (e.g.
|
|
378
|
+
// getRequestContext().env.ApiKey) resolves during the background
|
|
379
|
+
// revalidation instead of throwing "called outside of a request
|
|
380
|
+
// context". runWithRequestContext sets the store for fn's
|
|
381
|
+
// synchronous kickoff; its async continuations inherit it.
|
|
382
|
+
const scoped = runWithRequestContext(requestCtx, () =>
|
|
383
|
+
runWithCacheTagScope(() => fn.apply(this, args)),
|
|
384
|
+
);
|
|
385
|
+
const freshResult = await scoped.result;
|
|
235
386
|
bgStopCapture?.();
|
|
387
|
+
// Merge profile/DSL tags with runtime cacheTag() tags, read after
|
|
388
|
+
// awaiting so post-await cacheTag() calls are included. Normalize
|
|
389
|
+
// (drops empty profile tags, matching the invalidate path) + dedupe.
|
|
390
|
+
const freshTags = [
|
|
391
|
+
...new Set(
|
|
392
|
+
normalizeTags([...(profile.tags ?? []), ...scoped.tags]),
|
|
393
|
+
),
|
|
394
|
+
];
|
|
395
|
+
recordRequestTags(freshTags, requestCtx);
|
|
236
396
|
const serialized = await serializeResult(freshResult);
|
|
237
397
|
if (serialized !== null) {
|
|
398
|
+
const encodedHandles = bgCapture?.data
|
|
399
|
+
? await encodeHandles(bgCapture.data)
|
|
400
|
+
: undefined;
|
|
238
401
|
await store.setItem!(cacheKey, serialized, {
|
|
239
|
-
handles:
|
|
402
|
+
handles: encodedHandles,
|
|
240
403
|
ttl: profile.ttl,
|
|
241
404
|
swr: profile.swr,
|
|
242
|
-
tags:
|
|
405
|
+
tags: freshTags.length > 0 ? freshTags : undefined,
|
|
243
406
|
});
|
|
244
407
|
}
|
|
245
408
|
} catch (bgError) {
|
|
246
409
|
bgStopCapture?.();
|
|
247
|
-
requestCtx
|
|
410
|
+
// Pass requestCtx explicitly: this runs in a detached background
|
|
411
|
+
// task where the ALS context is gone, so onError can only fire if
|
|
412
|
+
// we hand it the context captured up front.
|
|
413
|
+
reportCacheError(
|
|
414
|
+
bgError,
|
|
415
|
+
"stale-revalidation",
|
|
416
|
+
"[use cache] background revalidation failed",
|
|
417
|
+
requestCtx,
|
|
418
|
+
);
|
|
248
419
|
} finally {
|
|
249
420
|
for (const arg of bgTaintedArgs) {
|
|
250
421
|
unstampCacheExec(arg as object);
|
|
251
422
|
}
|
|
252
|
-
if (requestCtx) {
|
|
253
|
-
unstampCacheExec(requestCtx as object);
|
|
254
|
-
}
|
|
255
423
|
// Restore original handle store
|
|
256
424
|
if (originalHandleStore && requestCtx) {
|
|
257
425
|
requestCtx._handleStore = originalHandleStore;
|
|
@@ -259,8 +427,14 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
259
427
|
}
|
|
260
428
|
});
|
|
261
429
|
return result;
|
|
262
|
-
} catch {
|
|
263
|
-
//
|
|
430
|
+
} catch (error) {
|
|
431
|
+
// Stale value is corrupt/partial; report and fall through to a fresh
|
|
432
|
+
// execution, which overwrites the faulty entry under the same key.
|
|
433
|
+
reportCacheError(
|
|
434
|
+
error,
|
|
435
|
+
"cache-corrupt",
|
|
436
|
+
`[use cache] "${id}" stale-hit`,
|
|
437
|
+
);
|
|
264
438
|
}
|
|
265
439
|
}
|
|
266
440
|
|
|
@@ -293,8 +467,10 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
293
467
|
}
|
|
294
468
|
|
|
295
469
|
let result: any;
|
|
470
|
+
let scoped: ReturnType<typeof runWithCacheTagScope>;
|
|
296
471
|
try {
|
|
297
|
-
|
|
472
|
+
scoped = runWithCacheTagScope(() => fn.apply(this, args));
|
|
473
|
+
result = await scoped.result;
|
|
298
474
|
} finally {
|
|
299
475
|
// Decrement ref count; symbol is deleted when it reaches zero
|
|
300
476
|
for (const arg of taintedArgs) {
|
|
@@ -307,17 +483,28 @@ export function registerCachedFunction<T extends (...args: any[]) => any>(
|
|
|
307
483
|
stopCapture?.();
|
|
308
484
|
}
|
|
309
485
|
|
|
486
|
+
// Merge profile/DSL tags with runtime cacheTag() tags. Read scoped.tags
|
|
487
|
+
// after awaiting result so post-await cacheTag() calls are included.
|
|
488
|
+
// Normalize (drops empty profile tags, matching the invalidate path) + dedupe.
|
|
489
|
+
const allTags = [
|
|
490
|
+
...new Set(normalizeTags([...(profile.tags ?? []), ...scoped!.tags])),
|
|
491
|
+
];
|
|
492
|
+
recordRequestTags(allTags, requestCtx);
|
|
493
|
+
|
|
310
494
|
// Serialize and store — fully non-blocking when waitUntil is available.
|
|
311
495
|
// The response does not need to wait for serialization or the store write.
|
|
312
496
|
const cacheWrite = async () => {
|
|
313
497
|
try {
|
|
314
498
|
const serialized = await serializeResult(result);
|
|
315
499
|
if (serialized !== null) {
|
|
500
|
+
const encodedHandles = capture?.data
|
|
501
|
+
? await encodeHandles(capture.data)
|
|
502
|
+
: undefined;
|
|
316
503
|
await store.setItem!(cacheKey, serialized, {
|
|
317
|
-
handles:
|
|
504
|
+
handles: encodedHandles,
|
|
318
505
|
ttl: profile.ttl,
|
|
319
506
|
swr: profile.swr,
|
|
320
|
-
tags:
|
|
507
|
+
tags: allTags.length > 0 ? allTags : undefined,
|
|
321
508
|
});
|
|
322
509
|
}
|
|
323
510
|
} catch (writeError) {
|