@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad
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 +17 -0
- package/README.md +291 -61
- package/dist/bin/rango.js +544 -143
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3744 -1329
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +67 -13
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +312 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +322 -19
- package/skills/composability/SKILL.md +27 -2
- 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 +282 -60
- package/skills/host-router/SKILL.md +278 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +297 -31
- 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 +203 -7
- package/skills/prerender/SKILL.md +155 -111
- package/skills/rango/SKILL.md +251 -23
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +104 -9
- package/skills/router-setup/SKILL.md +124 -11
- 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 +125 -222
- 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 +121 -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 +357 -52
- package/skills/use-cache/SKILL.md +46 -14
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/bin/rango.ts +18 -0
- 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 +197 -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/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +200 -30
- package/src/browser/navigation-client.ts +217 -58
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +312 -30
- package/src/browser/prefetch/fetch.ts +344 -47
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +125 -18
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- 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 +80 -5
- package/src/browser/rsc-router.tsx +226 -75
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +619 -442
- package/src/browser/types.ts +115 -11
- 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 +7 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +182 -37
- 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 +392 -106
- 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 +214 -48
- package/src/cache/cache-scope.ts +236 -89
- 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 +2224 -171
- 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 +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +45 -21
- package/src/client.tsx +120 -336
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +95 -0
- 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 -5
- 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 +79 -29
- package/src/index.ts +149 -65
- 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 +63 -26
- 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 +7 -72
- package/src/route-definition/dsl-helpers.ts +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- 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 +45 -22
- package/src/router/handler-context.ts +110 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -49
- package/src/router/metrics.ts +217 -26
- package/src/router/middleware-types.ts +24 -110
- package/src/router/middleware.ts +384 -197
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +148 -91
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +199 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +144 -74
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +129 -36
- package/src/router/router-options.ts +185 -23
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +281 -76
- package/src/router/segment-resolution/helpers.ts +116 -31
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +493 -391
- 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 +36 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +8 -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 +180 -58
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +182 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +702 -460
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +98 -19
- 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 +126 -106
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +269 -114
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/types.ts +34 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +49 -41
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +317 -63
- 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 +26 -46
- package/src/server/request-context.ts +425 -177
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +25 -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 +99 -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 +343 -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/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 +3 -19
- 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 +236 -88
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +10 -45
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -19
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- 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 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +64 -91
- package/src/vite/discovery/discover-routers.ts +147 -88
- 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 +247 -145
- 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 +61 -13
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +10 -3
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- 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 +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +241 -287
- package/src/vite/router-discovery.ts +956 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- 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 +141 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/CLAUDE.md +0 -5
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -6
- package/src/network-error-thrower.tsx +0 -23
- package/src/route-definition/route-function.ts +0 -119
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -7,7 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import type { ReactNode } from "react";
|
|
9
9
|
import { invariant } from "../../errors";
|
|
10
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
getParallelEntries,
|
|
12
|
+
getParallelSlotEntries,
|
|
13
|
+
type EntryData,
|
|
14
|
+
} from "../../server/context";
|
|
11
15
|
import type {
|
|
12
16
|
HandlerContext,
|
|
13
17
|
InternalHandlerContext,
|
|
@@ -21,52 +25,18 @@ import {
|
|
|
21
25
|
tryStaticSlot,
|
|
22
26
|
resolveLayoutComponent,
|
|
23
27
|
resolveWithErrorBoundary,
|
|
28
|
+
warnOnStreamedResponse,
|
|
29
|
+
buildLoaderErrorContext,
|
|
24
30
|
} from "./helpers.js";
|
|
31
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
25
32
|
import { getRouterContext } from "../router-context.js";
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
34
|
-
* React catches the actual error via its error boundary; this only emits
|
|
35
|
-
* the handler.error telemetry event.
|
|
36
|
-
*/
|
|
37
|
-
function observeStreamedHandler(
|
|
38
|
-
promise: Promise<ReactNode>,
|
|
39
|
-
segmentId: string,
|
|
40
|
-
segmentType: string,
|
|
41
|
-
pathname?: string,
|
|
42
|
-
routeKey?: string,
|
|
43
|
-
params?: Record<string, string>,
|
|
44
|
-
): void {
|
|
45
|
-
let routerCtx;
|
|
46
|
-
try {
|
|
47
|
-
routerCtx = getRouterContext();
|
|
48
|
-
} catch {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if (!routerCtx?.telemetry) return;
|
|
52
|
-
const sink = resolveSink(routerCtx.telemetry);
|
|
53
|
-
const reqId = routerCtx.requestId;
|
|
54
|
-
promise.catch((err: unknown) => {
|
|
55
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
56
|
-
safeEmit(sink, {
|
|
57
|
-
type: "handler.error",
|
|
58
|
-
timestamp: performance.now(),
|
|
59
|
-
requestId: reqId,
|
|
60
|
-
segmentId,
|
|
61
|
-
segmentType,
|
|
62
|
-
error: errorObj,
|
|
63
|
-
handledByBoundary: true,
|
|
64
|
-
pathname,
|
|
65
|
-
routeKey,
|
|
66
|
-
params,
|
|
67
|
-
});
|
|
68
|
-
});
|
|
69
|
-
}
|
|
33
|
+
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
34
|
+
import { observeHandler } from "../instrument.js";
|
|
35
|
+
import {
|
|
36
|
+
track,
|
|
37
|
+
RangoContext,
|
|
38
|
+
runInsideLoaderScope,
|
|
39
|
+
} from "../../server/context.js";
|
|
70
40
|
|
|
71
41
|
// ---------------------------------------------------------------------------
|
|
72
42
|
// Fresh path (full match, no revalidation)
|
|
@@ -90,8 +60,16 @@ export async function resolveLoaders<TEnv>(
|
|
|
90
60
|
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
91
61
|
const loadingDisabled = hasLoading && entry.loading === false;
|
|
92
62
|
|
|
63
|
+
// Error context for wrapLoaderPromise: without it, a throwing DSL loader never
|
|
64
|
+
// fires createRouter({ onError }) (phase "loader") nor emits the loader.error
|
|
65
|
+
// telemetry event — wrapLoaderPromise only builds the onError/telemetry path
|
|
66
|
+
// when errorContext is supplied. Built from ctx so the live render path reports
|
|
67
|
+
// loader failures the same way handlers/actions/routing/fetchable-loaders do.
|
|
68
|
+
const errorContext = buildLoaderErrorContext(ctx);
|
|
69
|
+
|
|
93
70
|
if (!loadingDisabled) {
|
|
94
|
-
|
|
71
|
+
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
72
|
+
const segments = loaderEntries.map((loaderEntry, i) => {
|
|
95
73
|
const { loader } = loaderEntry;
|
|
96
74
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
97
75
|
return {
|
|
@@ -103,40 +81,63 @@ export async function resolveLoaders<TEnv>(
|
|
|
103
81
|
params: ctx.params,
|
|
104
82
|
loaderId: loader.$$id,
|
|
105
83
|
loaderData: deps.wrapLoaderPromise(
|
|
106
|
-
|
|
84
|
+
runInsideLoaderScope(() =>
|
|
85
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
86
|
+
),
|
|
107
87
|
entry,
|
|
108
88
|
segmentId,
|
|
109
89
|
ctx.pathname,
|
|
90
|
+
errorContext,
|
|
110
91
|
),
|
|
111
92
|
belongsToRoute,
|
|
112
93
|
};
|
|
113
94
|
});
|
|
95
|
+
|
|
96
|
+
return segments;
|
|
114
97
|
}
|
|
115
98
|
|
|
116
99
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
117
100
|
// settled promises so handlers don't stream loading placeholders.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
101
|
+
//
|
|
102
|
+
// Wrap each loader promise with wrapLoaderPromise BEFORE awaiting. The wrapped
|
|
103
|
+
// promise resolves to a LoaderDataResult and never rejects, routing a failed
|
|
104
|
+
// loader to its own per-loader error boundary. Awaiting the RAW promises here
|
|
105
|
+
// instead would (1) propagate a rejection to the segment-level boundary,
|
|
106
|
+
// collapsing the whole entry and discarding successful sibling data, and
|
|
107
|
+
// (2) leave the other in-flight raw promises without a .catch, producing
|
|
108
|
+
// unhandled rejections. Mirrors the loading path and intercept-resolution.
|
|
109
|
+
const pendingLoaderData = loaderEntries.map((loaderEntry, i) => {
|
|
110
|
+
const { loader } = loaderEntry;
|
|
111
|
+
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
112
|
+
const wrapped = deps.wrapLoaderPromise(
|
|
113
|
+
runInsideLoaderScope(() =>
|
|
114
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
115
|
+
),
|
|
116
|
+
entry,
|
|
117
|
+
segmentId,
|
|
118
|
+
ctx.pathname,
|
|
119
|
+
errorContext,
|
|
120
|
+
);
|
|
121
|
+
return { wrapped, segmentId };
|
|
122
|
+
});
|
|
123
|
+
await Promise.all(pendingLoaderData.map((p) => p.wrapped));
|
|
122
124
|
|
|
123
125
|
return loaderEntries.map((loaderEntry, i) => {
|
|
124
126
|
const { loader } = loaderEntry;
|
|
125
|
-
const
|
|
127
|
+
const pending = pendingLoaderData[i]!;
|
|
128
|
+
// The "loader:<id>" perf metric is recorded by observePhase at the single
|
|
129
|
+
// loader-metering site (useLoader, reached via ctx.use during
|
|
130
|
+
// resolveLoaderData), with the real per-loader duration rather than a
|
|
131
|
+
// Promise.all batch ceiling.
|
|
126
132
|
return {
|
|
127
|
-
id: segmentId,
|
|
133
|
+
id: pending.segmentId,
|
|
128
134
|
namespace: entry.id,
|
|
129
135
|
type: "loader" as const,
|
|
130
136
|
index: i,
|
|
131
137
|
component: null,
|
|
132
138
|
params: ctx.params,
|
|
133
139
|
loaderId: loader.$$id,
|
|
134
|
-
loaderData:
|
|
135
|
-
pendingLoaderData[i]!,
|
|
136
|
-
entry,
|
|
137
|
-
segmentId,
|
|
138
|
-
ctx.pathname,
|
|
139
|
-
),
|
|
140
|
+
loaderData: pending.wrapped,
|
|
140
141
|
belongsToRoute,
|
|
141
142
|
};
|
|
142
143
|
});
|
|
@@ -148,6 +149,15 @@ export async function resolveLoaders<TEnv>(
|
|
|
148
149
|
export interface ResolveSegmentOptions {
|
|
149
150
|
/** When true, skip resolveLoaders() calls (used for pre-rendering) */
|
|
150
151
|
skipLoaders?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* When true, a thrown render error is re-thrown instead of being converted
|
|
154
|
+
* into an error-boundary segment. Set only by the pre-render path so a
|
|
155
|
+
* build-time render failure (and a `throw new Skip()` inside a render fn)
|
|
156
|
+
* surfaces to the build instead of being silently baked into a frozen error
|
|
157
|
+
* page served as a 200 (issue #587). The live request path leaves this unset,
|
|
158
|
+
* so error boundaries keep catching at request time.
|
|
159
|
+
*/
|
|
160
|
+
throwOnError?: boolean;
|
|
151
161
|
}
|
|
152
162
|
|
|
153
163
|
/**
|
|
@@ -178,7 +188,9 @@ export async function resolveSegment<TEnv>(
|
|
|
178
188
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
179
189
|
entry.shortCode;
|
|
180
190
|
|
|
191
|
+
const doneLayoutHandler = track(`handler:${entry.id}`, 2);
|
|
181
192
|
const component = await resolveLayoutComponent(entry, context);
|
|
193
|
+
doneLayoutHandler();
|
|
182
194
|
|
|
183
195
|
segments.push({
|
|
184
196
|
id: entry.shortCode,
|
|
@@ -187,14 +199,20 @@ export async function resolveSegment<TEnv>(
|
|
|
187
199
|
index: 0,
|
|
188
200
|
component,
|
|
189
201
|
loading: entry.loading === false ? null : entry.loading,
|
|
190
|
-
transition:
|
|
202
|
+
transition: applyViewTransitionDefault(
|
|
203
|
+
entry.transition,
|
|
204
|
+
deps.viewTransitionDefault,
|
|
205
|
+
),
|
|
191
206
|
params,
|
|
192
207
|
belongsToRoute: false,
|
|
193
208
|
layoutName: entry.id,
|
|
194
209
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
195
210
|
});
|
|
196
211
|
|
|
197
|
-
|
|
212
|
+
const resolvedParallelEntries = new Set<string>();
|
|
213
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
214
|
+
entry.parallel,
|
|
215
|
+
)) {
|
|
198
216
|
const parallelSegments = await resolveParallelEntry(
|
|
199
217
|
parallelEntry,
|
|
200
218
|
params,
|
|
@@ -204,8 +222,11 @@ export async function resolveSegment<TEnv>(
|
|
|
204
222
|
deps,
|
|
205
223
|
options,
|
|
206
224
|
routeKey,
|
|
225
|
+
[slot],
|
|
226
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
207
227
|
);
|
|
208
228
|
segments.push(...parallelSegments);
|
|
229
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
209
230
|
}
|
|
210
231
|
|
|
211
232
|
for (const orphan of entry.layout) {
|
|
@@ -241,9 +262,19 @@ export async function resolveSegment<TEnv>(
|
|
|
241
262
|
entry.shortCode,
|
|
242
263
|
);
|
|
243
264
|
if (component === undefined) {
|
|
265
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
266
|
+
// the build handler. At build time (context.build === true), always
|
|
267
|
+
// use the build handler from entry.handler.
|
|
268
|
+
const handler =
|
|
269
|
+
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
270
|
+
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
244
271
|
if (entry.loading) {
|
|
245
|
-
const result = handleHandlerResult(
|
|
272
|
+
const result = handleHandlerResult(
|
|
273
|
+
observeHandler(entry.id, handler, context),
|
|
274
|
+
);
|
|
246
275
|
if (result instanceof Promise) {
|
|
276
|
+
warnOnStreamedResponse(result, entry.id);
|
|
277
|
+
result.finally(doneRouteHandler).catch(() => {});
|
|
247
278
|
const tracked = deps.trackHandler(result, {
|
|
248
279
|
segmentId: entry.shortCode,
|
|
249
280
|
segmentType: entry.type,
|
|
@@ -258,10 +289,14 @@ export async function resolveSegment<TEnv>(
|
|
|
258
289
|
);
|
|
259
290
|
component = tracked;
|
|
260
291
|
} else {
|
|
292
|
+
doneRouteHandler();
|
|
261
293
|
component = result;
|
|
262
294
|
}
|
|
263
295
|
} else {
|
|
264
|
-
component = handleHandlerResult(
|
|
296
|
+
component = handleHandlerResult(
|
|
297
|
+
await observeHandler(entry.id, handler, context),
|
|
298
|
+
);
|
|
299
|
+
doneRouteHandler();
|
|
265
300
|
}
|
|
266
301
|
}
|
|
267
302
|
|
|
@@ -275,11 +310,15 @@ export async function resolveSegment<TEnv>(
|
|
|
275
310
|
deps,
|
|
276
311
|
options,
|
|
277
312
|
routeKey,
|
|
313
|
+
entry,
|
|
278
314
|
);
|
|
279
315
|
segments.push(...orphanSegments);
|
|
280
316
|
}
|
|
281
317
|
|
|
282
|
-
|
|
318
|
+
const resolvedParallelEntries = new Set<string>();
|
|
319
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
320
|
+
entry.parallel,
|
|
321
|
+
)) {
|
|
283
322
|
const parallelSegments = await resolveParallelEntry(
|
|
284
323
|
parallelEntry,
|
|
285
324
|
params,
|
|
@@ -289,8 +328,11 @@ export async function resolveSegment<TEnv>(
|
|
|
289
328
|
deps,
|
|
290
329
|
options,
|
|
291
330
|
routeKey,
|
|
331
|
+
[slot],
|
|
332
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
292
333
|
);
|
|
293
334
|
segments.push(...parallelSegments);
|
|
335
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
294
336
|
}
|
|
295
337
|
|
|
296
338
|
segments.push({
|
|
@@ -298,9 +340,12 @@ export async function resolveSegment<TEnv>(
|
|
|
298
340
|
namespace: entry.id,
|
|
299
341
|
type: "route",
|
|
300
342
|
index: 0,
|
|
301
|
-
component,
|
|
343
|
+
component: component ?? null,
|
|
302
344
|
loading: entry.loading === false ? null : entry.loading,
|
|
303
|
-
transition:
|
|
345
|
+
transition: applyViewTransitionDefault(
|
|
346
|
+
entry.transition,
|
|
347
|
+
deps.viewTransitionDefault,
|
|
348
|
+
),
|
|
304
349
|
params,
|
|
305
350
|
belongsToRoute: true,
|
|
306
351
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
@@ -324,6 +369,9 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
324
369
|
deps: SegmentResolutionDeps<TEnv>,
|
|
325
370
|
options?: ResolveSegmentOptions,
|
|
326
371
|
routeKey?: string,
|
|
372
|
+
/** Parent route entry — its loaders are inherited by the layout so
|
|
373
|
+
* parallel slots inside this layout can access them via useLoader(). */
|
|
374
|
+
parentRouteEntry?: EntryData,
|
|
327
375
|
): Promise<ResolvedSegment[]> {
|
|
328
376
|
invariant(
|
|
329
377
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -339,11 +387,37 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
339
387
|
deps,
|
|
340
388
|
);
|
|
341
389
|
segments.push(...loaderSegments);
|
|
390
|
+
|
|
391
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
392
|
+
// can access them via useLoader(). Without this, the route's loaders
|
|
393
|
+
// are only in the route's OutletProvider (rendered as <Outlet /> content),
|
|
394
|
+
// which is a child — not a parent — of the layout's context.
|
|
395
|
+
if (
|
|
396
|
+
parentRouteEntry &&
|
|
397
|
+
parentRouteEntry.loader &&
|
|
398
|
+
parentRouteEntry.loader.length > 0 &&
|
|
399
|
+
Object.keys(orphan.parallel).length > 0
|
|
400
|
+
) {
|
|
401
|
+
const inheritedLoaders = await resolveLoaders(
|
|
402
|
+
parentRouteEntry,
|
|
403
|
+
context,
|
|
404
|
+
belongsToRoute,
|
|
405
|
+
deps,
|
|
406
|
+
orphan.shortCode,
|
|
407
|
+
);
|
|
408
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
409
|
+
for (const s of inheritedLoaders) {
|
|
410
|
+
s._inherited = true;
|
|
411
|
+
}
|
|
412
|
+
segments.push(...inheritedLoaders);
|
|
413
|
+
}
|
|
342
414
|
}
|
|
343
415
|
|
|
344
416
|
// Handler-first: orphan layout handler executes before its parallels
|
|
345
417
|
// so that ctx.set() values are visible to parallel children.
|
|
418
|
+
const doneOrphanHandler = track(`handler:${orphan.id}`, 2);
|
|
346
419
|
const component = await resolveLayoutComponent(orphan, context);
|
|
420
|
+
doneOrphanHandler();
|
|
347
421
|
|
|
348
422
|
segments.push({
|
|
349
423
|
id: orphan.shortCode,
|
|
@@ -355,11 +429,17 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
355
429
|
belongsToRoute,
|
|
356
430
|
layoutName: orphan.id,
|
|
357
431
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
358
|
-
transition:
|
|
432
|
+
transition: applyViewTransitionDefault(
|
|
433
|
+
orphan.transition,
|
|
434
|
+
deps.viewTransitionDefault,
|
|
435
|
+
),
|
|
359
436
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
360
437
|
});
|
|
361
438
|
|
|
362
|
-
|
|
439
|
+
const resolvedParallelEntries = new Set<string>();
|
|
440
|
+
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
441
|
+
orphan.parallel,
|
|
442
|
+
)) {
|
|
363
443
|
const parallelSegments = await resolveParallelEntry(
|
|
364
444
|
parallelEntry,
|
|
365
445
|
params,
|
|
@@ -369,8 +449,11 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
369
449
|
deps,
|
|
370
450
|
options,
|
|
371
451
|
routeKey,
|
|
452
|
+
[slot],
|
|
453
|
+
!resolvedParallelEntries.has(parallelEntry.id),
|
|
372
454
|
);
|
|
373
455
|
segments.push(...parallelSegments);
|
|
456
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
374
457
|
}
|
|
375
458
|
|
|
376
459
|
return segments;
|
|
@@ -388,6 +471,8 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
388
471
|
deps: SegmentResolutionDeps<TEnv>,
|
|
389
472
|
options?: ResolveSegmentOptions,
|
|
390
473
|
routeKey?: string,
|
|
474
|
+
slotNames?: `@${string}`[],
|
|
475
|
+
includeLoaders: boolean = true,
|
|
391
476
|
): Promise<ResolvedSegment[]> {
|
|
392
477
|
invariant(
|
|
393
478
|
parallelEntry.type === "parallel",
|
|
@@ -402,7 +487,12 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
402
487
|
| ReactNode
|
|
403
488
|
>;
|
|
404
489
|
|
|
405
|
-
|
|
490
|
+
const slotsToResolve = slotNames ?? (Object.keys(slots) as `@${string}`[]);
|
|
491
|
+
|
|
492
|
+
for (const slot of slotsToResolve) {
|
|
493
|
+
// Try static lookup first — in production, handler bodies are evicted
|
|
494
|
+
// and replaced with stubs that have no .handler property (undefined).
|
|
495
|
+
// The static store holds the pre-rendered component for these slots.
|
|
406
496
|
let component: ReactNode | undefined = await tryStaticSlot(
|
|
407
497
|
parallelEntry,
|
|
408
498
|
slot,
|
|
@@ -410,12 +500,31 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
410
500
|
);
|
|
411
501
|
|
|
412
502
|
if (component === undefined) {
|
|
503
|
+
const handler = slots[slot];
|
|
504
|
+
if (handler === undefined) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
// Pin `_currentSegmentId` to the slot's own id so handle pushes from
|
|
508
|
+
// inside the slot handler get their own bucket in the HandleStore.
|
|
509
|
+
// Parent-keying would collapse them into the parent layout's bucket;
|
|
510
|
+
// the partial-update merge then replaces the parent's bucket on a
|
|
511
|
+
// slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
|
|
512
|
+
// filterSegmentOrder() retains slot ids so the client preserves them.
|
|
513
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
514
|
+
`${parentShortCode}.${slot}`;
|
|
515
|
+
const doneParallelHandler = track(
|
|
516
|
+
`handler:${parallelEntry.id}.${slot}`,
|
|
517
|
+
2,
|
|
518
|
+
);
|
|
413
519
|
const hasLoadingFallback =
|
|
414
520
|
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
415
521
|
if (hasLoadingFallback) {
|
|
416
522
|
const result =
|
|
417
|
-
typeof handler === "function"
|
|
523
|
+
typeof handler === "function"
|
|
524
|
+
? observeHandler(`${parallelEntry.id}.${slot}`, handler, context)
|
|
525
|
+
: handler;
|
|
418
526
|
if (result instanceof Promise) {
|
|
527
|
+
result.finally(doneParallelHandler).catch(() => {});
|
|
419
528
|
const tracked = deps.trackHandler(result, {
|
|
420
529
|
segmentId: `${parentShortCode}.${slot}`,
|
|
421
530
|
segmentType: "parallel",
|
|
@@ -430,11 +539,19 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
430
539
|
);
|
|
431
540
|
component = tracked as ReactNode;
|
|
432
541
|
} else {
|
|
542
|
+
doneParallelHandler();
|
|
433
543
|
component = result as ReactNode;
|
|
434
544
|
}
|
|
435
545
|
} else {
|
|
436
546
|
component =
|
|
437
|
-
typeof handler === "function"
|
|
547
|
+
typeof handler === "function"
|
|
548
|
+
? await observeHandler(
|
|
549
|
+
`${parallelEntry.id}.${slot}`,
|
|
550
|
+
handler,
|
|
551
|
+
context,
|
|
552
|
+
)
|
|
553
|
+
: handler;
|
|
554
|
+
doneParallelHandler();
|
|
438
555
|
}
|
|
439
556
|
}
|
|
440
557
|
|
|
@@ -445,7 +562,10 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
445
562
|
index: 0,
|
|
446
563
|
component,
|
|
447
564
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
448
|
-
transition:
|
|
565
|
+
transition: applyViewTransitionDefault(
|
|
566
|
+
parallelEntry.transition,
|
|
567
|
+
deps.viewTransitionDefault,
|
|
568
|
+
),
|
|
449
569
|
params,
|
|
450
570
|
slot,
|
|
451
571
|
belongsToRoute,
|
|
@@ -456,7 +576,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
456
576
|
});
|
|
457
577
|
}
|
|
458
578
|
|
|
459
|
-
if (!
|
|
579
|
+
if (!options?.skipLoaders && includeLoaders) {
|
|
460
580
|
const loaderSegments = await resolveLoaders(
|
|
461
581
|
parallelEntry,
|
|
462
582
|
context,
|
|
@@ -464,6 +584,15 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
464
584
|
deps,
|
|
465
585
|
parentShortCode,
|
|
466
586
|
);
|
|
587
|
+
// Tag parallel-owned loaders so renderSegments can stream them
|
|
588
|
+
// using the parallel's loading() instead of awaiting on the layout
|
|
589
|
+
const parallelLoading =
|
|
590
|
+
parallelEntry.loading === false ? undefined : parallelEntry.loading;
|
|
591
|
+
if (parallelLoading) {
|
|
592
|
+
for (const seg of loaderSegments) {
|
|
593
|
+
seg.parallelLoading = parallelLoading;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
467
596
|
segments.push(...loaderSegments);
|
|
468
597
|
}
|
|
469
598
|
|
|
@@ -499,6 +628,14 @@ export async function resolveAllSegments<TEnv>(
|
|
|
499
628
|
} catch {}
|
|
500
629
|
|
|
501
630
|
for (const entry of entries) {
|
|
631
|
+
// Set ALS flag when entering a cache() boundary so that ctx.get()
|
|
632
|
+
// can guard non-cacheable variable reads. Also guards response-level
|
|
633
|
+
// side effects (headers.set). Persists for all descendant entries.
|
|
634
|
+
if (entry.type === "cache") {
|
|
635
|
+
const store = RangoContext.getStore();
|
|
636
|
+
if (store) store.insideCacheScope = true;
|
|
637
|
+
}
|
|
638
|
+
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
502
639
|
const resolvedSegments = await resolveWithErrorBoundary(
|
|
503
640
|
entry,
|
|
504
641
|
params,
|
|
@@ -517,7 +654,9 @@ export async function resolveAllSegments<TEnv>(
|
|
|
517
654
|
deps,
|
|
518
655
|
{ request: safeRequest, url: context.url, routeKey, telemetry },
|
|
519
656
|
context.pathname,
|
|
657
|
+
options?.throwOnError,
|
|
520
658
|
);
|
|
659
|
+
doneEntry();
|
|
521
660
|
// Deduplicate by segment ID. include() scopes can produce entries that
|
|
522
661
|
// resolve the same shared layout/loader segment. Duplicates in the segment
|
|
523
662
|
// array propagate to the client's matched[] and change the React tree depth.
|
|
@@ -541,11 +680,77 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
541
680
|
deps: SegmentResolutionDeps<TEnv>,
|
|
542
681
|
): Promise<ResolvedSegment[]> {
|
|
543
682
|
const loaderSegments: ResolvedSegment[] = [];
|
|
683
|
+
const seenIds = new Set<string>();
|
|
684
|
+
|
|
685
|
+
async function collectEntryLoaders(
|
|
686
|
+
entry: EntryData,
|
|
687
|
+
belongsToRoute: boolean,
|
|
688
|
+
shortCodeOverride?: string,
|
|
689
|
+
): Promise<void> {
|
|
690
|
+
// Skip if all loaders from this entry have already been resolved
|
|
691
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
692
|
+
const entryLoaders = entry.loader ?? [];
|
|
693
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
694
|
+
const allAlreadySeen =
|
|
695
|
+
entryLoaders.length > 0 &&
|
|
696
|
+
entryLoaders.every((le, i) =>
|
|
697
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
698
|
+
);
|
|
699
|
+
if (!allAlreadySeen) {
|
|
700
|
+
const segments = await resolveLoaders(
|
|
701
|
+
entry,
|
|
702
|
+
context,
|
|
703
|
+
belongsToRoute,
|
|
704
|
+
deps,
|
|
705
|
+
shortCodeOverride,
|
|
706
|
+
);
|
|
707
|
+
for (const seg of segments) {
|
|
708
|
+
if (!seenIds.has(seg.id)) {
|
|
709
|
+
seenIds.add(seg.id);
|
|
710
|
+
loaderSegments.push(seg);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const seenParallelEntryIds = new Set<string>();
|
|
716
|
+
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
717
|
+
if (seenParallelEntryIds.has(parallelEntry.id)) continue;
|
|
718
|
+
seenParallelEntryIds.add(parallelEntry.id);
|
|
719
|
+
await collectEntryLoaders(parallelEntry, belongsToRoute, entry.shortCode);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
723
|
+
for (const layoutEntry of entry.layout) {
|
|
724
|
+
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
725
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
726
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
727
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
728
|
+
if (
|
|
729
|
+
entry.type === "route" &&
|
|
730
|
+
entry.loader &&
|
|
731
|
+
entry.loader.length > 0 &&
|
|
732
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
733
|
+
) {
|
|
734
|
+
const inherited = await resolveLoaders(
|
|
735
|
+
entry,
|
|
736
|
+
context,
|
|
737
|
+
childBelongsToRoute,
|
|
738
|
+
deps,
|
|
739
|
+
layoutEntry.shortCode,
|
|
740
|
+
);
|
|
741
|
+
for (const seg of inherited) {
|
|
742
|
+
if (!seenIds.has(seg.id)) {
|
|
743
|
+
seenIds.add(seg.id);
|
|
744
|
+
seg._inherited = true;
|
|
745
|
+
loaderSegments.push(seg);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
544
751
|
|
|
545
752
|
for (const entry of entries) {
|
|
546
|
-
|
|
547
|
-
const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
|
|
548
|
-
loaderSegments.push(...segments);
|
|
753
|
+
await collectEntryLoaders(entry, entry.type === "route");
|
|
549
754
|
}
|
|
550
755
|
|
|
551
756
|
return loaderSegments;
|