@rangojs/router 0.0.0-experimental.135c6902 → 0.0.0-experimental.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3386 -1111
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +78 -25
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +281 -11
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +289 -53
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +914 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +144 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +101 -5
- package/skills/router-setup/SKILL.md +117 -10
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +332 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +131 -30
- package/src/browser/navigation-client.ts +199 -86
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +174 -109
- package/src/browser/prefetch/cache.ts +222 -69
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +113 -32
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +300 -122
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +125 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -61
- package/src/browser/types.ts +115 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +239 -52
- package/src/cache/cache-scope.ts +234 -87
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +149 -74
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +83 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +54 -25
- package/src/router/match-middleware/cache-lookup.ts +205 -271
- package/src/router/match-middleware/cache-store.ts +81 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +93 -39
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +268 -171
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -27
- package/src/router/router-interfaces.ts +129 -35
- package/src/router/router-options.ts +202 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +197 -92
- package/src/router/segment-resolution/helpers.ts +115 -30
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +392 -338
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +174 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +660 -518
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +133 -13
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +115 -73
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -113
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +36 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +269 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +235 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +442 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +234 -82
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +214 -132
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +119 -111
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -19,61 +19,24 @@ import type {
|
|
|
19
19
|
} from "../../types";
|
|
20
20
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
21
21
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
22
|
-
import { _getRequestContext } from "../../server/request-context.js";
|
|
23
|
-
import { appendMetric } from "../metrics.js";
|
|
24
22
|
import {
|
|
25
23
|
handleHandlerResult,
|
|
26
24
|
tryStaticHandler,
|
|
27
25
|
tryStaticSlot,
|
|
28
26
|
resolveLayoutComponent,
|
|
29
27
|
resolveWithErrorBoundary,
|
|
28
|
+
warnOnStreamedResponse,
|
|
29
|
+
buildLoaderErrorContext,
|
|
30
30
|
} from "./helpers.js";
|
|
31
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
31
32
|
import { getRouterContext } from "../router-context.js";
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
41
|
-
* React catches the actual error via its error boundary; this only emits
|
|
42
|
-
* the handler.error telemetry event.
|
|
43
|
-
*/
|
|
44
|
-
function observeStreamedHandler(
|
|
45
|
-
promise: Promise<ReactNode>,
|
|
46
|
-
segmentId: string,
|
|
47
|
-
segmentType: string,
|
|
48
|
-
pathname?: string,
|
|
49
|
-
routeKey?: string,
|
|
50
|
-
params?: Record<string, string>,
|
|
51
|
-
): void {
|
|
52
|
-
let routerCtx;
|
|
53
|
-
try {
|
|
54
|
-
routerCtx = getRouterContext();
|
|
55
|
-
} catch {
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
if (!routerCtx?.telemetry) return;
|
|
59
|
-
const sink = resolveSink(routerCtx.telemetry);
|
|
60
|
-
const reqId = routerCtx.requestId;
|
|
61
|
-
promise.catch((err: unknown) => {
|
|
62
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
63
|
-
safeEmit(sink, {
|
|
64
|
-
type: "handler.error",
|
|
65
|
-
timestamp: performance.now(),
|
|
66
|
-
requestId: reqId,
|
|
67
|
-
segmentId,
|
|
68
|
-
segmentType,
|
|
69
|
-
error: errorObj,
|
|
70
|
-
handledByBoundary: true,
|
|
71
|
-
pathname,
|
|
72
|
-
routeKey,
|
|
73
|
-
params,
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
}
|
|
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";
|
|
77
40
|
|
|
78
41
|
// ---------------------------------------------------------------------------
|
|
79
42
|
// Fresh path (full match, no revalidation)
|
|
@@ -96,12 +59,16 @@ export async function resolveLoaders<TEnv>(
|
|
|
96
59
|
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
97
60
|
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
98
61
|
const loadingDisabled = hasLoading && entry.loading === false;
|
|
99
|
-
|
|
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);
|
|
100
69
|
|
|
101
70
|
if (!loadingDisabled) {
|
|
102
71
|
// Streaming loaders: promises kick off now, settle during RSC serialization.
|
|
103
|
-
// Record each loader as a marker span showing it was invoked.
|
|
104
|
-
const loaderStart = performance.now();
|
|
105
72
|
const segments = loaderEntries.map((loaderEntry, i) => {
|
|
106
73
|
const { loader } = loaderEntry;
|
|
107
74
|
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
@@ -114,53 +81,63 @@ export async function resolveLoaders<TEnv>(
|
|
|
114
81
|
params: ctx.params,
|
|
115
82
|
loaderId: loader.$$id,
|
|
116
83
|
loaderData: deps.wrapLoaderPromise(
|
|
117
|
-
|
|
84
|
+
runInsideLoaderScope(() =>
|
|
85
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
86
|
+
),
|
|
118
87
|
entry,
|
|
119
88
|
segmentId,
|
|
120
89
|
ctx.pathname,
|
|
90
|
+
errorContext,
|
|
121
91
|
),
|
|
122
92
|
belongsToRoute,
|
|
123
93
|
};
|
|
124
94
|
});
|
|
125
|
-
|
|
126
|
-
const dur = performance.now() - loaderStart;
|
|
127
|
-
for (const seg of segments) {
|
|
128
|
-
appendMetric(ms, `loader:${seg.loaderId}`, loaderStart, dur, 2);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
95
|
+
|
|
131
96
|
return segments;
|
|
132
97
|
}
|
|
133
98
|
|
|
134
99
|
// Loading disabled: still start all loaders in parallel, but only emit
|
|
135
100
|
// settled promises so handlers don't stream loading placeholders.
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
)
|
|
141
|
-
|
|
142
|
-
|
|
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));
|
|
143
124
|
|
|
144
125
|
return loaderEntries.map((loaderEntry, i) => {
|
|
145
126
|
const { loader } = loaderEntry;
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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.
|
|
150
132
|
return {
|
|
151
|
-
id: segmentId,
|
|
133
|
+
id: pending.segmentId,
|
|
152
134
|
namespace: entry.id,
|
|
153
135
|
type: "loader" as const,
|
|
154
136
|
index: i,
|
|
155
137
|
component: null,
|
|
156
138
|
params: ctx.params,
|
|
157
139
|
loaderId: loader.$$id,
|
|
158
|
-
loaderData:
|
|
159
|
-
pendingLoaderData[i]!,
|
|
160
|
-
entry,
|
|
161
|
-
segmentId,
|
|
162
|
-
ctx.pathname,
|
|
163
|
-
),
|
|
140
|
+
loaderData: pending.wrapped,
|
|
164
141
|
belongsToRoute,
|
|
165
142
|
};
|
|
166
143
|
});
|
|
@@ -172,6 +149,15 @@ export async function resolveLoaders<TEnv>(
|
|
|
172
149
|
export interface ResolveSegmentOptions {
|
|
173
150
|
/** When true, skip resolveLoaders() calls (used for pre-rendering) */
|
|
174
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;
|
|
175
161
|
}
|
|
176
162
|
|
|
177
163
|
/**
|
|
@@ -213,7 +199,11 @@ export async function resolveSegment<TEnv>(
|
|
|
213
199
|
index: 0,
|
|
214
200
|
component,
|
|
215
201
|
loading: entry.loading === false ? null : entry.loading,
|
|
216
|
-
transition:
|
|
202
|
+
transition: applyViewTransitionDefault(
|
|
203
|
+
entry.transition,
|
|
204
|
+
deps.viewTransitionDefault,
|
|
205
|
+
entry.shortCode,
|
|
206
|
+
),
|
|
217
207
|
params,
|
|
218
208
|
belongsToRoute: false,
|
|
219
209
|
layoutName: entry.id,
|
|
@@ -273,10 +263,18 @@ export async function resolveSegment<TEnv>(
|
|
|
273
263
|
entry.shortCode,
|
|
274
264
|
);
|
|
275
265
|
if (component === undefined) {
|
|
266
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
267
|
+
// the build handler. At build time (context.build === true), always
|
|
268
|
+
// use the build handler from entry.handler.
|
|
269
|
+
const handler =
|
|
270
|
+
!context.build && entry.liveHandler ? entry.liveHandler : entry.handler;
|
|
276
271
|
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
277
272
|
if (entry.loading) {
|
|
278
|
-
const result = handleHandlerResult(
|
|
273
|
+
const result = handleHandlerResult(
|
|
274
|
+
observeHandler(entry.id, handler, context),
|
|
275
|
+
);
|
|
279
276
|
if (result instanceof Promise) {
|
|
277
|
+
warnOnStreamedResponse(result, entry.id);
|
|
280
278
|
result.finally(doneRouteHandler).catch(() => {});
|
|
281
279
|
const tracked = deps.trackHandler(result, {
|
|
282
280
|
segmentId: entry.shortCode,
|
|
@@ -296,7 +294,9 @@ export async function resolveSegment<TEnv>(
|
|
|
296
294
|
component = result;
|
|
297
295
|
}
|
|
298
296
|
} else {
|
|
299
|
-
component = handleHandlerResult(
|
|
297
|
+
component = handleHandlerResult(
|
|
298
|
+
await observeHandler(entry.id, handler, context),
|
|
299
|
+
);
|
|
300
300
|
doneRouteHandler();
|
|
301
301
|
}
|
|
302
302
|
}
|
|
@@ -311,6 +311,7 @@ export async function resolveSegment<TEnv>(
|
|
|
311
311
|
deps,
|
|
312
312
|
options,
|
|
313
313
|
routeKey,
|
|
314
|
+
entry,
|
|
314
315
|
);
|
|
315
316
|
segments.push(...orphanSegments);
|
|
316
317
|
}
|
|
@@ -340,9 +341,13 @@ export async function resolveSegment<TEnv>(
|
|
|
340
341
|
namespace: entry.id,
|
|
341
342
|
type: "route",
|
|
342
343
|
index: 0,
|
|
343
|
-
component,
|
|
344
|
+
component: component ?? null,
|
|
344
345
|
loading: entry.loading === false ? null : entry.loading,
|
|
345
|
-
transition:
|
|
346
|
+
transition: applyViewTransitionDefault(
|
|
347
|
+
entry.transition,
|
|
348
|
+
deps.viewTransitionDefault,
|
|
349
|
+
entry.shortCode,
|
|
350
|
+
),
|
|
346
351
|
params,
|
|
347
352
|
belongsToRoute: true,
|
|
348
353
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
@@ -366,6 +371,9 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
366
371
|
deps: SegmentResolutionDeps<TEnv>,
|
|
367
372
|
options?: ResolveSegmentOptions,
|
|
368
373
|
routeKey?: string,
|
|
374
|
+
/** Parent route entry — its loaders are inherited by the layout so
|
|
375
|
+
* parallel slots inside this layout can access them via useLoader(). */
|
|
376
|
+
parentRouteEntry?: EntryData,
|
|
369
377
|
): Promise<ResolvedSegment[]> {
|
|
370
378
|
invariant(
|
|
371
379
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -381,6 +389,30 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
381
389
|
deps,
|
|
382
390
|
);
|
|
383
391
|
segments.push(...loaderSegments);
|
|
392
|
+
|
|
393
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
394
|
+
// can access them via useLoader(). Without this, the route's loaders
|
|
395
|
+
// are only in the route's OutletProvider (rendered as <Outlet /> content),
|
|
396
|
+
// which is a child — not a parent — of the layout's context.
|
|
397
|
+
if (
|
|
398
|
+
parentRouteEntry &&
|
|
399
|
+
parentRouteEntry.loader &&
|
|
400
|
+
parentRouteEntry.loader.length > 0 &&
|
|
401
|
+
Object.keys(orphan.parallel).length > 0
|
|
402
|
+
) {
|
|
403
|
+
const inheritedLoaders = await resolveLoaders(
|
|
404
|
+
parentRouteEntry,
|
|
405
|
+
context,
|
|
406
|
+
belongsToRoute,
|
|
407
|
+
deps,
|
|
408
|
+
orphan.shortCode,
|
|
409
|
+
);
|
|
410
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
411
|
+
for (const s of inheritedLoaders) {
|
|
412
|
+
s._inherited = true;
|
|
413
|
+
}
|
|
414
|
+
segments.push(...inheritedLoaders);
|
|
415
|
+
}
|
|
384
416
|
}
|
|
385
417
|
|
|
386
418
|
// Handler-first: orphan layout handler executes before its parallels
|
|
@@ -399,7 +431,11 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
399
431
|
belongsToRoute,
|
|
400
432
|
layoutName: orphan.id,
|
|
401
433
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
402
|
-
transition:
|
|
434
|
+
transition: applyViewTransitionDefault(
|
|
435
|
+
orphan.transition,
|
|
436
|
+
deps.viewTransitionDefault,
|
|
437
|
+
orphan.shortCode,
|
|
438
|
+
),
|
|
403
439
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
404
440
|
});
|
|
405
441
|
|
|
@@ -471,6 +507,14 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
471
507
|
if (handler === undefined) {
|
|
472
508
|
continue;
|
|
473
509
|
}
|
|
510
|
+
// Pin `_currentSegmentId` to the slot's own id so handle pushes from
|
|
511
|
+
// inside the slot handler get their own bucket in the HandleStore.
|
|
512
|
+
// Parent-keying would collapse them into the parent layout's bucket;
|
|
513
|
+
// the partial-update merge then replaces the parent's bucket on a
|
|
514
|
+
// slot-only revalidation and drops layout-pushed Meta/Breadcrumbs.
|
|
515
|
+
// filterSegmentOrder() retains slot ids so the client preserves them.
|
|
516
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
517
|
+
`${parentShortCode}.${slot}`;
|
|
474
518
|
const doneParallelHandler = track(
|
|
475
519
|
`handler:${parallelEntry.id}.${slot}`,
|
|
476
520
|
2,
|
|
@@ -479,7 +523,9 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
479
523
|
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
480
524
|
if (hasLoadingFallback) {
|
|
481
525
|
const result =
|
|
482
|
-
typeof handler === "function"
|
|
526
|
+
typeof handler === "function"
|
|
527
|
+
? observeHandler(`${parallelEntry.id}.${slot}`, handler, context)
|
|
528
|
+
: handler;
|
|
483
529
|
if (result instanceof Promise) {
|
|
484
530
|
result.finally(doneParallelHandler).catch(() => {});
|
|
485
531
|
const tracked = deps.trackHandler(result, {
|
|
@@ -501,7 +547,13 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
501
547
|
}
|
|
502
548
|
} else {
|
|
503
549
|
component =
|
|
504
|
-
typeof handler === "function"
|
|
550
|
+
typeof handler === "function"
|
|
551
|
+
? await observeHandler(
|
|
552
|
+
`${parallelEntry.id}.${slot}`,
|
|
553
|
+
handler,
|
|
554
|
+
context,
|
|
555
|
+
)
|
|
556
|
+
: handler;
|
|
505
557
|
doneParallelHandler();
|
|
506
558
|
}
|
|
507
559
|
}
|
|
@@ -513,7 +565,11 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
513
565
|
index: 0,
|
|
514
566
|
component,
|
|
515
567
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
516
|
-
transition:
|
|
568
|
+
transition: applyViewTransitionDefault(
|
|
569
|
+
parallelEntry.transition,
|
|
570
|
+
deps.viewTransitionDefault,
|
|
571
|
+
`${parentShortCode}.${slot}`,
|
|
572
|
+
),
|
|
517
573
|
params,
|
|
518
574
|
slot,
|
|
519
575
|
belongsToRoute,
|
|
@@ -576,6 +632,13 @@ export async function resolveAllSegments<TEnv>(
|
|
|
576
632
|
} catch {}
|
|
577
633
|
|
|
578
634
|
for (const entry of entries) {
|
|
635
|
+
// Set ALS flag when entering a cache() boundary so that ctx.get()
|
|
636
|
+
// can guard non-cacheable variable reads. Also guards response-level
|
|
637
|
+
// side effects (headers.set). Persists for all descendant entries.
|
|
638
|
+
if (entry.type === "cache") {
|
|
639
|
+
const store = RangoContext.getStore();
|
|
640
|
+
if (store) store.insideCacheScope = true;
|
|
641
|
+
}
|
|
579
642
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
580
643
|
const resolvedSegments = await resolveWithErrorBoundary(
|
|
581
644
|
entry,
|
|
@@ -595,6 +658,7 @@ export async function resolveAllSegments<TEnv>(
|
|
|
595
658
|
deps,
|
|
596
659
|
{ request: safeRequest, url: context.url, routeKey, telemetry },
|
|
597
660
|
context.pathname,
|
|
661
|
+
options?.throwOnError,
|
|
598
662
|
);
|
|
599
663
|
doneEntry();
|
|
600
664
|
// Deduplicate by segment ID. include() scopes can produce entries that
|
|
@@ -620,20 +684,37 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
620
684
|
deps: SegmentResolutionDeps<TEnv>,
|
|
621
685
|
): Promise<ResolvedSegment[]> {
|
|
622
686
|
const loaderSegments: ResolvedSegment[] = [];
|
|
687
|
+
const seenIds = new Set<string>();
|
|
623
688
|
|
|
624
689
|
async function collectEntryLoaders(
|
|
625
690
|
entry: EntryData,
|
|
626
691
|
belongsToRoute: boolean,
|
|
627
692
|
shortCodeOverride?: string,
|
|
628
693
|
): Promise<void> {
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
694
|
+
// Skip if all loaders from this entry have already been resolved
|
|
695
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
696
|
+
const entryLoaders = entry.loader ?? [];
|
|
697
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
698
|
+
const allAlreadySeen =
|
|
699
|
+
entryLoaders.length > 0 &&
|
|
700
|
+
entryLoaders.every((le, i) =>
|
|
701
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
702
|
+
);
|
|
703
|
+
if (!allAlreadySeen) {
|
|
704
|
+
const segments = await resolveLoaders(
|
|
705
|
+
entry,
|
|
706
|
+
context,
|
|
707
|
+
belongsToRoute,
|
|
708
|
+
deps,
|
|
709
|
+
shortCodeOverride,
|
|
710
|
+
);
|
|
711
|
+
for (const seg of segments) {
|
|
712
|
+
if (!seenIds.has(seg.id)) {
|
|
713
|
+
seenIds.add(seg.id);
|
|
714
|
+
loaderSegments.push(seg);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
637
718
|
|
|
638
719
|
const seenParallelEntryIds = new Set<string>();
|
|
639
720
|
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
@@ -645,6 +726,30 @@ export async function resolveLoadersOnly<TEnv>(
|
|
|
645
726
|
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
646
727
|
for (const layoutEntry of entry.layout) {
|
|
647
728
|
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
729
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
730
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
731
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
732
|
+
if (
|
|
733
|
+
entry.type === "route" &&
|
|
734
|
+
entry.loader &&
|
|
735
|
+
entry.loader.length > 0 &&
|
|
736
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
737
|
+
) {
|
|
738
|
+
const inherited = await resolveLoaders(
|
|
739
|
+
entry,
|
|
740
|
+
context,
|
|
741
|
+
childBelongsToRoute,
|
|
742
|
+
deps,
|
|
743
|
+
layoutEntry.shortCode,
|
|
744
|
+
);
|
|
745
|
+
for (const seg of inherited) {
|
|
746
|
+
if (!seenIds.has(seg.id)) {
|
|
747
|
+
seenIds.add(seg.id);
|
|
748
|
+
seg._inherited = true;
|
|
749
|
+
loaderSegments.push(seg);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
648
753
|
}
|
|
649
754
|
}
|
|
650
755
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Error boundary segment creation
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type
|
|
11
|
+
import { createElement, type ReactNode } from "react";
|
|
12
12
|
import { DataNotFoundError } from "../../errors";
|
|
13
13
|
import {
|
|
14
14
|
createErrorInfo,
|
|
@@ -19,13 +19,48 @@ import {
|
|
|
19
19
|
import { getRequestContext } from "../../server/request-context.js";
|
|
20
20
|
import { DefaultErrorFallback } from "../../default-error-boundary.js";
|
|
21
21
|
import type { EntryData } from "../../server/context";
|
|
22
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
ResolvedSegment,
|
|
24
|
+
ErrorInfo,
|
|
25
|
+
HandlerContext,
|
|
26
|
+
InternalHandlerContext,
|
|
27
|
+
} from "../../types";
|
|
23
28
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
24
29
|
import { debugLog } from "../logging.js";
|
|
25
30
|
import { tryStaticLookup } from "./static-store.js";
|
|
31
|
+
import { observeHandler } from "../instrument.js";
|
|
26
32
|
import type { TelemetrySink } from "../telemetry.js";
|
|
27
33
|
import { resolveSink, safeEmit, getRequestId } from "../telemetry.js";
|
|
28
34
|
|
|
35
|
+
/** The errorContext shape wrapLoaderPromise expects as its 5th argument. */
|
|
36
|
+
type LoaderErrorContext<TEnv> = NonNullable<
|
|
37
|
+
Parameters<SegmentResolutionDeps<TEnv>["wrapLoaderPromise"]>[4]
|
|
38
|
+
>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build the errorContext passed to wrapLoaderPromise so a throwing DSL loader
|
|
42
|
+
* fires createRouter({ onError }) (phase "loader") and emits the loader.error
|
|
43
|
+
* telemetry event. wrapLoaderPromise only wires the onError/telemetry path when
|
|
44
|
+
* this 5th argument is present; every real call site previously omitted it, so
|
|
45
|
+
* loaders were the one phase whose failures were silently dropped (handlers,
|
|
46
|
+
* actions, routing, rendering, and fetchable loaders all reported correctly).
|
|
47
|
+
*
|
|
48
|
+
* The fields come off the handler context, which already carries the request,
|
|
49
|
+
* url, params, env, and (on the internal shape) the matched route name.
|
|
50
|
+
*/
|
|
51
|
+
export function buildLoaderErrorContext<TEnv>(
|
|
52
|
+
ctx: HandlerContext<any, TEnv>,
|
|
53
|
+
): LoaderErrorContext<TEnv> {
|
|
54
|
+
const internal = ctx as InternalHandlerContext<any, TEnv>;
|
|
55
|
+
return {
|
|
56
|
+
request: ctx.request,
|
|
57
|
+
url: ctx.url,
|
|
58
|
+
routeKey: internal._routeName,
|
|
59
|
+
params: ctx.params as Record<string, string>,
|
|
60
|
+
env: ctx.env,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
29
64
|
// ---------------------------------------------------------------------------
|
|
30
65
|
// Handler result processing
|
|
31
66
|
// ---------------------------------------------------------------------------
|
|
@@ -52,6 +87,40 @@ export function handleHandlerResult(
|
|
|
52
87
|
return result;
|
|
53
88
|
}
|
|
54
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Dev-only: warn when a handler on a route that declares loading() resolves or
|
|
92
|
+
* rejects with a Response (e.g. redirect()).
|
|
93
|
+
*
|
|
94
|
+
* On a non-loading route a returned/thrown Response short-circuits to an HTTP
|
|
95
|
+
* redirect. But when the route declares loading(), the handler result is
|
|
96
|
+
* streamed (not awaited at the resolution boundary), so the Response surfaces
|
|
97
|
+
* only during RSC serialization and is rendered into the stream instead of
|
|
98
|
+
* becoming a 302/308 — a silent failure mode. Issue redirects from middleware,
|
|
99
|
+
* a loader, or a synchronous handler return instead. Compiled out in production.
|
|
100
|
+
*/
|
|
101
|
+
export function warnOnStreamedResponse(
|
|
102
|
+
result: Promise<unknown>,
|
|
103
|
+
entryId: string,
|
|
104
|
+
): void {
|
|
105
|
+
if (process.env.NODE_ENV === "production") return;
|
|
106
|
+
// A Response can surface either as a rejection (handleHandlerResult rethrows a
|
|
107
|
+
// resolved Response) or as a resolved value (the raw parallel-slot handler is
|
|
108
|
+
// not run through handleHandlerResult). Check both so every streamed path is
|
|
109
|
+
// covered. Each handler is an independent observer; it does not consume the
|
|
110
|
+
// rejection for the trackHandler/observeStreamedHandler chains.
|
|
111
|
+
const check = (value: unknown) => {
|
|
112
|
+
if (value instanceof Response) {
|
|
113
|
+
console.warn(
|
|
114
|
+
`[rango] Handler for "${entryId}" returned a Response (e.g. ` +
|
|
115
|
+
`redirect()), but it declares loading(): the Response is rendered ` +
|
|
116
|
+
`into the RSC stream, NOT sent as an HTTP redirect. Issue redirects ` +
|
|
117
|
+
`from middleware, a loader, or a synchronous handler return.`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
result.then(check, check);
|
|
122
|
+
}
|
|
123
|
+
|
|
55
124
|
// ---------------------------------------------------------------------------
|
|
56
125
|
// Static handler interception
|
|
57
126
|
// ---------------------------------------------------------------------------
|
|
@@ -96,11 +165,16 @@ export async function resolveLayoutComponent<TEnv>(
|
|
|
96
165
|
entry: EntryData,
|
|
97
166
|
context: HandlerContext<any, TEnv>,
|
|
98
167
|
): Promise<ReactNode> {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
168
|
+
// Static/prerender hit: no handler runs, so emit no rango.handler span.
|
|
169
|
+
const staticComponent = await tryStaticHandler(entry, entry.shortCode);
|
|
170
|
+
if (staticComponent !== undefined) return staticComponent;
|
|
171
|
+
const handler = entry.handler;
|
|
172
|
+
if (typeof handler !== "function") return handler as ReactNode;
|
|
173
|
+
// Wrap ONLY the handler call in the rango.handler span (the perf metric is owned
|
|
174
|
+
// by track("handler:<id>") at the call site). handleHandlerResult stays OUTSIDE
|
|
175
|
+
// the span so a handler that returns a Response (redirect control flow, which it
|
|
176
|
+
// rethrows) is not recorded as a span error — mirrors the route-handler sites.
|
|
177
|
+
return handleHandlerResult(await observeHandler(entry.id, handler, context));
|
|
104
178
|
}
|
|
105
179
|
|
|
106
180
|
// ---------------------------------------------------------------------------
|
|
@@ -180,34 +254,39 @@ export function catchSegmentError<TEnv>(
|
|
|
180
254
|
|
|
181
255
|
if (error instanceof DataNotFoundError) {
|
|
182
256
|
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
257
|
+
// Fall back to router's notFound component, then a plain default
|
|
258
|
+
const notFoundOption = deps.notFoundComponent;
|
|
259
|
+
const defaultFallback =
|
|
260
|
+
typeof notFoundOption === "function"
|
|
261
|
+
? notFoundOption({ pathname: pathname ?? "" })
|
|
262
|
+
: (notFoundOption ?? createElement("h1", null, "Not Found"));
|
|
263
|
+
const effectiveNotFoundFallback = notFoundFallback ?? defaultFallback;
|
|
183
264
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
265
|
+
const notFoundInfo = createNotFoundInfo(
|
|
266
|
+
error,
|
|
267
|
+
entry.shortCode,
|
|
268
|
+
entry.type,
|
|
269
|
+
pathname,
|
|
270
|
+
);
|
|
191
271
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
272
|
+
reportError(true, {
|
|
273
|
+
notFound: true,
|
|
274
|
+
message: notFoundInfo.message,
|
|
275
|
+
});
|
|
196
276
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
277
|
+
debugLog("segment", "notFound boundary handled error", {
|
|
278
|
+
segmentId: entry.shortCode,
|
|
279
|
+
message: notFoundInfo.message,
|
|
280
|
+
});
|
|
201
281
|
|
|
202
|
-
|
|
282
|
+
setResponseStatus(404);
|
|
203
283
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
284
|
+
return createNotFoundSegment(
|
|
285
|
+
notFoundInfo,
|
|
286
|
+
effectiveNotFoundFallback,
|
|
287
|
+
entry,
|
|
288
|
+
params,
|
|
289
|
+
);
|
|
211
290
|
}
|
|
212
291
|
|
|
213
292
|
const fallback = deps.findNearestErrorBoundary(entry);
|
|
@@ -245,11 +324,17 @@ export async function resolveWithErrorBoundary<TEnv, TResult>(
|
|
|
245
324
|
deps: SegmentResolutionDeps<TEnv>,
|
|
246
325
|
report?: ErrorReportContext,
|
|
247
326
|
pathname?: string,
|
|
327
|
+
throwOnError?: boolean,
|
|
248
328
|
): Promise<TResult> {
|
|
249
329
|
try {
|
|
250
330
|
return await resolveFn();
|
|
251
331
|
} catch (error) {
|
|
252
332
|
if (error instanceof Response) throw error;
|
|
333
|
+
// Pre-render surfaces render failures to the build instead of baking the
|
|
334
|
+
// error boundary as a frozen 200 (issue #587). A `throw new Skip()` in a
|
|
335
|
+
// render fn also propagates here so the build can skip that URL rather than
|
|
336
|
+
// bake its error page. The live request path leaves throwOnError unset.
|
|
337
|
+
if (throwOnError) throw error;
|
|
253
338
|
const segment = catchSegmentError(
|
|
254
339
|
error,
|
|
255
340
|
entry,
|