@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7
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 +3373 -1176
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- 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 +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 +235 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +77 -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 +70 -5
- package/skills/router-setup/SKILL.md +65 -8
- 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 +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 +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 +186 -100
- 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 +165 -112
- package/src/browser/prefetch/cache.ts +205 -62
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +295 -119
- 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 +114 -24
- 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 -72
- package/src/browser/types.ts +102 -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 +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 +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 +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 +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 +143 -69
- 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 +52 -31
- 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 +268 -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 +40 -24
- package/src/router/match-middleware/cache-lookup.ts +170 -276
- 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 +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +266 -169
- 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/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 -28
- package/src/router/router-interfaces.ts +115 -35
- package/src/router/router-options.ts +172 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +162 -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 +351 -321
- 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 +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +157 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +655 -529
- 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 +122 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +131 -14
- 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 +109 -74
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -115
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +29 -9
- 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 +236 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +224 -52
- 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 +401 -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 +125 -71
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +40 -11
- 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 +64 -170
- 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 +118 -114
- 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/debug-channel.ts +0 -93
- 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
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ReactNode } from "react";
|
|
8
|
-
import { track } from "../server/context";
|
|
9
8
|
import type { EntryData } from "../server/context";
|
|
9
|
+
import { observePhase, PHASES } from "./instrument.js";
|
|
10
10
|
import { contextGet } from "../context-var.js";
|
|
11
11
|
import type {
|
|
12
12
|
ResolvedSegment,
|
|
@@ -19,11 +19,17 @@ import type {
|
|
|
19
19
|
ErrorBoundaryFallbackProps,
|
|
20
20
|
ErrorInfo,
|
|
21
21
|
} from "../types";
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
22
|
+
import { isHandle, collectHandleData, type Handle } from "../handle.js";
|
|
23
|
+
import { withDefer } from "../defer.js";
|
|
24
|
+
import { buildHandleSnapshot } from "../server/handle-store.js";
|
|
25
25
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
26
26
|
import { _getRequestContext } from "../server/request-context.js";
|
|
27
|
+
import {
|
|
28
|
+
isInsideLoaderScope,
|
|
29
|
+
runInsideLoaderBodyScope,
|
|
30
|
+
isInsidePushCallbackScope,
|
|
31
|
+
runInsidePushCallbackScope,
|
|
32
|
+
} from "../server/context.js";
|
|
27
33
|
import { debugLog } from "./logging.js";
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -65,7 +71,9 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
65
71
|
) => ErrorInfo,
|
|
66
72
|
onError?: LoaderErrorCallback,
|
|
67
73
|
): Promise<LoaderDataResult<T>> {
|
|
68
|
-
// Extract
|
|
74
|
+
// Extract the trailing token from segmentId (format: "<shortCode>D<i>.<loaderId>").
|
|
75
|
+
// The token is the loader's $$id (hash#export in prod, pathfrag#export in dev),
|
|
76
|
+
// not a clean display name.
|
|
69
77
|
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
70
78
|
|
|
71
79
|
return Promise.resolve(promise)
|
|
@@ -101,16 +109,40 @@ export function wrapLoaderWithErrorHandling<T>(
|
|
|
101
109
|
};
|
|
102
110
|
}
|
|
103
111
|
|
|
104
|
-
// Render fallback on server
|
|
112
|
+
// Render fallback on server. The user ErrorBoundaryHandler may throw
|
|
113
|
+
// synchronously; if it does we must NOT let that rejection escape — the
|
|
114
|
+
// wrapped LoaderDataResult promise is contracted to never reject (see
|
|
115
|
+
// segment-resolution/fresh.ts `await Promise.all(...wrapped)`), and a
|
|
116
|
+
// rejection here would collapse the whole entry and discard healthy
|
|
117
|
+
// sibling loader data. On a fallback-render throw, fall back to the
|
|
118
|
+
// no-boundary result (fallback: null) so the client throws the ORIGINAL
|
|
119
|
+
// error, and the wrapped promise still resolves to a LoaderDataResult.
|
|
105
120
|
let renderedFallback: ReactNode;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
try {
|
|
122
|
+
if (typeof fallback === "function") {
|
|
123
|
+
// ErrorBoundaryHandler - call with error info
|
|
124
|
+
const props: ErrorBoundaryFallbackProps = {
|
|
125
|
+
error: errorInfo,
|
|
126
|
+
};
|
|
127
|
+
renderedFallback = fallback(props);
|
|
128
|
+
} else {
|
|
129
|
+
renderedFallback = fallback;
|
|
130
|
+
}
|
|
131
|
+
} catch (fallbackError) {
|
|
132
|
+
debugLog("loader", "error boundary fallback render threw", {
|
|
133
|
+
segmentId,
|
|
134
|
+
message: errorInfo.message,
|
|
135
|
+
fallbackError:
|
|
136
|
+
fallbackError instanceof Error
|
|
137
|
+
? fallbackError.message
|
|
138
|
+
: String(fallbackError),
|
|
139
|
+
});
|
|
140
|
+
return {
|
|
141
|
+
__loaderResult: true,
|
|
142
|
+
ok: false,
|
|
109
143
|
error: errorInfo,
|
|
144
|
+
fallback: null,
|
|
110
145
|
};
|
|
111
|
-
renderedFallback = fallback(props);
|
|
112
|
-
} else {
|
|
113
|
-
renderedFallback = fallback;
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
debugLog("loader", "loader error wrapped with boundary fallback", {
|
|
@@ -242,6 +274,16 @@ function createLoaderExecutor<TEnv>(
|
|
|
242
274
|
pendingLoaders.add(loader.$$id);
|
|
243
275
|
|
|
244
276
|
const currentLoaderId = loader.$$id;
|
|
277
|
+
const variables = (ctx as InternalHandlerContext<any, TEnv>)._variables;
|
|
278
|
+
|
|
279
|
+
// Capture whether this loader is being started from a DSL loader scope
|
|
280
|
+
// (runInsideLoaderScope in fresh.ts). Handler-invoked loaders are NOT
|
|
281
|
+
// inside loader scope. This determines whether rendered() is allowed.
|
|
282
|
+
const isDslLoader = isInsideLoaderScope();
|
|
283
|
+
|
|
284
|
+
let renderedResolved = false;
|
|
285
|
+
let renderedPromise: Promise<void> | null = null;
|
|
286
|
+
|
|
245
287
|
// Loader functions are always fresh (never cached), so they get an
|
|
246
288
|
// unguarded get that bypasses non-cacheable read guards. This applies
|
|
247
289
|
// to ALL loaders — DSL and handler-called — because the loader
|
|
@@ -255,26 +297,138 @@ function createLoaderExecutor<TEnv>(
|
|
|
255
297
|
search: (ctx as any).search,
|
|
256
298
|
pathname: ctx.pathname,
|
|
257
299
|
url: ctx.url,
|
|
300
|
+
originalUrl: ctx.originalUrl,
|
|
258
301
|
env: ctx.env,
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
302
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
303
|
+
executionContext: ctx.executionContext,
|
|
304
|
+
get: ((keyOrVar: any) =>
|
|
305
|
+
contextGet(variables, keyOrVar)) as typeof ctx.get,
|
|
306
|
+
use: ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
307
|
+
if (isHandle(item)) {
|
|
308
|
+
if (!renderedResolved) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`ctx.use(handle) in a loader requires "await ctx.rendered()" first. ` +
|
|
311
|
+
`Handle "${item.$$id}" cannot be read until the render tree has settled.`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
315
|
+
if (!reqCtx) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`ctx.use(handle) failed: request context not available.`,
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
const segmentOrder = reqCtx._renderBarrierSegmentOrder ?? [];
|
|
321
|
+
// The complete snapshot is cached at barrier resolution for
|
|
322
|
+
// non-streaming trees, and by rendered() after handleStore.settled for
|
|
323
|
+
// streaming trees (where the eager snapshot would have been incomplete
|
|
324
|
+
// because loading() handlers were still in flight). Either way it is
|
|
325
|
+
// present by the time a loader reads a handle; the fresh build is only
|
|
326
|
+
// a defensive fallback.
|
|
327
|
+
const snapshot =
|
|
328
|
+
reqCtx._renderBarrierHandleSnapshot ??
|
|
329
|
+
buildHandleSnapshot(reqCtx._handleStore, segmentOrder);
|
|
330
|
+
return collectHandleData(item, snapshot, segmentOrder);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Loader case
|
|
334
|
+
return useLoader(item as LoaderDefinition<any, any>, currentLoaderId);
|
|
335
|
+
}) as LoaderContext["use"],
|
|
266
336
|
method: "GET",
|
|
267
337
|
body: undefined,
|
|
268
338
|
reverse: ctx.reverse as LoaderContext["reverse"],
|
|
339
|
+
rendered: (): Promise<void> => {
|
|
340
|
+
// Guard: only DSL loaders may use rendered()
|
|
341
|
+
if (!isDslLoader) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
344
|
+
`Handler-invoked loaders (ctx.use(Loader) inside a handler) cannot use rendered().`,
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
349
|
+
|
|
350
|
+
if (renderedPromise) return renderedPromise;
|
|
351
|
+
|
|
352
|
+
if (!reqCtx) {
|
|
353
|
+
throw new Error(
|
|
354
|
+
`ctx.rendered() failed: request context not available.`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Bidirectional deadlock check: if a handler already started
|
|
359
|
+
// awaiting this loader, calling rendered() would deadlock. This is the
|
|
360
|
+
// real cycle guard (it holds for both streaming and non-streaming): the
|
|
361
|
+
// handler blocks segment resolution, which blocks the barrier, which
|
|
362
|
+
// blocks this loader.
|
|
363
|
+
if (reqCtx._handlerLoaderDeps?.has(currentLoaderId)) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`Deadlock: loader "${currentLoaderId}" called ctx.rendered() but a handler ` +
|
|
366
|
+
`is already awaiting this loader via ctx.use(). The handler blocks ` +
|
|
367
|
+
`segment resolution, which blocks the barrier, which blocks this loader. ` +
|
|
368
|
+
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Register this loader as waiting for the barrier so that
|
|
373
|
+
// setupLoaderAccess can detect deadlocks when a handler
|
|
374
|
+
// tries to await the same loader via ctx.use().
|
|
375
|
+
if (!reqCtx._renderBarrierWaiters) {
|
|
376
|
+
reqCtx._renderBarrierWaiters = new Set();
|
|
377
|
+
}
|
|
378
|
+
reqCtx._renderBarrierWaiters.add(currentLoaderId);
|
|
379
|
+
|
|
380
|
+
// Streaming trees (loading()): the barrier resolves once the segment
|
|
381
|
+
// tree is resolved, but loading() handlers stream behind Suspense and
|
|
382
|
+
// their handle pushes are still in flight then. Their async execution
|
|
383
|
+
// IS tracked in the handle store (trackHandler -> store.track), so after
|
|
384
|
+
// the barrier we seal (no further handlers register once the tree is
|
|
385
|
+
// resolved) and wait for settled — every tracked handler, streaming
|
|
386
|
+
// included, has finished pushing. The loader's own segment streams in
|
|
387
|
+
// after, so this does not block the shell; the deadlock guard above
|
|
388
|
+
// keeps a handler from depending on this loader.
|
|
389
|
+
const streaming = reqCtx._treeHasStreaming === true;
|
|
390
|
+
renderedPromise = reqCtx._renderBarrier.then(async () => {
|
|
391
|
+
if (streaming) {
|
|
392
|
+
reqCtx._handleStore.seal();
|
|
393
|
+
await reqCtx._handleStore.settled;
|
|
394
|
+
// The eager snapshot was intentionally left unbuilt for streaming
|
|
395
|
+
// (it would have been incomplete). Build the complete one once, now
|
|
396
|
+
// that the store has settled, so every ctx.use(handle) reads the
|
|
397
|
+
// cached snapshot instead of rebuilding it per call.
|
|
398
|
+
reqCtx._renderBarrierHandleSnapshot ??= buildHandleSnapshot(
|
|
399
|
+
reqCtx._handleStore,
|
|
400
|
+
reqCtx._renderBarrierSegmentOrder ?? [],
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
renderedResolved = true;
|
|
404
|
+
});
|
|
405
|
+
return renderedPromise;
|
|
406
|
+
},
|
|
269
407
|
};
|
|
270
408
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
409
|
+
// Meter this loader once via observePhase (loader:<id> perf metric +
|
|
410
|
+
// rango.loader span); loaderFn runs inside the span callback so its KV/D1/
|
|
411
|
+
// fetch spans nest under it. This is one of the observePhase loader funnels —
|
|
412
|
+
// see instrument.ts for the single-metering contract.
|
|
413
|
+
//
|
|
414
|
+
// Run the loader body inside loader scope so request-scoped reads
|
|
415
|
+
// (cookies()/headers() and non-cacheable ctx.get) are exempt from the
|
|
416
|
+
// cache-purity guards: loaders always run fresh, so their reads never leak
|
|
417
|
+
// into a cached segment. DSL loaders are already wrapped by fresh.ts; this
|
|
418
|
+
// also covers handler-invoked loaders (ctx.use(Loader) from a handler),
|
|
419
|
+
// which otherwise execute in the caller's cache scope and would wrongly
|
|
420
|
+
// throw. rendered() gating uses the captured isDslLoader (above), so this
|
|
421
|
+
// does not grant rendered() to handler-invoked loaders. Uses a body-only
|
|
422
|
+
// scope, so isInsideLoaderScope() / barrier / deadlock gating is unchanged.
|
|
423
|
+
const promise = observePhase(PHASES.loader(loader.$$id), () =>
|
|
424
|
+
Promise.resolve(
|
|
425
|
+
runInsideLoaderBodyScope(() =>
|
|
426
|
+
loaderFn(loaderCtx as LoaderContext<any, TEnv>),
|
|
427
|
+
),
|
|
428
|
+
).finally(() => {
|
|
429
|
+
pendingLoaders.delete(loader.$$id);
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
278
432
|
|
|
279
433
|
loaderPromises.set(loader.$$id, promise);
|
|
280
434
|
return promise;
|
|
@@ -296,12 +450,13 @@ export function setupLoaderAccess<TEnv>(
|
|
|
296
450
|
ctx: HandlerContext<any, TEnv>,
|
|
297
451
|
loaderPromises: Map<string, Promise<any>>,
|
|
298
452
|
): void {
|
|
299
|
-
// Eagerly capture the HandleStore at setup time
|
|
300
|
-
// In workerd/Cloudflare, dynamic imports and
|
|
301
|
-
// can disrupt AsyncLocalStorage, causing
|
|
302
|
-
// undefined when handlers later call
|
|
303
|
-
//
|
|
304
|
-
const
|
|
453
|
+
// Eagerly capture the request context and HandleStore at setup time
|
|
454
|
+
// (before pipeline async ops). In workerd/Cloudflare, dynamic imports and
|
|
455
|
+
// fetch() in the match pipeline can disrupt AsyncLocalStorage, causing
|
|
456
|
+
// getRequestContext() to return undefined when handlers later call
|
|
457
|
+
// ctx.use(handle). Capturing early ensures references survive ALS disruption.
|
|
458
|
+
const reqCtxRef = _getRequestContext();
|
|
459
|
+
const handleStoreRef = reqCtxRef?._handleStore;
|
|
305
460
|
|
|
306
461
|
const useLoader = createLoaderExecutor(ctx, loaderPromises);
|
|
307
462
|
|
|
@@ -319,21 +474,72 @@ export function setupLoaderAccess<TEnv>(
|
|
|
319
474
|
);
|
|
320
475
|
}
|
|
321
476
|
|
|
322
|
-
return (
|
|
323
|
-
dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
477
|
+
return withDefer(
|
|
478
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
479
|
+
if (!store) return;
|
|
480
|
+
|
|
481
|
+
if (typeof dataOrFn === "function") {
|
|
482
|
+
// Run the callback inside the push-callback scope so ctx.use(loader)
|
|
483
|
+
// calls it makes — including after its own awaits, for an async
|
|
484
|
+
// callback — are not registered as handler-to-loader deps and do not
|
|
485
|
+
// trip the deadlock guard. A pushed promise value is not tracked by
|
|
486
|
+
// handleStore.settled and does not block segment resolution, so it
|
|
487
|
+
// cannot form a rendered() deadlock. The ALS scope (not a plain
|
|
488
|
+
// boolean) is what survives the callback's awaits.
|
|
489
|
+
const result = runInsidePushCallbackScope(() =>
|
|
490
|
+
(dataOrFn as () => Promise<unknown>)(),
|
|
491
|
+
);
|
|
492
|
+
store.push(handle.$$id, segmentId, result);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
store.push(handle.$$id, segmentId, dataOrFn);
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
}
|
|
331
500
|
|
|
332
|
-
|
|
333
|
-
|
|
501
|
+
// Deadlock guard and handler-to-loader dependency tracking.
|
|
502
|
+
// Skip when inside a DSL loader scope (resolveLoaderData also calls
|
|
503
|
+
// ctx.use() but that's DSL-to-DSL, not handler-to-loader) or when
|
|
504
|
+
// inside a handle push callback (push callbacks don't block segment
|
|
505
|
+
// resolution so they can't cause rendered() deadlocks). The push-callback
|
|
506
|
+
// check is an ALS scope so it also exempts an ASYNC callback's continuation
|
|
507
|
+
// after its first await — relevant on streaming trees, where the guard
|
|
508
|
+
// state now stays live until handleStore.settled.
|
|
509
|
+
const loader = item as LoaderDefinition<any, any>;
|
|
510
|
+
if (!isInsideLoaderScope() && !isInsidePushCallbackScope()) {
|
|
511
|
+
const reqCtx = reqCtxRef ?? _getRequestContext();
|
|
512
|
+
if (reqCtx) {
|
|
513
|
+
// Direction 1: handler awaits loader that already called rendered()
|
|
514
|
+
if (
|
|
515
|
+
loaderPromises.has(loader.$$id) &&
|
|
516
|
+
reqCtx._renderBarrierWaiters?.has(loader.$$id)
|
|
517
|
+
) {
|
|
518
|
+
throw new Error(
|
|
519
|
+
`Deadlock: handler is awaiting loader "${loader.$$id}" which called ctx.rendered(). ` +
|
|
520
|
+
`The loader is waiting for segment resolution, but the handler blocks resolution. ` +
|
|
521
|
+
`Move the data dependency to a loader-to-loader pattern instead.`,
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
// Direction 2: track dep so rendered() can detect the deadlock if the
|
|
525
|
+
// loader calls it later. Skip once the guard window is CLOSED — for a
|
|
526
|
+
// non-streaming tree that is when the barrier resolves (rendered()
|
|
527
|
+
// resolves immediately), and for a streaming tree it is when
|
|
528
|
+
// handleStore.settled completes (rendered() keeps waiting until then, so
|
|
529
|
+
// a loading() handler resuming after the barrier can still form a
|
|
530
|
+
// cycle). Using the explicit guard-closed flag rather than
|
|
531
|
+
// _renderBarrierSegmentOrder keeps tracking live across the streaming
|
|
532
|
+
// settle wait. (Handle push callbacks are already excluded above via
|
|
533
|
+
// isInsidePushCallbackScope(), so they cannot produce false positives
|
|
534
|
+
// here.)
|
|
535
|
+
if (!reqCtx._renderBarrierGuardClosed) {
|
|
536
|
+
if (!reqCtx._handlerLoaderDeps) reqCtx._handlerLoaderDeps = new Set();
|
|
537
|
+
reqCtx._handlerLoaderDeps.add(loader.$$id);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
334
540
|
}
|
|
335
541
|
|
|
336
|
-
return useLoader(
|
|
542
|
+
return useLoader(loader, null);
|
|
337
543
|
}) as typeof ctx.use;
|
|
338
544
|
}
|
|
339
545
|
|
|
@@ -360,18 +566,21 @@ export function setupBuildUse<TEnv>(ctx: HandlerContext<any, TEnv>): void {
|
|
|
360
566
|
);
|
|
361
567
|
}
|
|
362
568
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
)
|
|
366
|
-
|
|
569
|
+
// Wrap with withDefer so ctx.use(Handle).defer(...) works on the build /
|
|
570
|
+
// prerender path, matching production setupLoaderAccess. Without it a
|
|
571
|
+
// prerender handler calling .defer() throws "defer is not a function".
|
|
572
|
+
return withDefer(
|
|
573
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
574
|
+
if (!store) return;
|
|
367
575
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
576
|
+
const valueOrPromise =
|
|
577
|
+
typeof dataOrFn === "function"
|
|
578
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
579
|
+
: dataOrFn;
|
|
372
580
|
|
|
373
|
-
|
|
374
|
-
|
|
581
|
+
store.push(handle.$$id, segmentId, valueOrPromise);
|
|
582
|
+
},
|
|
583
|
+
);
|
|
375
584
|
}
|
|
376
585
|
|
|
377
586
|
// Loader case: not available during pre-rendering
|
|
@@ -399,8 +608,11 @@ export function setupLoaderAccessSilent<TEnv>(
|
|
|
399
608
|
|
|
400
609
|
ctx.use = ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
401
610
|
if (isHandle(item)) {
|
|
402
|
-
// Silent mode - return a no-op so handle data is not pushed during caching
|
|
403
|
-
|
|
611
|
+
// Silent mode - return a no-op so handle data is not pushed during caching.
|
|
612
|
+
// Wrap with withDefer so ctx.use(Handle).defer(...) still resolves to a
|
|
613
|
+
// callable resolver (also a no-op here), matching production's push shape
|
|
614
|
+
// instead of throwing "defer is not a function".
|
|
615
|
+
return withDefer((_dataOrFn: unknown) => {});
|
|
404
616
|
}
|
|
405
617
|
|
|
406
618
|
return useLoader(item as LoaderDefinition<any, any>, null);
|
package/src/router/logging.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
3
3
|
|
|
4
|
-
// -- Revalidation trace types --
|
|
5
|
-
|
|
6
4
|
export interface RevalidationTraceEntry {
|
|
7
5
|
segmentId: string;
|
|
8
6
|
segmentType: string;
|
|
@@ -36,8 +34,6 @@ export interface RevalidationTrace {
|
|
|
36
34
|
entries: RevalidationTraceEntry[];
|
|
37
35
|
}
|
|
38
36
|
|
|
39
|
-
// -- Log context --
|
|
40
|
-
|
|
41
37
|
interface RouterLogContext {
|
|
42
38
|
requestId: string;
|
|
43
39
|
transactionId: string;
|
|
@@ -195,8 +191,6 @@ export function debugWarn(
|
|
|
195
191
|
console.warn(`${prefix} ${message}`);
|
|
196
192
|
}
|
|
197
193
|
|
|
198
|
-
// -- Revalidation trace helpers --
|
|
199
|
-
|
|
200
194
|
export function isTraceActive(): boolean {
|
|
201
195
|
if (!INTERNAL_RANGO_DEBUG) return false;
|
|
202
196
|
const ctx = routerLogContext.getStore();
|
package/src/router/manifest.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Router Manifest Loading
|
|
3
|
-
*
|
|
4
|
-
* Handles lazy loading and validation of route manifests.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
1
|
import { invariant, RouteNotFoundError } from "../errors";
|
|
8
2
|
import { createRouteHelpers } from "../route-definition";
|
|
9
3
|
import {
|
|
@@ -14,6 +8,7 @@ import {
|
|
|
14
8
|
type MetricsStore,
|
|
15
9
|
} from "../server/context";
|
|
16
10
|
import MapRootLayout from "../server/root-layout";
|
|
11
|
+
import { joinPrefix } from "./pattern-matching.js";
|
|
17
12
|
import type { RouteEntry } from "../types";
|
|
18
13
|
import type { UrlPatterns } from "../urls";
|
|
19
14
|
import { VERSION } from "@rangojs/router:version";
|
|
@@ -23,20 +18,19 @@ import { VERSION } from "@rangojs/router:version";
|
|
|
23
18
|
// stable references), so the resulting EntryData tree can be safely cached and reused
|
|
24
19
|
// across requests within the same isolate.
|
|
25
20
|
//
|
|
26
|
-
// Cache is keyed by (VERSION, mountIndex, routeKey, isSSR).
|
|
21
|
+
// Cache is keyed by (VERSION, routerId, mountIndex, routeKey, isSSR). routeKey is
|
|
22
|
+
// REQUIRED in the key: loadManifest() runs the handler with forRoute=routeKey, and
|
|
23
|
+
// path-helper.ts prunes (skips registering) every route except forRoute, so the
|
|
24
|
+
// resulting Store.manifest is pruned to the requested route — NOT the full include.
|
|
25
|
+
// Dropping routeKey would make a sibling route miss and overwrite this entry with its
|
|
26
|
+
// own pruned manifest, so alternating sibling requests would thrash (re-run the
|
|
27
|
+
// handler every time). Running the include handler once per isolate instead of once
|
|
28
|
+
// per route is possible but needs an unpruned manifest cache with prune-on-read — see
|
|
29
|
+
// LP1 in docs/internal/matching-and-lazy-discovery.md. VERSION comes from the
|
|
27
30
|
// @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
|
|
28
31
|
// When VERSION changes, this module re-evaluates and the cache is recreated empty.
|
|
29
|
-
// Including VERSION in the key is additional defense against stale entries.
|
|
30
32
|
const manifestModuleCache = new Map<string, Map<string, EntryData>>();
|
|
31
33
|
|
|
32
|
-
/**
|
|
33
|
-
* Load manifest from route entry with AsyncLocalStorage context
|
|
34
|
-
* Handles lazy imports, unwrapping, and validation
|
|
35
|
-
*
|
|
36
|
-
* Results are cached at module level after first execution. Subsequent calls
|
|
37
|
-
* for the same (routeKey, isSSR) within the same isolate return cached data
|
|
38
|
-
* without re-executing the DSL handler.
|
|
39
|
-
*/
|
|
40
34
|
/**
|
|
41
35
|
* Clear the module-level manifest cache.
|
|
42
36
|
* Called on HMR to ensure stale handler references are discarded.
|
|
@@ -65,9 +59,11 @@ export async function loadManifest(
|
|
|
65
59
|
|
|
66
60
|
const mountIndex = entry.mountIndex;
|
|
67
61
|
|
|
68
|
-
// Check module-level cache (persists across requests within same isolate)
|
|
62
|
+
// Check module-level cache (persists across requests within same isolate).
|
|
69
63
|
// Include routerId so multi-router setups (host routing) don't share cached
|
|
70
64
|
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
65
|
+
// routeKey is in the key because loadManifest() builds a manifest pruned to
|
|
66
|
+
// forRoute=routeKey (see path-helper.ts) — see the cache comment above.
|
|
71
67
|
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
72
68
|
const cached = manifestModuleCache.get(cacheKey);
|
|
73
69
|
if (cached) {
|
|
@@ -88,20 +84,15 @@ export async function loadManifest(
|
|
|
88
84
|
const storeSetupStart = performance.now();
|
|
89
85
|
const Store = getContext().getOrCreateStore(routeKey);
|
|
90
86
|
|
|
91
|
-
// Set mount index in store for unique shortCode prefixes
|
|
92
87
|
Store.mountIndex = mountIndex;
|
|
93
|
-
|
|
94
|
-
// Set isSSR flag so loading() can check if we're in SSR
|
|
95
88
|
Store.isSSR = isSSR;
|
|
96
89
|
|
|
97
|
-
// Attach metrics store to context if provided
|
|
98
90
|
if (metricsStore) {
|
|
99
91
|
Store.metrics = metricsStore;
|
|
100
92
|
}
|
|
101
93
|
|
|
102
94
|
pushMetric?.("manifest:store-setup", storeSetupStart);
|
|
103
95
|
|
|
104
|
-
// Clear manifest before rebuilding to prevent stale entry mutations
|
|
105
96
|
const clearStart = performance.now();
|
|
106
97
|
Store.manifest.clear();
|
|
107
98
|
pushMetric?.("manifest:clear", clearStart);
|
|
@@ -126,28 +117,37 @@ export async function loadManifest(
|
|
|
126
117
|
// were created during pattern extraction. This prevents shortCode
|
|
127
118
|
// collisions between lazy and non-lazy entries under the same parent
|
|
128
119
|
// (e.g., ArticlesLayout and BlogLayout both under NavLayout).
|
|
129
|
-
if (lazyContext
|
|
130
|
-
const
|
|
131
|
-
for (const [key, value] of Object.entries(captured)) {
|
|
120
|
+
if (lazyContext?.counters) {
|
|
121
|
+
for (const [key, value] of Object.entries(lazyContext.counters)) {
|
|
132
122
|
Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
|
|
133
123
|
}
|
|
134
124
|
}
|
|
135
125
|
|
|
136
126
|
// Propagate cache profiles for DSL-time cache("profileName") resolution.
|
|
137
127
|
// Non-lazy entries carry profiles directly; lazy entries carry them
|
|
138
|
-
// in the captured lazyContext from include() time.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
128
|
+
// in the captured lazyContext from include() time. Always write
|
|
129
|
+
// (including clearing to undefined) so a prior lazy build's profile
|
|
130
|
+
// map cannot leak into a later non-lazy build on the same ALS-backed
|
|
131
|
+
// Store — which would otherwise let cache("name") resolve a profile
|
|
132
|
+
// from an unrelated entry.
|
|
133
|
+
Store.cacheProfiles = entry.cacheProfiles ?? lazyContext?.cacheProfiles;
|
|
144
134
|
|
|
145
135
|
// Propagate rootScoped from lazyContext so that routes inside
|
|
146
136
|
// nested { name: "sub" } under { name: "" } keep inherited root scope
|
|
147
|
-
// when the manifest is rebuilt on each request.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
// when the manifest is rebuilt on each request. Always write
|
|
138
|
+
// (including clearing to undefined, which makes getRootScoped()
|
|
139
|
+
// return its true default) so a prior lazy build's scope cannot leak
|
|
140
|
+
// into a later non-lazy build on the same ALS-backed Store — which
|
|
141
|
+
// would otherwise mis-register plain routes as non-root-scoped and
|
|
142
|
+
// break dot-local reverse resolution.
|
|
143
|
+
Store.rootScoped = lazyContext?.rootScoped;
|
|
144
|
+
|
|
145
|
+
// Propagate includeScope from lazyContext so that direct-descendant
|
|
146
|
+
// shortCodes of this include use the correct scoped counter namespace
|
|
147
|
+
// on every manifest rebuild. Always write (including clearing to
|
|
148
|
+
// undefined) so a prior lazy build's scope cannot leak into a later
|
|
149
|
+
// non-lazy build on the same ALS-backed Store.
|
|
150
|
+
Store.includeScope = lazyContext?.includeScope;
|
|
151
151
|
|
|
152
152
|
const handlerExecStart = performance.now();
|
|
153
153
|
const useItems = await getContext().runWithStore(
|
|
@@ -167,7 +167,10 @@ export async function loadManifest(
|
|
|
167
167
|
if (entry.lazy && entry.lazyPatterns) {
|
|
168
168
|
const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
|
|
169
169
|
const includePrefix = (entry as any)._lazyPrefix || "";
|
|
170
|
-
|
|
170
|
+
// Slash-collapsing join so a trailing-slash parent prefix does not
|
|
171
|
+
// bake a double slash into the registered route patterns (must match
|
|
172
|
+
// the same join in evaluateLazyEntry / the build-time runWithPrefixes).
|
|
173
|
+
const fullPrefix = joinPrefix(lazyContext?.urlPrefix, includePrefix);
|
|
171
174
|
|
|
172
175
|
if (fullPrefix || lazyContext?.namePrefix) {
|
|
173
176
|
return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
|
|
@@ -177,20 +180,16 @@ export async function loadManifest(
|
|
|
177
180
|
return lazyPatterns.handler();
|
|
178
181
|
}
|
|
179
182
|
|
|
180
|
-
// Wrap handler execution in root layout so routes get correct parent
|
|
181
|
-
// This ensures all routes are registered with the layout as their parent
|
|
182
183
|
let promiseResult: Promise<any> | null = null;
|
|
183
184
|
const wrappedItems = helpers.layout(MapRootLayout, () => {
|
|
184
185
|
const result = entry.handler();
|
|
185
186
|
if (result instanceof Promise) {
|
|
186
|
-
// Lazy handler detected - capture promise for async handling
|
|
187
187
|
promiseResult = result;
|
|
188
|
-
return [];
|
|
188
|
+
return [];
|
|
189
189
|
}
|
|
190
190
|
return result;
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
-
// Handle lazy (Promise-based) handlers
|
|
194
193
|
if (promiseResult !== null) {
|
|
195
194
|
const load = await (promiseResult as Promise<any>);
|
|
196
195
|
if (
|
|
@@ -224,7 +223,6 @@ export async function loadManifest(
|
|
|
224
223
|
);
|
|
225
224
|
}
|
|
226
225
|
|
|
227
|
-
// Inline handler - routes were registered with correct parent inside layout
|
|
228
226
|
return [wrappedItems].flat(3);
|
|
229
227
|
},
|
|
230
228
|
);
|