@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3373 -1176
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +70 -5
- package/skills/router-setup/SKILL.md +65 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +131 -30
- package/src/browser/navigation-client.ts +186 -100
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +165 -112
- package/src/browser/prefetch/cache.ts +205 -62
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +295 -119
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +114 -24
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -72
- package/src/browser/types.ts +102 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +99 -13
- package/src/index.ts +139 -19
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +143 -69
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +52 -31
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +268 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +170 -276
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +266 -169
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +115 -35
- package/src/router/router-options.ts +172 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +162 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +351 -321
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +157 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +655 -529
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +122 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +131 -14
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +109 -74
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -115
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +29 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +236 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +224 -52
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +401 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +125 -71
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +214 -132
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- package/src/vite/plugins/performance-tracks.ts +64 -170
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +118 -114
- package/src/vite/router-discovery.ts +941 -142
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getParallelEntries,
|
|
15
15
|
getParallelSlotEntries,
|
|
16
16
|
type EntryData,
|
|
17
|
+
type ParallelEntryData,
|
|
17
18
|
} from "../../server/context";
|
|
18
19
|
import type {
|
|
19
20
|
HandlerContext,
|
|
@@ -34,55 +35,41 @@ import {
|
|
|
34
35
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
35
36
|
import {
|
|
36
37
|
handleHandlerResult,
|
|
38
|
+
warnOnStreamedResponse,
|
|
37
39
|
tryStaticHandler,
|
|
38
40
|
tryStaticSlot,
|
|
39
41
|
resolveLayoutComponent,
|
|
40
42
|
resolveWithErrorBoundary,
|
|
43
|
+
buildLoaderErrorContext,
|
|
41
44
|
} from "./helpers.js";
|
|
45
|
+
import { applyViewTransitionDefault } from "./view-transition-default.js";
|
|
42
46
|
import { getRouterContext } from "../router-context.js";
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
import { observeEvent, observeHandler } from "../instrument.js";
|
|
48
|
+
import { observeStreamedHandler } from "./streamed-handler-telemetry.js";
|
|
49
|
+
import {
|
|
50
|
+
track,
|
|
51
|
+
RangoContext,
|
|
52
|
+
runInsideLoaderScope,
|
|
53
|
+
} from "../../server/context.js";
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
56
|
+
* Trace a parallel slot that's being force-rendered on a full refetch (client
|
|
57
|
+
* has no cached state). User revalidate fns are bypassed in this case — see
|
|
58
|
+
* the call sites for the load-bearing rationale.
|
|
54
59
|
*/
|
|
55
|
-
function
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
segmentType: string,
|
|
59
|
-
pathname?: string,
|
|
60
|
-
routeKey?: string,
|
|
61
|
-
params?: Record<string, string>,
|
|
60
|
+
function traceFullRefetchedParallelSlot(
|
|
61
|
+
parallelId: string,
|
|
62
|
+
belongsToRoute: boolean,
|
|
62
63
|
): void {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
promise.catch((err: unknown) => {
|
|
73
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
74
|
-
safeEmit(sink, {
|
|
75
|
-
type: "handler.error",
|
|
76
|
-
timestamp: performance.now(),
|
|
77
|
-
requestId: reqId,
|
|
78
|
-
segmentId,
|
|
79
|
-
segmentType,
|
|
80
|
-
error: errorObj,
|
|
81
|
-
handledByBoundary: true,
|
|
82
|
-
pathname,
|
|
83
|
-
routeKey,
|
|
84
|
-
params,
|
|
85
|
-
});
|
|
64
|
+
if (!isTraceActive()) return;
|
|
65
|
+
pushRevalidationTraceEntry({
|
|
66
|
+
segmentId: parallelId,
|
|
67
|
+
segmentType: "parallel",
|
|
68
|
+
belongsToRoute,
|
|
69
|
+
source: "parallel",
|
|
70
|
+
defaultShouldRevalidate: true,
|
|
71
|
+
finalShouldRevalidate: true,
|
|
72
|
+
reason: "full-refetch",
|
|
86
73
|
});
|
|
87
74
|
}
|
|
88
75
|
|
|
@@ -101,23 +88,14 @@ function emitRevalidationDecision(
|
|
|
101
88
|
routeKey: string,
|
|
102
89
|
shouldRevalidate: boolean,
|
|
103
90
|
): void {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
type: "revalidation.decision",
|
|
113
|
-
timestamp: performance.now(),
|
|
114
|
-
requestId: routerCtx.requestId,
|
|
115
|
-
segmentId,
|
|
116
|
-
pathname,
|
|
117
|
-
routeKey,
|
|
118
|
-
shouldRevalidate,
|
|
119
|
-
});
|
|
120
|
-
}
|
|
91
|
+
observeEvent({
|
|
92
|
+
type: "revalidation.decision",
|
|
93
|
+
timestamp: performance.now(),
|
|
94
|
+
segmentId,
|
|
95
|
+
pathname,
|
|
96
|
+
routeKey,
|
|
97
|
+
shouldRevalidate,
|
|
98
|
+
});
|
|
121
99
|
}
|
|
122
100
|
|
|
123
101
|
// ---------------------------------------------------------------------------
|
|
@@ -222,6 +200,10 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
222
200
|
),
|
|
223
201
|
);
|
|
224
202
|
|
|
203
|
+
// Partial (revalidation) render path: a throwing DSL loader must still fire
|
|
204
|
+
// onError/loader.error. isPartial flags the reporting phase accordingly.
|
|
205
|
+
const errorContext = { ...buildLoaderErrorContext(ctx), isPartial: true };
|
|
206
|
+
|
|
225
207
|
const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
|
|
226
208
|
const segments: ResolvedSegment[] = loadersToRun.map(
|
|
227
209
|
({ loaderEntry, loader, segmentId, index }) => ({
|
|
@@ -233,10 +215,13 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
233
215
|
params: ctx.params,
|
|
234
216
|
loaderId: loader.$$id,
|
|
235
217
|
loaderData: deps.wrapLoaderPromise(
|
|
236
|
-
|
|
218
|
+
runInsideLoaderScope(() =>
|
|
219
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
220
|
+
),
|
|
237
221
|
entry,
|
|
238
222
|
segmentId,
|
|
239
223
|
ctx.pathname,
|
|
224
|
+
errorContext,
|
|
240
225
|
),
|
|
241
226
|
belongsToRoute,
|
|
242
227
|
}),
|
|
@@ -314,6 +299,39 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
314
299
|
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
315
300
|
for (const layoutEntry of entry.layout) {
|
|
316
301
|
await collectEntryLoaders(layoutEntry, childBelongsToRoute);
|
|
302
|
+
// Inherit route loaders for orphan layouts with parallels.
|
|
303
|
+
// Resolve directly — do NOT re-enter collectEntryLoaders with the
|
|
304
|
+
// route entry, as that would re-iterate route.layout and loop.
|
|
305
|
+
if (
|
|
306
|
+
entry.type === "route" &&
|
|
307
|
+
entry.loader &&
|
|
308
|
+
entry.loader.length > 0 &&
|
|
309
|
+
Object.keys(layoutEntry.parallel).length > 0
|
|
310
|
+
) {
|
|
311
|
+
const inherited = await resolveLoadersWithRevalidation(
|
|
312
|
+
entry,
|
|
313
|
+
context,
|
|
314
|
+
childBelongsToRoute,
|
|
315
|
+
clientSegmentIds,
|
|
316
|
+
prevParams,
|
|
317
|
+
request,
|
|
318
|
+
prevUrl,
|
|
319
|
+
nextUrl,
|
|
320
|
+
routeKey,
|
|
321
|
+
deps,
|
|
322
|
+
actionContext,
|
|
323
|
+
layoutEntry.shortCode,
|
|
324
|
+
stale,
|
|
325
|
+
);
|
|
326
|
+
for (const seg of inherited.segments) {
|
|
327
|
+
if (!seenIds.has(seg.id)) {
|
|
328
|
+
seenIds.add(seg.id);
|
|
329
|
+
seg._inherited = true;
|
|
330
|
+
allLoaderSegments.push(seg);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
allMatchedIds.push(...inherited.matchedIds);
|
|
334
|
+
}
|
|
317
335
|
}
|
|
318
336
|
}
|
|
319
337
|
|
|
@@ -366,6 +384,97 @@ export function buildEntryRevalidateMap(
|
|
|
366
384
|
return map;
|
|
367
385
|
}
|
|
368
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Resolve the component for a single parallel slot on the revalidation path.
|
|
389
|
+
* Pure component resolution shared verbatim by
|
|
390
|
+
* resolveParallelSegmentsWithRevalidation and the orphan-inlined loop in
|
|
391
|
+
* resolveOrphanLayoutWithRevalidation: try the static slot cache, else run the
|
|
392
|
+
* slot handler (pinning _currentSegmentId to the slot id so handle pushes land
|
|
393
|
+
* in the slot's own bucket, and wrapping a streamed handler). Returns the
|
|
394
|
+
* resolved component and whether the handler actually ran. Does NOT touch the
|
|
395
|
+
* revalidate-default policy (the caller decides shouldResolve, including the
|
|
396
|
+
* orphan-vs-main defaultOverride divergence) or loader-resolution ordering.
|
|
397
|
+
*/
|
|
398
|
+
async function resolveParallelSlotComponent<TEnv>(args: {
|
|
399
|
+
shouldResolve: boolean;
|
|
400
|
+
parallelEntry: ParallelEntryData;
|
|
401
|
+
slot: string;
|
|
402
|
+
parallelId: string;
|
|
403
|
+
handler:
|
|
404
|
+
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
405
|
+
| ReactNode
|
|
406
|
+
| undefined;
|
|
407
|
+
context: HandlerContext<any, TEnv>;
|
|
408
|
+
deps: SegmentResolutionDeps<TEnv>;
|
|
409
|
+
routeKey: string;
|
|
410
|
+
params: Record<string, string>;
|
|
411
|
+
}): Promise<{ component: ReactNode | undefined; handlerRan: boolean }> {
|
|
412
|
+
const {
|
|
413
|
+
shouldResolve,
|
|
414
|
+
parallelEntry,
|
|
415
|
+
slot,
|
|
416
|
+
parallelId,
|
|
417
|
+
handler,
|
|
418
|
+
context,
|
|
419
|
+
deps,
|
|
420
|
+
routeKey,
|
|
421
|
+
params,
|
|
422
|
+
} = args;
|
|
423
|
+
|
|
424
|
+
let component: ReactNode | undefined;
|
|
425
|
+
let handlerRan = false;
|
|
426
|
+
if (shouldResolve) {
|
|
427
|
+
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
428
|
+
// tryStaticSlot returning a value means the static cache supplied the
|
|
429
|
+
// component — handler did NOT run. handlerRan stays false.
|
|
430
|
+
}
|
|
431
|
+
if (component === undefined) {
|
|
432
|
+
const hasLoadingFallback =
|
|
433
|
+
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
434
|
+
if (!shouldResolve) {
|
|
435
|
+
component = null;
|
|
436
|
+
} else if (handler === undefined) {
|
|
437
|
+
// Handler evicted (production static slot) but static lookup missed.
|
|
438
|
+
// Nothing to render — use null so the client keeps its cached version.
|
|
439
|
+
component = null;
|
|
440
|
+
} else {
|
|
441
|
+
// Slot-keyed pushes — slot owns its own bucket, parent layout owns its
|
|
442
|
+
// own. On slot-only revalidations the partial merge updates only the
|
|
443
|
+
// slot's bucket; the parent's bucket stays intact.
|
|
444
|
+
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
445
|
+
parallelId;
|
|
446
|
+
handlerRan = true;
|
|
447
|
+
if (hasLoadingFallback) {
|
|
448
|
+
const result =
|
|
449
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
450
|
+
if (result instanceof Promise) {
|
|
451
|
+
warnOnStreamedResponse(result, parallelId);
|
|
452
|
+
const tracked = deps.trackHandler(result, {
|
|
453
|
+
segmentId: parallelId,
|
|
454
|
+
segmentType: "parallel",
|
|
455
|
+
});
|
|
456
|
+
observeStreamedHandler(
|
|
457
|
+
tracked,
|
|
458
|
+
parallelId,
|
|
459
|
+
"parallel",
|
|
460
|
+
context.pathname,
|
|
461
|
+
routeKey,
|
|
462
|
+
params,
|
|
463
|
+
);
|
|
464
|
+
component = tracked as ReactNode;
|
|
465
|
+
} else {
|
|
466
|
+
component = result as ReactNode;
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
component =
|
|
470
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return { component, handlerRan };
|
|
476
|
+
}
|
|
477
|
+
|
|
369
478
|
/**
|
|
370
479
|
* Resolve parallel segments with revalidation.
|
|
371
480
|
*/
|
|
@@ -383,9 +492,30 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
383
492
|
deps: SegmentResolutionDeps<TEnv>,
|
|
384
493
|
actionContext?: ActionContext,
|
|
385
494
|
stale?: boolean,
|
|
495
|
+
options?: {
|
|
496
|
+
/**
|
|
497
|
+
* Seed for an unknown parent-chain slot (slot not in clientSegmentIds) when
|
|
498
|
+
* there are no deciding revalidate fns. "type-derived" (default, main path):
|
|
499
|
+
* `belongsToRoute || isNewParent`. "force-render" (orphan path): always
|
|
500
|
+
* `true` — orphan parallels always belong to the route and must render
|
|
501
|
+
* unless the user opts out via revalidate(); the #482 blank-parent-chain-
|
|
502
|
+
* slot guard.
|
|
503
|
+
*/
|
|
504
|
+
parentChainDefault?: "type-derived" | "force-render";
|
|
505
|
+
/**
|
|
506
|
+
* When a slot's loaders are resolved relative to the slot segment push.
|
|
507
|
+
* "after" (default, main path) pushes the slot segment first; "before"
|
|
508
|
+
* (orphan path) resolves loaders first. This only changes the
|
|
509
|
+
* segments/matchedIds emission ORDER (the client reconciler is insensitive
|
|
510
|
+
* to it: loader sub-ids are filtered out and slots are re-grouped by parent).
|
|
511
|
+
*/
|
|
512
|
+
loaderOrder?: "after" | "before";
|
|
513
|
+
},
|
|
386
514
|
): Promise<SegmentRevalidationResult> {
|
|
387
515
|
const segments: ResolvedSegment[] = [];
|
|
388
516
|
const matchedIds: string[] = [];
|
|
517
|
+
const parentChainDefault = options?.parentChainDefault ?? "type-derived";
|
|
518
|
+
const loaderOrder = options?.loaderOrder ?? "after";
|
|
389
519
|
|
|
390
520
|
const resolvedParallelEntries = new Set<string>();
|
|
391
521
|
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
@@ -410,44 +540,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
410
540
|
|
|
411
541
|
const isFullRefetch = clientSegmentIds.size === 0;
|
|
412
542
|
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
543
|
+
|
|
544
|
+
// A slot's loaders (never cached) are deduped per parallel entry and
|
|
545
|
+
// emitted either before or after the slot segment per loaderOrder.
|
|
546
|
+
const resolveSlotLoaders = async () => {
|
|
547
|
+
if (resolvedParallelEntries.has(parallelEntry.id)) return;
|
|
548
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
549
|
+
parallelEntry,
|
|
550
|
+
context,
|
|
551
|
+
belongsToRoute,
|
|
552
|
+
clientSegmentIds,
|
|
553
|
+
prevParams,
|
|
554
|
+
request,
|
|
555
|
+
prevUrl,
|
|
556
|
+
nextUrl,
|
|
557
|
+
routeKey,
|
|
558
|
+
deps,
|
|
559
|
+
actionContext,
|
|
560
|
+
entry.shortCode,
|
|
561
|
+
stale,
|
|
562
|
+
);
|
|
563
|
+
segments.push(...loaderResult.segments);
|
|
564
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
565
|
+
resolvedParallelEntries.add(parallelEntry.id);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
if (loaderOrder === "before") {
|
|
569
|
+
await resolveSlotLoaders();
|
|
420
570
|
}
|
|
571
|
+
// Always announce the slot in matchedIds — it's unconditionally appended
|
|
572
|
+
// to `segments` below, and a segment present in segments but missing from
|
|
573
|
+
// matched lets the client prune it (then it's missing from clientSegmentIds
|
|
574
|
+
// on the next request, perpetuating the staleness).
|
|
575
|
+
matchedIds.push(parallelId);
|
|
421
576
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
}
|
|
435
|
-
return true;
|
|
436
|
-
}
|
|
577
|
+
let shouldResolve: boolean;
|
|
578
|
+
if (isFullRefetch) {
|
|
579
|
+
// Client has nothing cached — slot MUST render. User revalidate fns are
|
|
580
|
+
// bypassed here because returning false would leave the segment blank
|
|
581
|
+
// with no client-side fallback.
|
|
582
|
+
traceFullRefetchedParallelSlot(parallelId, belongsToRoute);
|
|
583
|
+
shouldResolve = true;
|
|
584
|
+
} else {
|
|
585
|
+
// For non-empty client sets, consult user revalidate fns. When the slot
|
|
586
|
+
// is unknown to the client, override the type-derived default so the
|
|
587
|
+
// soft chain seeds with the right "new segment" / "parent-chain" value.
|
|
588
|
+
let defaultOverride: { value: boolean; reason: string } | undefined;
|
|
437
589
|
if (!clientSegmentIds.has(parallelId)) {
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
finalShouldRevalidate: result,
|
|
447
|
-
reason: result ? "new-segment" : "skip-parent-chain",
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
return result;
|
|
590
|
+
const value =
|
|
591
|
+
parentChainDefault === "force-render"
|
|
592
|
+
? true
|
|
593
|
+
: belongsToRoute || isNewParent;
|
|
594
|
+
defaultOverride = {
|
|
595
|
+
value,
|
|
596
|
+
reason: value ? "new-segment" : "skip-parent-chain",
|
|
597
|
+
};
|
|
451
598
|
}
|
|
452
599
|
|
|
453
600
|
const dummySegment: ResolvedSegment = {
|
|
@@ -465,7 +612,7 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
465
612
|
: {}),
|
|
466
613
|
};
|
|
467
614
|
|
|
468
|
-
|
|
615
|
+
shouldResolve = await evaluateRevalidation({
|
|
469
616
|
segment: dummySegment,
|
|
470
617
|
prevParams,
|
|
471
618
|
getPrevSegment: null,
|
|
@@ -481,8 +628,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
481
628
|
actionContext,
|
|
482
629
|
stale,
|
|
483
630
|
traceSource: "parallel",
|
|
631
|
+
defaultOverride,
|
|
484
632
|
});
|
|
485
|
-
}
|
|
633
|
+
}
|
|
486
634
|
emitRevalidationDecision(
|
|
487
635
|
parallelId,
|
|
488
636
|
context.pathname,
|
|
@@ -490,44 +638,17 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
490
638
|
shouldResolve,
|
|
491
639
|
);
|
|
492
640
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
// Nothing to render — use null so the client keeps its cached version.
|
|
505
|
-
component = null;
|
|
506
|
-
} else if (hasLoadingFallback) {
|
|
507
|
-
const result =
|
|
508
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
509
|
-
if (result instanceof Promise) {
|
|
510
|
-
const tracked = deps.trackHandler(result, {
|
|
511
|
-
segmentId: parallelId,
|
|
512
|
-
segmentType: "parallel",
|
|
513
|
-
});
|
|
514
|
-
observeStreamedHandler(
|
|
515
|
-
tracked,
|
|
516
|
-
parallelId,
|
|
517
|
-
"parallel",
|
|
518
|
-
context.pathname,
|
|
519
|
-
routeKey,
|
|
520
|
-
params,
|
|
521
|
-
);
|
|
522
|
-
component = tracked as ReactNode;
|
|
523
|
-
} else {
|
|
524
|
-
component = result as ReactNode;
|
|
525
|
-
}
|
|
526
|
-
} else {
|
|
527
|
-
component =
|
|
528
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
529
|
-
}
|
|
530
|
-
}
|
|
641
|
+
const { component, handlerRan } = await resolveParallelSlotComponent({
|
|
642
|
+
shouldResolve,
|
|
643
|
+
parallelEntry,
|
|
644
|
+
slot,
|
|
645
|
+
parallelId,
|
|
646
|
+
handler,
|
|
647
|
+
context,
|
|
648
|
+
deps,
|
|
649
|
+
routeKey,
|
|
650
|
+
params,
|
|
651
|
+
});
|
|
531
652
|
|
|
532
653
|
segments.push({
|
|
533
654
|
id: parallelId,
|
|
@@ -536,9 +657,14 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
536
657
|
index: 0,
|
|
537
658
|
component,
|
|
538
659
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
539
|
-
transition:
|
|
660
|
+
transition: applyViewTransitionDefault(
|
|
661
|
+
parallelEntry.transition,
|
|
662
|
+
deps.viewTransitionDefault,
|
|
663
|
+
parallelId,
|
|
664
|
+
),
|
|
540
665
|
params,
|
|
541
666
|
slot,
|
|
667
|
+
_handlerRan: handlerRan,
|
|
542
668
|
belongsToRoute,
|
|
543
669
|
parallelName: `${parallelEntry.id}.${slot}`,
|
|
544
670
|
...(parallelEntry.mountPath
|
|
@@ -546,28 +672,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
546
672
|
: {}),
|
|
547
673
|
});
|
|
548
674
|
|
|
549
|
-
if (
|
|
550
|
-
|
|
675
|
+
if (loaderOrder === "after") {
|
|
676
|
+
await resolveSlotLoaders();
|
|
551
677
|
}
|
|
552
|
-
|
|
553
|
-
const loaderResult = await resolveLoadersWithRevalidation(
|
|
554
|
-
parallelEntry,
|
|
555
|
-
context,
|
|
556
|
-
belongsToRoute,
|
|
557
|
-
clientSegmentIds,
|
|
558
|
-
prevParams,
|
|
559
|
-
request,
|
|
560
|
-
prevUrl,
|
|
561
|
-
nextUrl,
|
|
562
|
-
routeKey,
|
|
563
|
-
deps,
|
|
564
|
-
actionContext,
|
|
565
|
-
entry.shortCode,
|
|
566
|
-
stale,
|
|
567
|
-
);
|
|
568
|
-
segments.push(...loaderResult.segments);
|
|
569
|
-
matchedIds.push(...loaderResult.matchedIds);
|
|
570
|
-
resolvedParallelEntries.add(parallelEntry.id);
|
|
571
678
|
}
|
|
572
679
|
|
|
573
680
|
return { segments, matchedIds };
|
|
@@ -593,6 +700,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
593
700
|
): Promise<{ segment: ResolvedSegment; matchedId: string }> {
|
|
594
701
|
const matchedId = entry.shortCode;
|
|
595
702
|
|
|
703
|
+
let handlerRan = false;
|
|
596
704
|
const component = await revalidate(
|
|
597
705
|
async () => {
|
|
598
706
|
const hasSegment = clientSegmentIds.has(entry.shortCode);
|
|
@@ -669,6 +777,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
669
777
|
return shouldRevalidate;
|
|
670
778
|
},
|
|
671
779
|
async () => {
|
|
780
|
+
handlerRan = true;
|
|
672
781
|
const doneHandler = track(`handler:${entry.id}`, 2);
|
|
673
782
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
674
783
|
entry.shortCode;
|
|
@@ -683,14 +792,26 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
683
792
|
return staticComponent;
|
|
684
793
|
}
|
|
685
794
|
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
795
|
+
// For Passthrough routes at runtime, use the live handler instead of
|
|
796
|
+
// the build handler. At build time (context.build === true), always
|
|
797
|
+
// use the build handler from routeEntry.handler.
|
|
798
|
+
const handler =
|
|
799
|
+
!context.build && routeEntry.liveHandler
|
|
800
|
+
? routeEntry.liveHandler
|
|
801
|
+
: routeEntry.handler;
|
|
686
802
|
if (!routeEntry.loading) {
|
|
687
|
-
const result = handleHandlerResult(
|
|
803
|
+
const result = handleHandlerResult(
|
|
804
|
+
await observeHandler(entry.id, handler, context),
|
|
805
|
+
);
|
|
688
806
|
doneHandler();
|
|
689
807
|
return result;
|
|
690
808
|
}
|
|
691
809
|
if (!actionContext) {
|
|
692
|
-
const result = handleHandlerResult(
|
|
810
|
+
const result = handleHandlerResult(
|
|
811
|
+
observeHandler(entry.id, handler, context),
|
|
812
|
+
);
|
|
693
813
|
if (result instanceof Promise) {
|
|
814
|
+
warnOnStreamedResponse(result, routeEntry.id);
|
|
694
815
|
result.finally(doneHandler).catch(() => {});
|
|
695
816
|
const tracked = deps.trackHandler(result, {
|
|
696
817
|
segmentId: entry.shortCode,
|
|
@@ -713,7 +834,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
713
834
|
entryId: entry.id,
|
|
714
835
|
});
|
|
715
836
|
const actionResult = handleHandlerResult(
|
|
716
|
-
await
|
|
837
|
+
await observeHandler(entry.id, handler, context),
|
|
717
838
|
);
|
|
718
839
|
doneHandler();
|
|
719
840
|
return {
|
|
@@ -738,13 +859,18 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
738
859
|
index: 0,
|
|
739
860
|
component: resolvedComponent,
|
|
740
861
|
loading: entry.loading === false ? null : entry.loading,
|
|
741
|
-
transition:
|
|
862
|
+
transition: applyViewTransitionDefault(
|
|
863
|
+
entry.transition,
|
|
864
|
+
deps.viewTransitionDefault,
|
|
865
|
+
entry.shortCode,
|
|
866
|
+
),
|
|
742
867
|
params,
|
|
743
868
|
belongsToRoute,
|
|
744
869
|
...(entry.type === "layout" || entry.type === "cache"
|
|
745
870
|
? { layoutName: entry.id }
|
|
746
871
|
: {}),
|
|
747
872
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
873
|
+
_handlerRan: handlerRan,
|
|
748
874
|
};
|
|
749
875
|
|
|
750
876
|
return { segment, matchedId };
|
|
@@ -763,7 +889,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
763
889
|
request: Request,
|
|
764
890
|
prevUrl: URL,
|
|
765
891
|
nextUrl: URL,
|
|
766
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
767
892
|
deps: SegmentResolutionDeps<TEnv>,
|
|
768
893
|
actionContext?: ActionContext,
|
|
769
894
|
stale?: boolean,
|
|
@@ -825,11 +950,11 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
825
950
|
prevUrl,
|
|
826
951
|
nextUrl,
|
|
827
952
|
routeKey,
|
|
828
|
-
loaderPromises,
|
|
829
953
|
true,
|
|
830
954
|
deps,
|
|
831
955
|
actionContext,
|
|
832
956
|
stale,
|
|
957
|
+
entry,
|
|
833
958
|
);
|
|
834
959
|
segments.push(...orphanResult.segments);
|
|
835
960
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -909,7 +1034,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
909
1034
|
prevUrl,
|
|
910
1035
|
nextUrl,
|
|
911
1036
|
routeKey,
|
|
912
|
-
loaderPromises,
|
|
913
1037
|
false,
|
|
914
1038
|
deps,
|
|
915
1039
|
actionContext,
|
|
@@ -936,11 +1060,12 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
936
1060
|
prevUrl: URL,
|
|
937
1061
|
nextUrl: URL,
|
|
938
1062
|
routeKey: string,
|
|
939
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
940
1063
|
belongsToRoute: boolean,
|
|
941
1064
|
deps: SegmentResolutionDeps<TEnv>,
|
|
942
1065
|
actionContext?: ActionContext,
|
|
943
1066
|
stale?: boolean,
|
|
1067
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
1068
|
+
parentRouteEntry?: EntryData,
|
|
944
1069
|
): Promise<SegmentRevalidationResult> {
|
|
945
1070
|
invariant(
|
|
946
1071
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -968,6 +1093,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
968
1093
|
segments.push(...loaderResult.segments);
|
|
969
1094
|
matchedIds.push(...loaderResult.matchedIds);
|
|
970
1095
|
|
|
1096
|
+
// Inherit parent route's loaders so parallel slots inside this layout
|
|
1097
|
+
// can access them via useLoader(). See resolveOrphanLayout in fresh.ts.
|
|
1098
|
+
if (
|
|
1099
|
+
parentRouteEntry &&
|
|
1100
|
+
parentRouteEntry.loader &&
|
|
1101
|
+
parentRouteEntry.loader.length > 0 &&
|
|
1102
|
+
Object.keys(orphan.parallel).length > 0
|
|
1103
|
+
) {
|
|
1104
|
+
const inheritedResult = await resolveLoadersWithRevalidation(
|
|
1105
|
+
parentRouteEntry,
|
|
1106
|
+
context,
|
|
1107
|
+
belongsToRoute,
|
|
1108
|
+
clientSegmentIds,
|
|
1109
|
+
prevParams,
|
|
1110
|
+
request,
|
|
1111
|
+
prevUrl,
|
|
1112
|
+
nextUrl,
|
|
1113
|
+
routeKey,
|
|
1114
|
+
deps,
|
|
1115
|
+
actionContext,
|
|
1116
|
+
orphan.shortCode,
|
|
1117
|
+
stale,
|
|
1118
|
+
);
|
|
1119
|
+
// Tag as inherited so buildMatchResult can deduplicate when safe
|
|
1120
|
+
for (const s of inheritedResult.segments) {
|
|
1121
|
+
s._inherited = true;
|
|
1122
|
+
}
|
|
1123
|
+
segments.push(...inheritedResult.segments);
|
|
1124
|
+
matchedIds.push(...inheritedResult.matchedIds);
|
|
1125
|
+
}
|
|
1126
|
+
|
|
971
1127
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
972
1128
|
// so ctx.set() values are visible to parallel children.
|
|
973
1129
|
matchedIds.push(orphan.shortCode);
|
|
@@ -1040,165 +1196,41 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1040
1196
|
belongsToRoute,
|
|
1041
1197
|
layoutName: orphan.id,
|
|
1042
1198
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
1043
|
-
transition:
|
|
1199
|
+
transition: applyViewTransitionDefault(
|
|
1200
|
+
orphan.transition,
|
|
1201
|
+
deps.viewTransitionDefault,
|
|
1202
|
+
orphan.shortCode,
|
|
1203
|
+
),
|
|
1044
1204
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1045
1205
|
});
|
|
1046
1206
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
)
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
resolvedParallelEntries.add(parallelEntry.id);
|
|
1075
|
-
}
|
|
1076
|
-
|
|
1077
|
-
const slots = parallelEntry.handler as Record<
|
|
1078
|
-
`@${string}`,
|
|
1079
|
-
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1080
|
-
| ReactNode
|
|
1081
|
-
>;
|
|
1082
|
-
// Handler may be undefined in production after static handler eviction.
|
|
1083
|
-
const handler = slots[slot];
|
|
1084
|
-
|
|
1085
|
-
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1086
|
-
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1087
|
-
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1088
|
-
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1089
|
-
matchedIds.push(parallelId);
|
|
1090
|
-
|
|
1091
|
-
const shouldResolve = await (async () => {
|
|
1092
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
1093
|
-
if (isTraceActive()) {
|
|
1094
|
-
pushRevalidationTraceEntry({
|
|
1095
|
-
segmentId: parallelId,
|
|
1096
|
-
segmentType: "parallel",
|
|
1097
|
-
belongsToRoute,
|
|
1098
|
-
source: "parallel",
|
|
1099
|
-
defaultShouldRevalidate: true,
|
|
1100
|
-
finalShouldRevalidate: true,
|
|
1101
|
-
reason: "new-segment",
|
|
1102
|
-
});
|
|
1103
|
-
}
|
|
1104
|
-
return true;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
const dummySegment: ResolvedSegment = {
|
|
1108
|
-
id: parallelId,
|
|
1109
|
-
namespace: parallelEntry.id,
|
|
1110
|
-
type: "parallel",
|
|
1111
|
-
index: 0,
|
|
1112
|
-
component: null as any,
|
|
1113
|
-
params,
|
|
1114
|
-
slot,
|
|
1115
|
-
belongsToRoute,
|
|
1116
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1117
|
-
...(parallelEntry.mountPath
|
|
1118
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1119
|
-
: {}),
|
|
1120
|
-
};
|
|
1121
|
-
|
|
1122
|
-
return await evaluateRevalidation({
|
|
1123
|
-
segment: dummySegment,
|
|
1124
|
-
prevParams,
|
|
1125
|
-
getPrevSegment: null,
|
|
1126
|
-
request,
|
|
1127
|
-
prevUrl,
|
|
1128
|
-
nextUrl,
|
|
1129
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1130
|
-
name: `revalidate${i}`,
|
|
1131
|
-
fn,
|
|
1132
|
-
})),
|
|
1133
|
-
routeKey,
|
|
1134
|
-
context,
|
|
1135
|
-
actionContext,
|
|
1136
|
-
stale,
|
|
1137
|
-
traceSource: "parallel",
|
|
1138
|
-
});
|
|
1139
|
-
})();
|
|
1140
|
-
emitRevalidationDecision(
|
|
1141
|
-
parallelId,
|
|
1142
|
-
context.pathname,
|
|
1143
|
-
routeKey,
|
|
1144
|
-
shouldResolve,
|
|
1145
|
-
);
|
|
1146
|
-
|
|
1147
|
-
let component: ReactNode | undefined;
|
|
1148
|
-
if (shouldResolve) {
|
|
1149
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1150
|
-
}
|
|
1151
|
-
if (component === undefined) {
|
|
1152
|
-
const hasLoadingFallback =
|
|
1153
|
-
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1154
|
-
if (!shouldResolve) {
|
|
1155
|
-
component = null;
|
|
1156
|
-
} else if (handler === undefined) {
|
|
1157
|
-
// Handler evicted (production static slot) but static lookup missed.
|
|
1158
|
-
component = null;
|
|
1159
|
-
} else if (hasLoadingFallback) {
|
|
1160
|
-
const result =
|
|
1161
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
1162
|
-
if (result instanceof Promise) {
|
|
1163
|
-
const tracked = deps.trackHandler(result, {
|
|
1164
|
-
segmentId: parallelId,
|
|
1165
|
-
segmentType: "parallel",
|
|
1166
|
-
});
|
|
1167
|
-
observeStreamedHandler(
|
|
1168
|
-
tracked,
|
|
1169
|
-
parallelId,
|
|
1170
|
-
"parallel",
|
|
1171
|
-
context.pathname,
|
|
1172
|
-
routeKey,
|
|
1173
|
-
params,
|
|
1174
|
-
);
|
|
1175
|
-
component = tracked as ReactNode;
|
|
1176
|
-
} else {
|
|
1177
|
-
component = result as ReactNode;
|
|
1178
|
-
}
|
|
1179
|
-
} else {
|
|
1180
|
-
component =
|
|
1181
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
segments.push({
|
|
1186
|
-
id: parallelId,
|
|
1187
|
-
namespace: parallelEntry.id,
|
|
1188
|
-
type: "parallel",
|
|
1189
|
-
index: 0,
|
|
1190
|
-
component,
|
|
1191
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1192
|
-
transition: parallelEntry.transition,
|
|
1193
|
-
params,
|
|
1194
|
-
slot,
|
|
1195
|
-
belongsToRoute,
|
|
1196
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1197
|
-
...(parallelEntry.mountPath
|
|
1198
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1199
|
-
: {}),
|
|
1200
|
-
});
|
|
1201
|
-
}
|
|
1207
|
+
// Resolve the orphan layout's parallel slots through the shared main-path
|
|
1208
|
+
// helper. The orphan policy is carried by explicit args, byte-for-byte:
|
|
1209
|
+
// - parentChainDefault "force-render": an unknown parent-chain slot seeds
|
|
1210
|
+
// `true` (orphan parallels always belong to the route — the #482 guard),
|
|
1211
|
+
// where the main path would seed `belongsToRoute || isNewParent`.
|
|
1212
|
+
// - loaderOrder "before": a slot's loaders are emitted before the slot
|
|
1213
|
+
// segment, matching the prior inlined order.
|
|
1214
|
+
// `entry.shortCode` inside the helper is `orphan.shortCode` (orphan is passed
|
|
1215
|
+
// as `entry`), so the parallel ids + loader shortCodeOverride are unchanged.
|
|
1216
|
+
const parallelResult = await resolveParallelSegmentsWithRevalidation(
|
|
1217
|
+
orphan,
|
|
1218
|
+
params,
|
|
1219
|
+
context,
|
|
1220
|
+
belongsToRoute,
|
|
1221
|
+
clientSegmentIds,
|
|
1222
|
+
prevParams,
|
|
1223
|
+
request,
|
|
1224
|
+
prevUrl,
|
|
1225
|
+
nextUrl,
|
|
1226
|
+
routeKey,
|
|
1227
|
+
deps,
|
|
1228
|
+
actionContext,
|
|
1229
|
+
stale,
|
|
1230
|
+
{ parentChainDefault: "force-render", loaderOrder: "before" },
|
|
1231
|
+
);
|
|
1232
|
+
segments.push(...parallelResult.segments);
|
|
1233
|
+
matchedIds.push(...parallelResult.matchedIds);
|
|
1202
1234
|
|
|
1203
1235
|
return { segments, matchedIds };
|
|
1204
1236
|
}
|
|
@@ -1216,7 +1248,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1216
1248
|
request: Request,
|
|
1217
1249
|
prevUrl: URL,
|
|
1218
1250
|
nextUrl: URL,
|
|
1219
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
1220
1251
|
actionContext: ActionContext | undefined,
|
|
1221
1252
|
interceptResult: { intercept: any; entry: EntryData } | null,
|
|
1222
1253
|
localRouteName: string,
|
|
@@ -1250,7 +1281,7 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1250
1281
|
|
|
1251
1282
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1252
1283
|
if (entry.type === "cache") {
|
|
1253
|
-
const store =
|
|
1284
|
+
const store = RangoContext.getStore();
|
|
1254
1285
|
if (store) store.insideCacheScope = true;
|
|
1255
1286
|
}
|
|
1256
1287
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
@@ -1268,7 +1299,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1268
1299
|
request,
|
|
1269
1300
|
prevUrl,
|
|
1270
1301
|
nextUrl,
|
|
1271
|
-
loaderPromises,
|
|
1272
1302
|
deps,
|
|
1273
1303
|
actionContext,
|
|
1274
1304
|
stale,
|