@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -3,7 +3,10 @@ import ts from "typescript";
|
|
|
3
3
|
import { extractParamsFromPattern } from "./param-extraction.js";
|
|
4
4
|
import { extractRoutesFromSource } from "./ast-route-extraction.js";
|
|
5
5
|
import { generatePerModuleTypesSource } from "./codegen.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
buildCombinedRouteMapWithSearch,
|
|
8
|
+
createScanMemo,
|
|
9
|
+
} from "./include-resolution.js";
|
|
7
10
|
import type { ScanFilter } from "./scan-filter.js";
|
|
8
11
|
import { findTsFiles } from "./scan-filter.js";
|
|
9
12
|
|
|
@@ -78,10 +81,20 @@ export function writePerModuleRouteTypesForFile(filePath: string): void {
|
|
|
78
81
|
if (varNames.length > 0) {
|
|
79
82
|
// Follow includes recursively via the combined route map builder.
|
|
80
83
|
// The visited set in buildCombinedRouteMapWithSearch prevents infinite loops.
|
|
84
|
+
// Share one per-file scan memo across all urls() variables so a shared
|
|
85
|
+
// include target is read+parsed once for this file, not once per variable.
|
|
81
86
|
routes = [];
|
|
87
|
+
const memo = createScanMemo();
|
|
82
88
|
for (const varName of varNames) {
|
|
83
89
|
const { routes: routeMap, searchSchemas } =
|
|
84
|
-
buildCombinedRouteMapWithSearch(
|
|
90
|
+
buildCombinedRouteMapWithSearch(
|
|
91
|
+
filePath,
|
|
92
|
+
varName,
|
|
93
|
+
undefined,
|
|
94
|
+
undefined,
|
|
95
|
+
undefined,
|
|
96
|
+
memo,
|
|
97
|
+
);
|
|
85
98
|
for (const [name, pattern] of Object.entries(routeMap)) {
|
|
86
99
|
const params = extractParamsFromPattern(pattern);
|
|
87
100
|
routes.push({
|
|
@@ -54,8 +54,33 @@ function isRoutableSourceFile(name: string): boolean {
|
|
|
54
54
|
);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
function
|
|
57
|
+
function isExcludedScanDir(name: string): boolean {
|
|
58
|
+
return (
|
|
59
|
+
name === "node_modules" ||
|
|
60
|
+
name === "dist" ||
|
|
61
|
+
name === "coverage" ||
|
|
62
|
+
name === "__tests__" ||
|
|
63
|
+
name === "__mocks__" ||
|
|
64
|
+
name.startsWith(".")
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Recursively collect source files whose code contains `pattern` (a comment- or
|
|
70
|
+
* string-only mention is ignored via firstCodeMatchIndex). Shared by createRouter
|
|
71
|
+
* and createHostRouter discovery, which differ only in the call pattern and
|
|
72
|
+
* `stopAtMatchDir`: createRouter treats a directory containing a match as a router
|
|
73
|
+
* root and stops descending it; createHostRouter descends the whole tree (the host
|
|
74
|
+
* entry sits above the sub-app router roots).
|
|
75
|
+
*
|
|
76
|
+
* `pattern` is the non-global tester (no lastIndex state); `patternG` is its global
|
|
77
|
+
* twin for the code-region scan.
|
|
78
|
+
*/
|
|
79
|
+
function findCallSiteFilesRecursive(
|
|
58
80
|
dir: string,
|
|
81
|
+
pattern: RegExp,
|
|
82
|
+
patternG: RegExp,
|
|
83
|
+
stopAtMatchDir: boolean,
|
|
59
84
|
filter: ScanFilter | undefined,
|
|
60
85
|
results: string[],
|
|
61
86
|
): void {
|
|
@@ -70,21 +95,12 @@ function findRouterFilesRecursive(
|
|
|
70
95
|
}
|
|
71
96
|
|
|
72
97
|
const childDirs: string[] = [];
|
|
73
|
-
const
|
|
98
|
+
const matchesInDir: string[] = [];
|
|
74
99
|
|
|
75
100
|
for (const entry of entries) {
|
|
76
101
|
const fullPath = join(dir, entry.name);
|
|
77
102
|
if (entry.isDirectory()) {
|
|
78
|
-
if (
|
|
79
|
-
entry.name === "node_modules" ||
|
|
80
|
-
entry.name === "dist" ||
|
|
81
|
-
entry.name === "coverage" ||
|
|
82
|
-
entry.name === "__tests__" ||
|
|
83
|
-
entry.name === "__mocks__" ||
|
|
84
|
-
entry.name.startsWith(".")
|
|
85
|
-
)
|
|
86
|
-
continue;
|
|
87
|
-
childDirs.push(fullPath);
|
|
103
|
+
if (!isExcludedScanDir(entry.name)) childDirs.push(fullPath);
|
|
88
104
|
continue;
|
|
89
105
|
}
|
|
90
106
|
|
|
@@ -100,26 +116,29 @@ function findRouterFilesRecursive(
|
|
|
100
116
|
// so a mention inside a comment or string is not mistaken for a real
|
|
101
117
|
// router file (which previously triggered a spurious "Multiple routers
|
|
102
118
|
// found" error).
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0
|
|
106
|
-
) {
|
|
107
|
-
routerFilesInDir.push(fullPath);
|
|
119
|
+
if (pattern.test(source) && firstCodeMatchIndex(source, patternG) >= 0) {
|
|
120
|
+
matchesInDir.push(fullPath);
|
|
108
121
|
}
|
|
109
122
|
} catch {
|
|
110
123
|
continue;
|
|
111
124
|
}
|
|
112
125
|
}
|
|
113
126
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
127
|
+
results.push(...matchesInDir);
|
|
128
|
+
|
|
129
|
+
// createRouter (stopAtMatchDir): a directory that contains a match is a router
|
|
130
|
+
// root, so deeper directories are skipped. createHostRouter: always descend.
|
|
131
|
+
if (!stopAtMatchDir || matchesInDir.length === 0) {
|
|
132
|
+
for (const childDir of childDirs) {
|
|
133
|
+
findCallSiteFilesRecursive(
|
|
134
|
+
childDir,
|
|
135
|
+
pattern,
|
|
136
|
+
patternG,
|
|
137
|
+
stopAtMatchDir,
|
|
138
|
+
filter,
|
|
139
|
+
results,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
123
142
|
}
|
|
124
143
|
}
|
|
125
144
|
|
|
@@ -322,12 +341,6 @@ export function extractBasenameFromRouter(code: string): string | undefined {
|
|
|
322
341
|
return result;
|
|
323
342
|
}
|
|
324
343
|
|
|
325
|
-
/** @deprecated Use extractUrlsFromRouter instead */
|
|
326
|
-
export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
327
|
-
const result = extractUrlsFromRouter(code);
|
|
328
|
-
return result?.kind === "variable" ? result.name : null;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
344
|
/** Apply a basename prefix to all route patterns in a result set. */
|
|
332
345
|
function applyBasenameToRoutes(
|
|
333
346
|
result: {
|
|
@@ -558,7 +571,38 @@ export function detectUnresolvableIncludesForUrlsFile(
|
|
|
558
571
|
*/
|
|
559
572
|
export function findRouterFiles(root: string, filter?: ScanFilter): string[] {
|
|
560
573
|
const result: string[] = [];
|
|
561
|
-
|
|
574
|
+
findCallSiteFilesRecursive(
|
|
575
|
+
root,
|
|
576
|
+
ROUTER_CALL_PATTERN,
|
|
577
|
+
ROUTER_CALL_PATTERN_G,
|
|
578
|
+
true,
|
|
579
|
+
filter,
|
|
580
|
+
result,
|
|
581
|
+
);
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
const HOST_ROUTER_CALL_PATTERN = /\bcreateHostRouter\s*[<(]/;
|
|
586
|
+
const HOST_ROUTER_CALL_PATTERN_G = /\bcreateHostRouter\s*[<(]/g;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Scan for files containing createHostRouter() and return their paths. Unlike
|
|
590
|
+
* findRouterFiles, this does NOT stop at the first router-root directory -- a host
|
|
591
|
+
* entry typically sits above the sub-app router roots, so the whole tree is scanned.
|
|
592
|
+
*/
|
|
593
|
+
export function findHostRouterFiles(
|
|
594
|
+
root: string,
|
|
595
|
+
filter?: ScanFilter,
|
|
596
|
+
): string[] {
|
|
597
|
+
const result: string[] = [];
|
|
598
|
+
findCallSiteFilesRecursive(
|
|
599
|
+
root,
|
|
600
|
+
HOST_ROUTER_CALL_PATTERN,
|
|
601
|
+
HOST_ROUTER_CALL_PATTERN_G,
|
|
602
|
+
false,
|
|
603
|
+
filter,
|
|
604
|
+
result,
|
|
605
|
+
);
|
|
562
606
|
return result;
|
|
563
607
|
}
|
|
564
608
|
|
|
@@ -617,9 +661,6 @@ export function writeCombinedRouteTypes(
|
|
|
617
661
|
? readFileSync(outPath, "utf-8")
|
|
618
662
|
: null;
|
|
619
663
|
|
|
620
|
-
// When the static parser can't extract routes (e.g. callback-style urls()),
|
|
621
|
-
// write an empty placeholder so the build-time transform's injected import
|
|
622
|
-
// resolves. Runtime discovery will overwrite this with the real routes.
|
|
623
664
|
if (Object.keys(result.routes).length === 0) {
|
|
624
665
|
if (!existing) {
|
|
625
666
|
const emptySource = generateRouteTypesSource({});
|
|
@@ -635,11 +676,6 @@ export function writeCombinedRouteTypes(
|
|
|
635
676
|
hasSearchSchemas ? result.searchSchemas : undefined,
|
|
636
677
|
);
|
|
637
678
|
if (existing !== source) {
|
|
638
|
-
// On initial dev startup, don't overwrite a file from runtime discovery
|
|
639
|
-
// (which has all dynamic routes) with a smaller set from the static
|
|
640
|
-
// parser. The static parser can't see routes generated by Array.from()
|
|
641
|
-
// or other dynamic code. During HMR (file watcher), always write so
|
|
642
|
-
// newly added routes appear immediately.
|
|
643
679
|
if (opts?.preserveIfLarger && existing) {
|
|
644
680
|
const existingCount = countPublicRouteEntries(existing);
|
|
645
681
|
const newCount = Object.keys(result.routes).filter(
|
|
@@ -13,12 +13,19 @@
|
|
|
13
13
|
// Memory: O(1) for the boolean check; O(#matches) for the index list. No
|
|
14
14
|
// stripped copy and no per-char array are ever materialized.
|
|
15
15
|
//
|
|
16
|
-
// Pragmatic scanner, not a full tokenizer: regex literals
|
|
17
|
-
// (
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
16
|
+
// Pragmatic scanner, not a full tokenizer: regex literals ARE coarsely skipped
|
|
17
|
+
// (see below) and template interpolations are treated as opaque string content.
|
|
18
|
+
// One intentional consequence: a token whose match would only complete by
|
|
19
|
+
// treating an interleaved comment as whitespace (e.g. `createRouter /* x */ (`)
|
|
20
|
+
// is not detected — real calls never interleave a comment between the callee
|
|
21
|
+
// and its arguments.
|
|
22
|
+
//
|
|
23
|
+
// Regex literals are skipped because a literal containing a quote or comment
|
|
24
|
+
// char (e.g. `const re = /it's a "x"/g;`) would otherwise open a phantom string
|
|
25
|
+
// at the inner quote and swallow the following REAL code — dropping a router
|
|
26
|
+
// file from discovery. We only treat a `/` as a regex start when it is in
|
|
27
|
+
// "regex position" (the previous significant code char is not value-producing),
|
|
28
|
+
// so genuine division (`a / b`) is left untouched.
|
|
22
29
|
|
|
23
30
|
// JS line terminators end a `//` comment: LF, CR, LS (U+2028), PS (U+2029).
|
|
24
31
|
function isLineTerminator(ch: string): boolean {
|
|
@@ -27,6 +34,56 @@ function isLineTerminator(ch: string): boolean {
|
|
|
27
34
|
return c === 10 || c === 13 || c === 0x2028 || c === 0x2029;
|
|
28
35
|
}
|
|
29
36
|
|
|
37
|
+
// Identifier-position keywords after which a `/` begins a regex literal, not
|
|
38
|
+
// division: the keyword cannot be the left operand of a division, so `return
|
|
39
|
+
// /re/`, `typeof /re/`, `case /re/`, etc. are regexes. After any OTHER identifier
|
|
40
|
+
// or number (a value), `/` is division.
|
|
41
|
+
const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
42
|
+
"return",
|
|
43
|
+
"typeof",
|
|
44
|
+
"instanceof",
|
|
45
|
+
"in",
|
|
46
|
+
"of",
|
|
47
|
+
"new",
|
|
48
|
+
"delete",
|
|
49
|
+
"void",
|
|
50
|
+
"do",
|
|
51
|
+
"else",
|
|
52
|
+
"yield",
|
|
53
|
+
"await",
|
|
54
|
+
"case",
|
|
55
|
+
"throw",
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
// A `/` at `slashPos` is a regex-literal start (not division) when the previous
|
|
59
|
+
// significant code char cannot end an expression. A closing `)`/`]`/`}` and an
|
|
60
|
+
// identifier/digit/`$`/`_` are value-producing (division); everything else
|
|
61
|
+
// (operators, `(`, `,`, `=`, `:`, `{`, `;`, `<`, `>`, ...) and the start-of-file
|
|
62
|
+
// put `/` in regex position. The one subtlety: an identifier that is actually a
|
|
63
|
+
// regex-preceding KEYWORD (`return /re/`) ends in a word char, so the
|
|
64
|
+
// previous-char-only test misread it as division and then let the regex body's
|
|
65
|
+
// inner quotes open a phantom string — dropping a later real `createRouter()`.
|
|
66
|
+
// So when the previous char is a word char we walk back over any whitespace and
|
|
67
|
+
// the identifier and treat `/` as a regex iff that identifier is such a keyword.
|
|
68
|
+
// `}` stays value-producing to avoid swallowing an object/block followed by
|
|
69
|
+
// division; the cost is only that a regex right after a block isn't skipped.
|
|
70
|
+
function isRegexPositionAt(
|
|
71
|
+
code: string,
|
|
72
|
+
slashPos: number,
|
|
73
|
+
prevChar: string | undefined,
|
|
74
|
+
): boolean {
|
|
75
|
+
if (prevChar === undefined) return true; // start of file
|
|
76
|
+
if (prevChar === ")" || prevChar === "]" || prevChar === "}") return false;
|
|
77
|
+
if (!/[\w$]/.test(prevChar)) return true; // operator / `(` / `,` / `=` / ...
|
|
78
|
+
// Previous char ends an identifier or number: regex only after a keyword that
|
|
79
|
+
// expects an expression. Walk back over whitespace + the identifier run.
|
|
80
|
+
let k = slashPos - 1;
|
|
81
|
+
while (k >= 0 && /\s/.test(code[k])) k--;
|
|
82
|
+
const wordEnd = k + 1;
|
|
83
|
+
while (k >= 0 && /[\w$]/.test(code[k])) k--;
|
|
84
|
+
return REGEX_PRECEDING_KEYWORDS.has(code.slice(k + 1, wordEnd));
|
|
85
|
+
}
|
|
86
|
+
|
|
30
87
|
/**
|
|
31
88
|
* Build a classifier that answers "is offset `q` in code (not a comment or
|
|
32
89
|
* string)?" for STRICTLY INCREASING `q`. The internal cursor only moves forward,
|
|
@@ -37,6 +94,9 @@ function makeCodeClassifier(code: string): (q: number) => boolean {
|
|
|
37
94
|
let i = 0; // forward cursor: everything before `i` is already classified
|
|
38
95
|
let skipStart = -1; // last detected comment/string region (cache)
|
|
39
96
|
let skipEnd = -1;
|
|
97
|
+
// Last significant code char, used to disambiguate `/` (regex vs division).
|
|
98
|
+
// Comments are transparent (don't update it); strings/regex are value-producing.
|
|
99
|
+
let lastSig: string | undefined;
|
|
40
100
|
|
|
41
101
|
return (q: number): boolean => {
|
|
42
102
|
if (q >= skipStart && q < skipEnd) return false; // q in the cached region
|
|
@@ -44,14 +104,17 @@ function makeCodeClassifier(code: string): (q: number) => boolean {
|
|
|
44
104
|
const c = code[i];
|
|
45
105
|
const d = i + 1 < n ? code[i + 1] : "";
|
|
46
106
|
let end = -1;
|
|
107
|
+
let transparent = false; // comment: skipped but does not set lastSig
|
|
47
108
|
if (c === "/" && d === "/") {
|
|
48
109
|
let j = i + 2;
|
|
49
110
|
while (j < n && !isLineTerminator(code[j])) j++;
|
|
50
111
|
end = j;
|
|
112
|
+
transparent = true;
|
|
51
113
|
} else if (c === "/" && d === "*") {
|
|
52
114
|
let j = i + 2;
|
|
53
115
|
while (j < n && !(code[j] === "*" && code[j + 1] === "/")) j++;
|
|
54
116
|
end = Math.min(n, j + 2);
|
|
117
|
+
transparent = true;
|
|
55
118
|
} else if (c === '"' || c === "'" || c === "`") {
|
|
56
119
|
let j = i + 1;
|
|
57
120
|
while (j < n) {
|
|
@@ -66,16 +129,51 @@ function makeCodeClassifier(code: string): (q: number) => boolean {
|
|
|
66
129
|
j++;
|
|
67
130
|
}
|
|
68
131
|
end = j;
|
|
132
|
+
} else if (
|
|
133
|
+
c === "/" &&
|
|
134
|
+
d !== "/" &&
|
|
135
|
+
d !== "*" &&
|
|
136
|
+
isRegexPositionAt(code, i, lastSig)
|
|
137
|
+
) {
|
|
138
|
+
// Coarse regex-literal skip. A regex literal cannot span a raw newline;
|
|
139
|
+
// `/` inside a `[...]` character class is literal (not a terminator).
|
|
140
|
+
// Bail (treat the `/` as a normal char) if no closing `/` on the line
|
|
141
|
+
// so a stray division-looking `/` never swallows the rest of the line.
|
|
142
|
+
let j = i + 1;
|
|
143
|
+
let inClass = false;
|
|
144
|
+
let closed = false;
|
|
145
|
+
while (j < n && !isLineTerminator(code[j])) {
|
|
146
|
+
const r = code[j];
|
|
147
|
+
if (r === "\\") {
|
|
148
|
+
j += 2;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (r === "[") inClass = true;
|
|
152
|
+
else if (r === "]") inClass = false;
|
|
153
|
+
else if (r === "/" && !inClass) {
|
|
154
|
+
j++;
|
|
155
|
+
closed = true;
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
j++;
|
|
159
|
+
}
|
|
160
|
+
if (closed) {
|
|
161
|
+
while (j < n && /[a-z]/.test(code[j])) j++; // flags
|
|
162
|
+
end = j;
|
|
163
|
+
}
|
|
69
164
|
}
|
|
70
165
|
if (end >= 0) {
|
|
71
|
-
// Comment/string region [i, end). `q >= i` here (loop condition).
|
|
166
|
+
// Comment/string/regex region [i, end). `q >= i` here (loop condition).
|
|
72
167
|
if (q < end) {
|
|
73
168
|
skipStart = i;
|
|
74
169
|
skipEnd = end;
|
|
75
170
|
return false;
|
|
76
171
|
}
|
|
77
172
|
i = end;
|
|
173
|
+
// Strings and regex literals are value-producing; comments are not.
|
|
174
|
+
if (!transparent) lastSig = "x";
|
|
78
175
|
} else {
|
|
176
|
+
if (!/\s/.test(c)) lastSig = c;
|
|
79
177
|
i++;
|
|
80
178
|
}
|
|
81
179
|
}
|
|
@@ -139,7 +139,10 @@ export async function discoverAndWriteRouteTypes(
|
|
|
139
139
|
for (const [id, router] of registry) {
|
|
140
140
|
if (!router.urlpatterns) continue;
|
|
141
141
|
|
|
142
|
-
const manifest = generateManifest(
|
|
142
|
+
const manifest = await generateManifest(
|
|
143
|
+
router.urlpatterns,
|
|
144
|
+
routerMountIndex,
|
|
145
|
+
);
|
|
143
146
|
routerMountIndex++;
|
|
144
147
|
|
|
145
148
|
// Filter out auto-generated route names that the runtime creates for
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache error reporting.
|
|
3
|
+
*
|
|
4
|
+
* Caches are best-effort: a read failure degrades to a miss (render fresh) and a
|
|
5
|
+
* write failure degrades to a no-op - they MUST NOT throw up and fail the
|
|
6
|
+
* request. But the failure must still be LOUD: it is logged to the console (so
|
|
7
|
+
* it is visible even in a background waitUntil task or when no hook is wired)
|
|
8
|
+
* AND routed through the router's onError callback (via the request context's
|
|
9
|
+
* deduped _reportBackgroundError) so consumers can observe cache degradation in
|
|
10
|
+
* their own telemetry.
|
|
11
|
+
*
|
|
12
|
+
* The one deliberate exception is the invalidation WRITE verb (updateTag ->
|
|
13
|
+
* store.invalidateTags): a failed durable marker write is rejected so an awaited
|
|
14
|
+
* updateTag() surfaces it (read-your-own-writes honesty). That is not a data
|
|
15
|
+
* read/write and does not go through this helper's swallow-and-degrade path.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { _getRequestContext } from "../server/request-context.js";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Minimal shape of a request context for error reporting. Passed explicitly by
|
|
22
|
+
* background tasks (waitUntil) where the ALS context is already gone, so the
|
|
23
|
+
* error can still reach the router's onError. Structural to avoid importing the
|
|
24
|
+
* full RequestContext type (request-context.ts imports CacheErrorCategory from
|
|
25
|
+
* here - a mutual type-only reference).
|
|
26
|
+
*/
|
|
27
|
+
export interface CacheErrorReporter {
|
|
28
|
+
_reportBackgroundError?: (
|
|
29
|
+
error: unknown,
|
|
30
|
+
category: CacheErrorCategory,
|
|
31
|
+
) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type CacheErrorCategory =
|
|
35
|
+
/** A read failed (transient infra: KV/Cache API error). Degrade to a miss. */
|
|
36
|
+
| "cache-read"
|
|
37
|
+
/** A write failed. Degrade to a no-op (entry simply not cached). */
|
|
38
|
+
| "cache-write"
|
|
39
|
+
/** A delete/eviction failed. Best-effort. */
|
|
40
|
+
| "cache-delete"
|
|
41
|
+
/**
|
|
42
|
+
* A STORED entry could not be parsed/deserialized (partial KV read, truncated
|
|
43
|
+
* Cache API body, malformed envelope/RSC payload). The entry is faulty and is
|
|
44
|
+
* evicted so subsequent reads do not keep failing on it. Distinct from
|
|
45
|
+
* cache-read so consumers can tell corruption from a transient outage.
|
|
46
|
+
*/
|
|
47
|
+
| "cache-corrupt"
|
|
48
|
+
/** A tag-invalidation side effect failed (e.g. the eager CDN purge hook). */
|
|
49
|
+
| "cache-invalidate"
|
|
50
|
+
/**
|
|
51
|
+
* A background stale-while-revalidate refresh failed (the `"use cache"`
|
|
52
|
+
* read-through path). The stale value was already served; the refresh that
|
|
53
|
+
* would have replaced it errored.
|
|
54
|
+
*/
|
|
55
|
+
| "stale-revalidation";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Report a non-fatal cache error loudly without failing the request: always logs
|
|
59
|
+
* (label + error) and, when a request context is available, routes the error
|
|
60
|
+
* through the router's onError callback. Never throws.
|
|
61
|
+
*
|
|
62
|
+
* `ctx` is for callers running in a detached background task (waitUntil), where
|
|
63
|
+
* the ALS request context is already gone (so `_getRequestContext()` is null):
|
|
64
|
+
* they capture the context up front and pass it here so onError still fires.
|
|
65
|
+
* Foreground callers omit it and fall back to the ALS context.
|
|
66
|
+
*/
|
|
67
|
+
export function reportCacheError(
|
|
68
|
+
error: unknown,
|
|
69
|
+
category: CacheErrorCategory,
|
|
70
|
+
label: string,
|
|
71
|
+
ctx?: CacheErrorReporter,
|
|
72
|
+
): void {
|
|
73
|
+
console.error(`${label}:`, error);
|
|
74
|
+
try {
|
|
75
|
+
const target = ctx ?? _getRequestContext();
|
|
76
|
+
target?._reportBackgroundError?.(error, category);
|
|
77
|
+
} catch {
|
|
78
|
+
// Reporting must never itself break the cache path.
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run a best-effort async cache task (typically scheduled via waitUntil), catching
|
|
84
|
+
* any rejection and routing it through reportCacheError so background cache work
|
|
85
|
+
* (non-blocking L1 writes, KV persistence, L1 promotion) reports failures via
|
|
86
|
+
* onError instead of throwing or silently swallowing. Never rejects.
|
|
87
|
+
*
|
|
88
|
+
* Pass `ctx` when the task runs detached (the ALS context is gone) and the
|
|
89
|
+
* failure should still reach onError; omit it to fall back to the ALS context.
|
|
90
|
+
*
|
|
91
|
+
* @example this.waitUntil(() => reportingAsync(() => cache.put(req, res), "cache-write", "[CFCacheStore] L1 write"))
|
|
92
|
+
*/
|
|
93
|
+
export async function reportingAsync(
|
|
94
|
+
task: () => Promise<unknown>,
|
|
95
|
+
category: CacheErrorCategory,
|
|
96
|
+
label: string,
|
|
97
|
+
ctx?: CacheErrorReporter,
|
|
98
|
+
): Promise<void> {
|
|
99
|
+
try {
|
|
100
|
+
await task();
|
|
101
|
+
} catch (error) {
|
|
102
|
+
reportCacheError(error, category, label, ctx);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -6,24 +6,45 @@
|
|
|
6
6
|
* document-cache, and loader-cache.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { encodeKV } from "../encode-kv.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Reserved URL query params that the router owns and must never key the cache
|
|
13
|
+
* on. `_rsc*` is the router's internal navigation/action/loader prefix (matched
|
|
14
|
+
* by prefix). `__no_cache` is the single `__`-prefixed param the router reads
|
|
15
|
+
* (handler.ts / testing dispatch.ts use it to bypass the store); it and the
|
|
16
|
+
* other router-internal `__`-prefixed request params are matched by an EXACT
|
|
17
|
+
* allowlist, not a blanket `__` prefix. A blanket `__` filter would silently
|
|
18
|
+
* collapse consumer params like `__variant=a` vs `__variant=b` onto one cache
|
|
19
|
+
* slot; an allowlist keeps the router's own params out of the key while leaving
|
|
20
|
+
* consumer `__` params intact.
|
|
21
|
+
*/
|
|
22
|
+
const RESERVED_SEARCH_PARAMS = new Set([
|
|
23
|
+
"__no_cache",
|
|
24
|
+
"__rsc",
|
|
25
|
+
"__html",
|
|
26
|
+
"__debug_manifest",
|
|
27
|
+
"__prerender_collect",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function isReservedSearchParam(key: string): boolean {
|
|
31
|
+
return key.startsWith("_rsc") || RESERVED_SEARCH_PARAMS.has(key);
|
|
32
|
+
}
|
|
33
|
+
|
|
9
34
|
/**
|
|
10
35
|
* Build a sorted, deterministic query string from URLSearchParams,
|
|
11
|
-
* excluding
|
|
36
|
+
* excluding the router's reserved params (see isReservedSearchParam).
|
|
12
37
|
*
|
|
13
38
|
* Returns empty string when no user-facing params exist.
|
|
14
39
|
*/
|
|
15
40
|
export function sortedSearchString(searchParams: URLSearchParams): string {
|
|
16
41
|
const pairs: [string, string][] = [];
|
|
17
42
|
for (const [k, v] of searchParams) {
|
|
18
|
-
if (!
|
|
43
|
+
if (!isReservedSearchParam(k)) {
|
|
19
44
|
pairs.push([k, v]);
|
|
20
45
|
}
|
|
21
46
|
}
|
|
22
|
-
|
|
23
|
-
pairs.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
24
|
-
return pairs
|
|
25
|
-
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
26
|
-
.join("&");
|
|
47
|
+
return encodeKV(pairs, { sort: true });
|
|
27
48
|
}
|
|
28
49
|
|
|
29
50
|
/**
|
|
@@ -35,10 +56,34 @@ export function sortedRouteParams(
|
|
|
35
56
|
params: Record<string, string> | undefined,
|
|
36
57
|
): string {
|
|
37
58
|
if (!params) return "";
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
59
|
+
return encodeKV(Object.entries(params), { sort: true });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Host-namespaced cache key base: `${host}${pathname}[:params][?search]`.
|
|
64
|
+
*
|
|
65
|
+
* The ONE composition of the host-namespacing rule, shared by the segment tier
|
|
66
|
+
* (cache-scope.ts) and the document tier (document-cache.ts) so the rule cannot
|
|
67
|
+
* drift between them. Host prefixing matters because VercelCacheStore /
|
|
68
|
+
* MemorySegmentCacheStore key by the raw string (only CFCacheStore adds host
|
|
69
|
+
* internally) -- on a single function serving multiple domains an
|
|
70
|
+
* un-namespaced key bleeds tenant A's cached response to tenant B.
|
|
71
|
+
*
|
|
72
|
+
* Output is BYTE-STABLE by contract: changing the composition silently
|
|
73
|
+
* invalidates every persisted cache entry on upgrade. Callers append their own
|
|
74
|
+
* tier-specific suffixes (`:rsc`/`:html`, segment hash) after this base.
|
|
75
|
+
*/
|
|
76
|
+
export function cacheKeyBase(
|
|
77
|
+
host: string,
|
|
78
|
+
pathname: string,
|
|
79
|
+
searchParams?: URLSearchParams,
|
|
80
|
+
params?: Record<string, string>,
|
|
81
|
+
): string {
|
|
82
|
+
const paramStr = sortedRouteParams(params);
|
|
83
|
+
const searchStr = searchParams ? sortedSearchString(searchParams) : "";
|
|
84
|
+
|
|
85
|
+
let key = `${host}${pathname}`;
|
|
86
|
+
if (paramStr) key += `:${paramStr}`;
|
|
87
|
+
if (searchStr) key += `?${searchStr}`;
|
|
88
|
+
return key;
|
|
44
89
|
}
|