@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
|
@@ -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,54 +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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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";
|
|
49
54
|
|
|
50
55
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
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.
|
|
53
59
|
*/
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
segmentType: string,
|
|
58
|
-
pathname?: string,
|
|
59
|
-
routeKey?: string,
|
|
60
|
-
params?: Record<string, string>,
|
|
60
|
+
function traceFullRefetchedParallelSlot(
|
|
61
|
+
parallelId: string,
|
|
62
|
+
belongsToRoute: boolean,
|
|
61
63
|
): void {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
promise.catch((err: unknown) => {
|
|
72
|
-
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
73
|
-
safeEmit(sink, {
|
|
74
|
-
type: "handler.error",
|
|
75
|
-
timestamp: performance.now(),
|
|
76
|
-
requestId: reqId,
|
|
77
|
-
segmentId,
|
|
78
|
-
segmentType,
|
|
79
|
-
error: errorObj,
|
|
80
|
-
handledByBoundary: true,
|
|
81
|
-
pathname,
|
|
82
|
-
routeKey,
|
|
83
|
-
params,
|
|
84
|
-
});
|
|
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",
|
|
85
73
|
});
|
|
86
74
|
}
|
|
87
75
|
|
|
@@ -100,23 +88,14 @@ function emitRevalidationDecision(
|
|
|
100
88
|
routeKey: string,
|
|
101
89
|
shouldRevalidate: boolean,
|
|
102
90
|
): void {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
type: "revalidation.decision",
|
|
112
|
-
timestamp: performance.now(),
|
|
113
|
-
requestId: routerCtx.requestId,
|
|
114
|
-
segmentId,
|
|
115
|
-
pathname,
|
|
116
|
-
routeKey,
|
|
117
|
-
shouldRevalidate,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
91
|
+
observeEvent({
|
|
92
|
+
type: "revalidation.decision",
|
|
93
|
+
timestamp: performance.now(),
|
|
94
|
+
segmentId,
|
|
95
|
+
pathname,
|
|
96
|
+
routeKey,
|
|
97
|
+
shouldRevalidate,
|
|
98
|
+
});
|
|
120
99
|
}
|
|
121
100
|
|
|
122
101
|
// ---------------------------------------------------------------------------
|
|
@@ -221,6 +200,10 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
221
200
|
),
|
|
222
201
|
);
|
|
223
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
|
+
|
|
224
207
|
const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
|
|
225
208
|
const segments: ResolvedSegment[] = loadersToRun.map(
|
|
226
209
|
({ loaderEntry, loader, segmentId, index }) => ({
|
|
@@ -232,10 +215,13 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
232
215
|
params: ctx.params,
|
|
233
216
|
loaderId: loader.$$id,
|
|
234
217
|
loaderData: deps.wrapLoaderPromise(
|
|
235
|
-
|
|
218
|
+
runInsideLoaderScope(() =>
|
|
219
|
+
resolveLoaderData(loaderEntry, ctx, ctx.pathname),
|
|
220
|
+
),
|
|
236
221
|
entry,
|
|
237
222
|
segmentId,
|
|
238
223
|
ctx.pathname,
|
|
224
|
+
errorContext,
|
|
239
225
|
),
|
|
240
226
|
belongsToRoute,
|
|
241
227
|
}),
|
|
@@ -262,29 +248,46 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
262
248
|
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
263
249
|
const allLoaderSegments: ResolvedSegment[] = [];
|
|
264
250
|
const allMatchedIds: string[] = [];
|
|
251
|
+
const seenIds = new Set<string>();
|
|
265
252
|
|
|
266
253
|
async function collectEntryLoaders(
|
|
267
254
|
entry: EntryData,
|
|
268
255
|
belongsToRoute: boolean,
|
|
269
256
|
shortCodeOverride?: string,
|
|
270
257
|
): Promise<void> {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
258
|
+
// Skip if all loaders from this entry have already been resolved
|
|
259
|
+
// via a parent (e.g., cache boundary wrapping a layout with shared loaders).
|
|
260
|
+
const loaderEntries = entry.loader ?? [];
|
|
261
|
+
const sc = shortCodeOverride ?? entry.shortCode;
|
|
262
|
+
const allAlreadySeen =
|
|
263
|
+
loaderEntries.length > 0 &&
|
|
264
|
+
loaderEntries.every((le, i) =>
|
|
265
|
+
seenIds.has(`${sc}D${i}.${le.loader.$$id}`),
|
|
266
|
+
);
|
|
267
|
+
if (!allAlreadySeen) {
|
|
268
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
269
|
+
entry,
|
|
270
|
+
context,
|
|
271
|
+
belongsToRoute,
|
|
272
|
+
clientSegmentIds,
|
|
273
|
+
prevParams,
|
|
274
|
+
request,
|
|
275
|
+
prevUrl,
|
|
276
|
+
nextUrl,
|
|
277
|
+
routeKey,
|
|
278
|
+
deps,
|
|
279
|
+
actionContext,
|
|
280
|
+
shortCodeOverride,
|
|
281
|
+
stale,
|
|
282
|
+
);
|
|
283
|
+
for (const seg of segments) {
|
|
284
|
+
if (!seenIds.has(seg.id)) {
|
|
285
|
+
seenIds.add(seg.id);
|
|
286
|
+
allLoaderSegments.push(seg);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
allMatchedIds.push(...matchedIds);
|
|
290
|
+
}
|
|
288
291
|
|
|
289
292
|
const seenParallelEntryIds = new Set<string>();
|
|
290
293
|
for (const parallelEntry of getParallelEntries(entry.parallel)) {
|
|
@@ -296,6 +299,39 @@ export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
|
296
299
|
const childBelongsToRoute = belongsToRoute || entry.type === "route";
|
|
297
300
|
for (const layoutEntry of entry.layout) {
|
|
298
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
|
+
}
|
|
299
335
|
}
|
|
300
336
|
}
|
|
301
337
|
|
|
@@ -348,6 +384,97 @@ export function buildEntryRevalidateMap(
|
|
|
348
384
|
return map;
|
|
349
385
|
}
|
|
350
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
|
+
|
|
351
478
|
/**
|
|
352
479
|
* Resolve parallel segments with revalidation.
|
|
353
480
|
*/
|
|
@@ -365,9 +492,30 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
365
492
|
deps: SegmentResolutionDeps<TEnv>,
|
|
366
493
|
actionContext?: ActionContext,
|
|
367
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
|
+
},
|
|
368
514
|
): Promise<SegmentRevalidationResult> {
|
|
369
515
|
const segments: ResolvedSegment[] = [];
|
|
370
516
|
const matchedIds: string[] = [];
|
|
517
|
+
const parentChainDefault = options?.parentChainDefault ?? "type-derived";
|
|
518
|
+
const loaderOrder = options?.loaderOrder ?? "after";
|
|
371
519
|
|
|
372
520
|
const resolvedParallelEntries = new Set<string>();
|
|
373
521
|
for (const { slot, entry: parallelEntry } of getParallelSlotEntries(
|
|
@@ -392,44 +540,61 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
392
540
|
|
|
393
541
|
const isFullRefetch = clientSegmentIds.size === 0;
|
|
394
542
|
const isNewParent = !clientSegmentIds.has(entry.shortCode);
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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();
|
|
402
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);
|
|
403
576
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
return true;
|
|
418
|
-
}
|
|
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;
|
|
419
589
|
if (!clientSegmentIds.has(parallelId)) {
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
finalShouldRevalidate: result,
|
|
429
|
-
reason: result ? "new-segment" : "skip-parent-chain",
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
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
|
+
};
|
|
433
598
|
}
|
|
434
599
|
|
|
435
600
|
const dummySegment: ResolvedSegment = {
|
|
@@ -447,7 +612,7 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
447
612
|
: {}),
|
|
448
613
|
};
|
|
449
614
|
|
|
450
|
-
|
|
615
|
+
shouldResolve = await evaluateRevalidation({
|
|
451
616
|
segment: dummySegment,
|
|
452
617
|
prevParams,
|
|
453
618
|
getPrevSegment: null,
|
|
@@ -463,8 +628,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
463
628
|
actionContext,
|
|
464
629
|
stale,
|
|
465
630
|
traceSource: "parallel",
|
|
631
|
+
defaultOverride,
|
|
466
632
|
});
|
|
467
|
-
}
|
|
633
|
+
}
|
|
468
634
|
emitRevalidationDecision(
|
|
469
635
|
parallelId,
|
|
470
636
|
context.pathname,
|
|
@@ -472,44 +638,17 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
472
638
|
shouldResolve,
|
|
473
639
|
);
|
|
474
640
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
// Nothing to render — use null so the client keeps its cached version.
|
|
487
|
-
component = null;
|
|
488
|
-
} else if (hasLoadingFallback) {
|
|
489
|
-
const result =
|
|
490
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
491
|
-
if (result instanceof Promise) {
|
|
492
|
-
const tracked = deps.trackHandler(result, {
|
|
493
|
-
segmentId: parallelId,
|
|
494
|
-
segmentType: "parallel",
|
|
495
|
-
});
|
|
496
|
-
observeStreamedHandler(
|
|
497
|
-
tracked,
|
|
498
|
-
parallelId,
|
|
499
|
-
"parallel",
|
|
500
|
-
context.pathname,
|
|
501
|
-
routeKey,
|
|
502
|
-
params,
|
|
503
|
-
);
|
|
504
|
-
component = tracked as ReactNode;
|
|
505
|
-
} else {
|
|
506
|
-
component = result as ReactNode;
|
|
507
|
-
}
|
|
508
|
-
} else {
|
|
509
|
-
component =
|
|
510
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
511
|
-
}
|
|
512
|
-
}
|
|
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
|
+
});
|
|
513
652
|
|
|
514
653
|
segments.push({
|
|
515
654
|
id: parallelId,
|
|
@@ -518,9 +657,14 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
518
657
|
index: 0,
|
|
519
658
|
component,
|
|
520
659
|
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
521
|
-
transition:
|
|
660
|
+
transition: applyViewTransitionDefault(
|
|
661
|
+
parallelEntry.transition,
|
|
662
|
+
deps.viewTransitionDefault,
|
|
663
|
+
parallelId,
|
|
664
|
+
),
|
|
522
665
|
params,
|
|
523
666
|
slot,
|
|
667
|
+
_handlerRan: handlerRan,
|
|
524
668
|
belongsToRoute,
|
|
525
669
|
parallelName: `${parallelEntry.id}.${slot}`,
|
|
526
670
|
...(parallelEntry.mountPath
|
|
@@ -528,28 +672,9 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
528
672
|
: {}),
|
|
529
673
|
});
|
|
530
674
|
|
|
531
|
-
if (
|
|
532
|
-
|
|
675
|
+
if (loaderOrder === "after") {
|
|
676
|
+
await resolveSlotLoaders();
|
|
533
677
|
}
|
|
534
|
-
|
|
535
|
-
const loaderResult = await resolveLoadersWithRevalidation(
|
|
536
|
-
parallelEntry,
|
|
537
|
-
context,
|
|
538
|
-
belongsToRoute,
|
|
539
|
-
clientSegmentIds,
|
|
540
|
-
prevParams,
|
|
541
|
-
request,
|
|
542
|
-
prevUrl,
|
|
543
|
-
nextUrl,
|
|
544
|
-
routeKey,
|
|
545
|
-
deps,
|
|
546
|
-
actionContext,
|
|
547
|
-
entry.shortCode,
|
|
548
|
-
stale,
|
|
549
|
-
);
|
|
550
|
-
segments.push(...loaderResult.segments);
|
|
551
|
-
matchedIds.push(...loaderResult.matchedIds);
|
|
552
|
-
resolvedParallelEntries.add(parallelEntry.id);
|
|
553
678
|
}
|
|
554
679
|
|
|
555
680
|
return { segments, matchedIds };
|
|
@@ -575,6 +700,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
575
700
|
): Promise<{ segment: ResolvedSegment; matchedId: string }> {
|
|
576
701
|
const matchedId = entry.shortCode;
|
|
577
702
|
|
|
703
|
+
let handlerRan = false;
|
|
578
704
|
const component = await revalidate(
|
|
579
705
|
async () => {
|
|
580
706
|
const hasSegment = clientSegmentIds.has(entry.shortCode);
|
|
@@ -651,6 +777,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
651
777
|
return shouldRevalidate;
|
|
652
778
|
},
|
|
653
779
|
async () => {
|
|
780
|
+
handlerRan = true;
|
|
654
781
|
const doneHandler = track(`handler:${entry.id}`, 2);
|
|
655
782
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
656
783
|
entry.shortCode;
|
|
@@ -665,14 +792,26 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
665
792
|
return staticComponent;
|
|
666
793
|
}
|
|
667
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;
|
|
668
802
|
if (!routeEntry.loading) {
|
|
669
|
-
const result = handleHandlerResult(
|
|
803
|
+
const result = handleHandlerResult(
|
|
804
|
+
await observeHandler(entry.id, handler, context),
|
|
805
|
+
);
|
|
670
806
|
doneHandler();
|
|
671
807
|
return result;
|
|
672
808
|
}
|
|
673
809
|
if (!actionContext) {
|
|
674
|
-
const result = handleHandlerResult(
|
|
810
|
+
const result = handleHandlerResult(
|
|
811
|
+
observeHandler(entry.id, handler, context),
|
|
812
|
+
);
|
|
675
813
|
if (result instanceof Promise) {
|
|
814
|
+
warnOnStreamedResponse(result, routeEntry.id);
|
|
676
815
|
result.finally(doneHandler).catch(() => {});
|
|
677
816
|
const tracked = deps.trackHandler(result, {
|
|
678
817
|
segmentId: entry.shortCode,
|
|
@@ -695,7 +834,7 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
695
834
|
entryId: entry.id,
|
|
696
835
|
});
|
|
697
836
|
const actionResult = handleHandlerResult(
|
|
698
|
-
await
|
|
837
|
+
await observeHandler(entry.id, handler, context),
|
|
699
838
|
);
|
|
700
839
|
doneHandler();
|
|
701
840
|
return {
|
|
@@ -705,10 +844,12 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
705
844
|
() => null,
|
|
706
845
|
);
|
|
707
846
|
|
|
847
|
+
// Normalize void handlers (undefined) to null so the reconciler's
|
|
848
|
+
// component === null checks work consistently for both void and explicit null.
|
|
708
849
|
const resolvedComponent =
|
|
709
850
|
component && typeof component === "object" && "content" in component
|
|
710
|
-
? (component as { content: ReactNode }).content
|
|
711
|
-
: component;
|
|
851
|
+
? ((component as { content: ReactNode }).content ?? null)
|
|
852
|
+
: (component ?? null);
|
|
712
853
|
|
|
713
854
|
const segment: ResolvedSegment = {
|
|
714
855
|
id: entry.shortCode,
|
|
@@ -718,13 +859,18 @@ export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
|
718
859
|
index: 0,
|
|
719
860
|
component: resolvedComponent,
|
|
720
861
|
loading: entry.loading === false ? null : entry.loading,
|
|
721
|
-
transition:
|
|
862
|
+
transition: applyViewTransitionDefault(
|
|
863
|
+
entry.transition,
|
|
864
|
+
deps.viewTransitionDefault,
|
|
865
|
+
entry.shortCode,
|
|
866
|
+
),
|
|
722
867
|
params,
|
|
723
868
|
belongsToRoute,
|
|
724
869
|
...(entry.type === "layout" || entry.type === "cache"
|
|
725
870
|
? { layoutName: entry.id }
|
|
726
871
|
: {}),
|
|
727
872
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
873
|
+
_handlerRan: handlerRan,
|
|
728
874
|
};
|
|
729
875
|
|
|
730
876
|
return { segment, matchedId };
|
|
@@ -743,7 +889,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
743
889
|
request: Request,
|
|
744
890
|
prevUrl: URL,
|
|
745
891
|
nextUrl: URL,
|
|
746
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
747
892
|
deps: SegmentResolutionDeps<TEnv>,
|
|
748
893
|
actionContext?: ActionContext,
|
|
749
894
|
stale?: boolean,
|
|
@@ -805,11 +950,11 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
805
950
|
prevUrl,
|
|
806
951
|
nextUrl,
|
|
807
952
|
routeKey,
|
|
808
|
-
loaderPromises,
|
|
809
953
|
true,
|
|
810
954
|
deps,
|
|
811
955
|
actionContext,
|
|
812
956
|
stale,
|
|
957
|
+
entry,
|
|
813
958
|
);
|
|
814
959
|
segments.push(...orphanResult.segments);
|
|
815
960
|
matchedIds.push(...orphanResult.matchedIds);
|
|
@@ -889,7 +1034,6 @@ export async function resolveSegmentWithRevalidation<TEnv>(
|
|
|
889
1034
|
prevUrl,
|
|
890
1035
|
nextUrl,
|
|
891
1036
|
routeKey,
|
|
892
|
-
loaderPromises,
|
|
893
1037
|
false,
|
|
894
1038
|
deps,
|
|
895
1039
|
actionContext,
|
|
@@ -916,11 +1060,12 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
916
1060
|
prevUrl: URL,
|
|
917
1061
|
nextUrl: URL,
|
|
918
1062
|
routeKey: string,
|
|
919
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
920
1063
|
belongsToRoute: boolean,
|
|
921
1064
|
deps: SegmentResolutionDeps<TEnv>,
|
|
922
1065
|
actionContext?: ActionContext,
|
|
923
1066
|
stale?: boolean,
|
|
1067
|
+
/** Parent route entry — its loaders are inherited so parallel slots can access them. */
|
|
1068
|
+
parentRouteEntry?: EntryData,
|
|
924
1069
|
): Promise<SegmentRevalidationResult> {
|
|
925
1070
|
invariant(
|
|
926
1071
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -948,6 +1093,37 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
948
1093
|
segments.push(...loaderResult.segments);
|
|
949
1094
|
matchedIds.push(...loaderResult.matchedIds);
|
|
950
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
|
+
|
|
951
1127
|
// Handler-first: resolve orphan layout handler before its parallels
|
|
952
1128
|
// so ctx.set() values are visible to parallel children.
|
|
953
1129
|
matchedIds.push(orphan.shortCode);
|
|
@@ -1020,165 +1196,41 @@ export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
|
1020
1196
|
belongsToRoute,
|
|
1021
1197
|
layoutName: orphan.id,
|
|
1022
1198
|
loading: orphan.loading === false ? null : orphan.loading,
|
|
1023
|
-
transition:
|
|
1199
|
+
transition: applyViewTransitionDefault(
|
|
1200
|
+
orphan.transition,
|
|
1201
|
+
deps.viewTransitionDefault,
|
|
1202
|
+
orphan.shortCode,
|
|
1203
|
+
),
|
|
1024
1204
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1025
1205
|
});
|
|
1026
1206
|
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
)
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
resolvedParallelEntries.add(parallelEntry.id);
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
const slots = parallelEntry.handler as Record<
|
|
1058
|
-
`@${string}`,
|
|
1059
|
-
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1060
|
-
| ReactNode
|
|
1061
|
-
>;
|
|
1062
|
-
// Handler may be undefined in production after static handler eviction.
|
|
1063
|
-
const handler = slots[slot];
|
|
1064
|
-
|
|
1065
|
-
// Use orphan.shortCode (the parent layout) to match the SSR path
|
|
1066
|
-
// (resolveParallelEntry receives parentShortCode = orphan.shortCode).
|
|
1067
|
-
// Using parallelEntry.shortCode would generate IDs the client doesn't know about.
|
|
1068
|
-
const parallelId = `${orphan.shortCode}.${slot}`;
|
|
1069
|
-
matchedIds.push(parallelId);
|
|
1070
|
-
|
|
1071
|
-
const shouldResolve = await (async () => {
|
|
1072
|
-
if (!clientSegmentIds.has(parallelId)) {
|
|
1073
|
-
if (isTraceActive()) {
|
|
1074
|
-
pushRevalidationTraceEntry({
|
|
1075
|
-
segmentId: parallelId,
|
|
1076
|
-
segmentType: "parallel",
|
|
1077
|
-
belongsToRoute,
|
|
1078
|
-
source: "parallel",
|
|
1079
|
-
defaultShouldRevalidate: true,
|
|
1080
|
-
finalShouldRevalidate: true,
|
|
1081
|
-
reason: "new-segment",
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
return true;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
const dummySegment: ResolvedSegment = {
|
|
1088
|
-
id: parallelId,
|
|
1089
|
-
namespace: parallelEntry.id,
|
|
1090
|
-
type: "parallel",
|
|
1091
|
-
index: 0,
|
|
1092
|
-
component: null as any,
|
|
1093
|
-
params,
|
|
1094
|
-
slot,
|
|
1095
|
-
belongsToRoute,
|
|
1096
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1097
|
-
...(parallelEntry.mountPath
|
|
1098
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1099
|
-
: {}),
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1102
|
-
return await evaluateRevalidation({
|
|
1103
|
-
segment: dummySegment,
|
|
1104
|
-
prevParams,
|
|
1105
|
-
getPrevSegment: null,
|
|
1106
|
-
request,
|
|
1107
|
-
prevUrl,
|
|
1108
|
-
nextUrl,
|
|
1109
|
-
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1110
|
-
name: `revalidate${i}`,
|
|
1111
|
-
fn,
|
|
1112
|
-
})),
|
|
1113
|
-
routeKey,
|
|
1114
|
-
context,
|
|
1115
|
-
actionContext,
|
|
1116
|
-
stale,
|
|
1117
|
-
traceSource: "parallel",
|
|
1118
|
-
});
|
|
1119
|
-
})();
|
|
1120
|
-
emitRevalidationDecision(
|
|
1121
|
-
parallelId,
|
|
1122
|
-
context.pathname,
|
|
1123
|
-
routeKey,
|
|
1124
|
-
shouldResolve,
|
|
1125
|
-
);
|
|
1126
|
-
|
|
1127
|
-
let component: ReactNode | undefined;
|
|
1128
|
-
if (shouldResolve) {
|
|
1129
|
-
component = await tryStaticSlot(parallelEntry, slot, parallelId);
|
|
1130
|
-
}
|
|
1131
|
-
if (component === undefined) {
|
|
1132
|
-
const hasLoadingFallback =
|
|
1133
|
-
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
1134
|
-
if (!shouldResolve) {
|
|
1135
|
-
component = null;
|
|
1136
|
-
} else if (handler === undefined) {
|
|
1137
|
-
// Handler evicted (production static slot) but static lookup missed.
|
|
1138
|
-
component = null;
|
|
1139
|
-
} else if (hasLoadingFallback) {
|
|
1140
|
-
const result =
|
|
1141
|
-
typeof handler === "function" ? handler(context) : handler;
|
|
1142
|
-
if (result instanceof Promise) {
|
|
1143
|
-
const tracked = deps.trackHandler(result, {
|
|
1144
|
-
segmentId: parallelId,
|
|
1145
|
-
segmentType: "parallel",
|
|
1146
|
-
});
|
|
1147
|
-
observeStreamedHandler(
|
|
1148
|
-
tracked,
|
|
1149
|
-
parallelId,
|
|
1150
|
-
"parallel",
|
|
1151
|
-
context.pathname,
|
|
1152
|
-
routeKey,
|
|
1153
|
-
params,
|
|
1154
|
-
);
|
|
1155
|
-
component = tracked as ReactNode;
|
|
1156
|
-
} else {
|
|
1157
|
-
component = result as ReactNode;
|
|
1158
|
-
}
|
|
1159
|
-
} else {
|
|
1160
|
-
component =
|
|
1161
|
-
typeof handler === "function" ? await handler(context) : handler;
|
|
1162
|
-
}
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
segments.push({
|
|
1166
|
-
id: parallelId,
|
|
1167
|
-
namespace: parallelEntry.id,
|
|
1168
|
-
type: "parallel",
|
|
1169
|
-
index: 0,
|
|
1170
|
-
component,
|
|
1171
|
-
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1172
|
-
transition: parallelEntry.transition,
|
|
1173
|
-
params,
|
|
1174
|
-
slot,
|
|
1175
|
-
belongsToRoute,
|
|
1176
|
-
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1177
|
-
...(parallelEntry.mountPath
|
|
1178
|
-
? { mountPath: parallelEntry.mountPath }
|
|
1179
|
-
: {}),
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
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);
|
|
1182
1234
|
|
|
1183
1235
|
return { segments, matchedIds };
|
|
1184
1236
|
}
|
|
@@ -1196,7 +1248,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1196
1248
|
request: Request,
|
|
1197
1249
|
prevUrl: URL,
|
|
1198
1250
|
nextUrl: URL,
|
|
1199
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
1200
1251
|
actionContext: ActionContext | undefined,
|
|
1201
1252
|
interceptResult: { intercept: any; entry: EntryData } | null,
|
|
1202
1253
|
localRouteName: string,
|
|
@@ -1229,6 +1280,10 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1229
1280
|
}
|
|
1230
1281
|
|
|
1231
1282
|
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1283
|
+
if (entry.type === "cache") {
|
|
1284
|
+
const store = RangoContext.getStore();
|
|
1285
|
+
if (store) store.insideCacheScope = true;
|
|
1286
|
+
}
|
|
1232
1287
|
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
1233
1288
|
const resolved = await resolveWithErrorBoundary(
|
|
1234
1289
|
nonParallelEntry,
|
|
@@ -1244,7 +1299,6 @@ export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
|
1244
1299
|
request,
|
|
1245
1300
|
prevUrl,
|
|
1246
1301
|
nextUrl,
|
|
1247
|
-
loaderPromises,
|
|
1248
1302
|
deps,
|
|
1249
1303
|
actionContext,
|
|
1250
1304
|
stale,
|