@rangojs/router 0.24.3 → 0.25.0
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/README.md +1 -0
- package/dist/bin/rango.js +363 -39
- package/dist/testing/vitest.js +37 -4
- package/dist/types/browser/event-controller.d.ts +24 -5
- package/dist/types/browser/intercept-utils.d.ts +5 -2
- package/dist/types/browser/merge-segment-loaders.d.ts +0 -23
- package/dist/types/browser/navigation-transaction.d.ts +5 -5
- package/dist/types/browser/partial-update.d.ts +21 -13
- package/dist/types/browser/prefetch/cache.d.ts +8 -0
- package/dist/types/browser/rango-marker.d.ts +7 -0
- package/dist/types/browser/react/filter-segment-order.d.ts +10 -0
- package/dist/types/browser/react/use-handle.d.ts +20 -0
- package/dist/types/browser/react/use-href.d.ts +6 -0
- package/dist/types/browser/react/use-router.d.ts +6 -1
- package/dist/types/browser/response-adapter.d.ts +5 -3
- package/dist/types/browser/scroll-restoration.d.ts +9 -3
- package/dist/types/browser/segment-reconciler.d.ts +10 -13
- package/dist/types/browser/types.d.ts +38 -8
- package/dist/types/build/route-types/per-module-writer.d.ts +12 -0
- package/dist/types/cache/cf/cf-shell-frame.d.ts +2 -0
- package/dist/types/cache/types.d.ts +8 -0
- package/dist/types/client-urls/client-root.d.ts +4 -1
- package/dist/types/client-urls/client-urls.d.ts +28 -2
- package/dist/types/client-urls/navigation.d.ts +6 -8
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +20 -5
- package/dist/types/client-urls/types.d.ts +35 -17
- package/dist/types/client.d.ts +2 -2
- package/dist/types/client.rsc.d.ts +2 -2
- package/dist/types/decode-loader-results.d.ts +28 -7
- package/dist/types/href-client.d.ts +3 -1
- package/dist/types/index.rsc.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +2 -3
- package/dist/types/outlet-context.d.ts +7 -6
- package/dist/types/prefetch-markers.d.ts +14 -5
- package/dist/types/route-content-wrapper.d.ts +17 -14
- package/dist/types/route-definition/helpers-types.d.ts +7 -2
- package/dist/types/route-definition/resolve-handler-use.d.ts +6 -1
- package/dist/types/route-definition/rewrite.d.ts +48 -5
- package/dist/types/route-types.d.ts +2 -2
- package/dist/types/router/handler-context.d.ts +14 -2
- package/dist/types/router/intercept-resolution.d.ts +19 -46
- package/dist/types/router/lazy-includes.d.ts +7 -0
- package/dist/types/router/match-middleware/intercept-resolution.d.ts +2 -2
- package/dist/types/router/middleware.d.ts +0 -16
- package/dist/types/router/navigation-snapshot.d.ts +2 -0
- package/dist/types/router/prefetch-cache-ttl.d.ts +8 -11
- package/dist/types/router/revalidation.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +4 -16
- package/dist/types/router/router-interfaces.d.ts +0 -6
- package/dist/types/router/router-options.d.ts +4 -7
- package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +31 -8
- package/dist/types/router/segment-resolution/revalidation.d.ts +38 -4
- package/dist/types/router/segment-resolution.d.ts +1 -1
- package/dist/types/router/segment-wrappers.d.ts +3 -5
- package/dist/types/router/substitute-pattern-params.d.ts +19 -2
- package/dist/types/router/tracing.d.ts +2 -2
- package/dist/types/rsc/full-payload.d.ts +14 -2
- package/dist/types/rsc/helpers.d.ts +8 -0
- package/dist/types/rsc/manifest-init.d.ts +20 -0
- package/dist/types/rsc/prefetch-render-error.d.ts +22 -0
- package/dist/types/rsc/progressive-enhancement.d.ts +20 -0
- package/dist/types/rsc/rewrite-passes.d.ts +12 -2
- package/dist/types/rsc/server-action.d.ts +6 -0
- package/dist/types/rsc/shell-capture-constants.d.ts +10 -1
- package/dist/types/rsc/shell-capture.d.ts +7 -0
- package/dist/types/rsc/types.d.ts +5 -0
- package/dist/types/segment-system.d.ts +15 -0
- package/dist/types/server/context.d.ts +64 -4
- package/dist/types/server/request-context.d.ts +41 -4
- package/dist/types/server/request-headers.d.ts +12 -0
- package/dist/types/server/rewrite-trail.d.ts +8 -1
- package/dist/types/testing/e2e/index.d.ts +1 -1
- package/dist/types/testing/flight.entry.d.ts +1 -1
- package/dist/types/testing/index.d.ts +1 -1
- package/dist/types/testing/internal/client-urls-projection.d.ts +4 -3
- package/dist/types/testing/render-route.d.ts +21 -11
- package/dist/types/testing/run-loader.d.ts +7 -0
- package/dist/types/testing/run-middleware.d.ts +7 -0
- package/dist/types/testing/run-revalidate.d.ts +5 -4
- package/dist/types/testing/serve-shell-request.d.ts +41 -4
- package/dist/types/testing/shell-status.d.ts +17 -0
- package/dist/types/testing/vitest-stubs/version.d.ts +1 -0
- package/dist/types/testing/vitest.d.ts +10 -1
- package/dist/types/thenable-status.d.ts +0 -6
- package/dist/types/types/handler-context.d.ts +12 -1
- package/dist/types/types/loader-types.d.ts +4 -3
- package/dist/types/types/route-entry.d.ts +2 -1
- package/dist/types/urls/path-helper-types.d.ts +4 -2
- package/dist/types/urls/path-helper.d.ts +5 -0
- package/dist/types/vite/discovery/client-urls-projection.d.ts +55 -5
- package/dist/types/vite/discovery/state.d.ts +20 -0
- package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +28 -16
- package/dist/types/vite/plugins/hoist/source.d.ts +8 -7
- package/dist/types/vite/plugins/virtual-entries.d.ts +6 -0
- package/dist/types/vite/utils/prerender-utils.d.ts +7 -0
- package/dist/vite/index.js +6816 -6547
- package/package.json +1 -1
- package/skills/app-structure/SKILL.md +255 -0
- package/skills/app-structure/content-site.md +333 -0
- package/skills/app-structure/e-commerce.md +435 -0
- package/skills/app-structure/large-app.md +885 -0
- package/skills/app-structure/multi-app.md +280 -0
- package/skills/breadcrumbs/SKILL.md +6 -1
- package/skills/caching/SKILL.md +3 -1
- package/skills/catalog.json +12 -0
- package/skills/client-urls/SKILL.md +195 -32
- package/skills/composability/SKILL.md +10 -8
- package/skills/handler-use/SKILL.md +48 -9
- package/skills/hooks/handle-and-actions.md +104 -0
- package/skills/hooks/navigation.md +24 -10
- package/skills/hooks/urls.md +1 -1
- package/skills/i18n/SKILL.md +2 -1
- package/skills/intercept/SKILL.md +60 -29
- package/skills/layout/SKILL.md +6 -3
- package/skills/links/SKILL.md +151 -17
- package/skills/loader/SKILL.md +32 -10
- package/skills/middleware/SKILL.md +111 -15
- package/skills/observability/SKILL.md +11 -0
- package/skills/parallel/SKILL.md +5 -5
- package/skills/ppr/SKILL.md +43 -8
- package/skills/prerender/SKILL.md +1 -2
- package/skills/rango/SKILL.md +3 -0
- package/skills/response-routes/SKILL.md +4 -1
- package/skills/scripts/SKILL.md +12 -1
- package/skills/server-actions/SKILL.md +60 -20
- package/skills/testing/SKILL.md +1 -0
- package/skills/testing/client-components.md +13 -10
- package/skills/testing/loader.md +25 -24
- package/skills/testing/middleware.md +11 -11
- package/skills/testing/response-routes.md +4 -1
- package/skills/testing/serve-shell-request.md +85 -21
- package/skills/ux-architecture/SKILL.md +980 -0
- package/skills/view-transitions/SKILL.md +43 -10
- package/src/__augment-tests__/augmented.check.ts +28 -0
- package/src/browser/event-controller.ts +44 -18
- package/src/browser/intercept-utils.ts +3 -2
- package/src/browser/merge-segment-loaders.ts +0 -85
- package/src/browser/navigation-bridge.ts +119 -98
- package/src/browser/navigation-client.ts +20 -15
- package/src/browser/navigation-store.ts +4 -2
- package/src/browser/navigation-transaction.ts +9 -8
- package/src/browser/partial-update.ts +90 -32
- package/src/browser/prefetch/cache.ts +8 -0
- package/src/browser/prefetch/fetch.ts +13 -3
- package/src/browser/rango-marker.ts +13 -0
- package/src/browser/react/NavigationProvider.tsx +25 -7
- package/src/browser/react/filter-segment-order.ts +13 -0
- package/src/browser/react/use-action.ts +9 -15
- package/src/browser/react/use-handle.ts +63 -71
- package/src/browser/react/use-href.tsx +24 -5
- package/src/browser/react/use-link-status.ts +1 -1
- package/src/browser/react/use-navigation.ts +30 -40
- package/src/browser/react/use-reverse.ts +4 -17
- package/src/browser/react/use-router.ts +37 -6
- package/src/browser/react/use-segments.ts +8 -12
- package/src/browser/response-adapter.ts +11 -5
- package/src/browser/rsc-router.tsx +3 -1
- package/src/browser/scroll-restoration.ts +34 -20
- package/src/browser/segment-reconciler.ts +11 -29
- package/src/browser/server-action-bridge.ts +52 -16
- package/src/browser/types.ts +40 -8
- package/src/build/generate-manifest.ts +12 -1
- package/src/build/route-types/codegen.ts +6 -2
- package/src/build/route-types/per-module-writer.ts +94 -61
- package/src/cache/cache-runtime.ts +9 -0
- package/src/cache/cache-scope.ts +2 -1
- package/src/cache/cf/cf-cache-store.ts +2 -0
- package/src/cache/cf/cf-shell-frame.ts +3 -0
- package/src/cache/types.ts +8 -0
- package/src/cache/vercel/vercel-cache-store.ts +6 -1
- package/src/client-urls/client-root.tsx +10 -5
- package/src/client-urls/client-urls.ts +246 -166
- package/src/client-urls/navigation.ts +45 -38
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +286 -75
- package/src/client-urls/types.ts +40 -16
- package/src/client.rsc.tsx +11 -1
- package/src/client.tsx +9 -1
- package/src/decode-loader-results.ts +64 -31
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +1 -0
- package/src/index.ts +12 -4
- package/src/loader-redirect.tsx +2 -3
- package/src/outlet-context.ts +7 -6
- package/src/prefetch-markers.ts +15 -5
- package/src/reverse.ts +3 -3
- package/src/route-content-wrapper.tsx +19 -17
- package/src/route-definition/dsl-helpers.ts +27 -7
- package/src/route-definition/helpers-types.ts +7 -2
- package/src/route-definition/resolve-handler-use.ts +23 -10
- package/src/route-definition/rewrite.ts +86 -19
- package/src/route-types.ts +1 -2
- package/src/router/find-match.ts +37 -1
- package/src/router/handler-context.ts +85 -40
- package/src/router/intercept-resolution.ts +161 -307
- package/src/router/lazy-includes.ts +43 -27
- package/src/router/loader-resolution.ts +21 -1
- package/src/router/manifest.ts +6 -8
- package/src/router/match-api.ts +11 -1
- package/src/router/match-middleware/background-revalidation.ts +2 -4
- package/src/router/match-middleware/cache-lookup.ts +16 -20
- package/src/router/match-middleware/intercept-resolution.ts +41 -60
- package/src/router/match-result.ts +31 -2
- package/src/router/middleware.ts +6 -147
- package/src/router/navigation-snapshot.ts +38 -2
- package/src/router/prefetch-cache-ttl.ts +10 -18
- package/src/router/prerender-match.ts +2 -1
- package/src/router/revalidation.ts +24 -3
- package/src/router/router-context.ts +5 -18
- package/src/router/router-interfaces.ts +0 -7
- package/src/router/router-options.ts +4 -7
- package/src/router/segment-resolution/fresh.ts +6 -2
- package/src/router/segment-resolution/helpers.ts +7 -1
- package/src/router/segment-resolution/prefetch-deferral.ts +62 -12
- package/src/router/segment-resolution/revalidation.ts +75 -35
- package/src/router/segment-resolution.ts +0 -1
- package/src/router/segment-wrappers.ts +8 -13
- package/src/router/substitute-pattern-params.ts +37 -2
- package/src/router/tracing.ts +2 -2
- package/src/router.ts +2 -9
- package/src/rsc/full-payload.ts +26 -4
- package/src/rsc/handler.ts +83 -41
- package/src/rsc/helpers.ts +13 -0
- package/src/rsc/manifest-init.ts +56 -3
- package/src/rsc/prefetch-render-error.ts +38 -0
- package/src/rsc/progressive-enhancement.ts +161 -211
- package/src/rsc/rewrite-passes.ts +56 -9
- package/src/rsc/rsc-rendering.ts +79 -23
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture-constants.ts +22 -0
- package/src/rsc/shell-capture.ts +11 -0
- package/src/rsc/types.ts +5 -0
- package/src/segment-system.tsx +125 -119
- package/src/server/context.ts +96 -5
- package/src/server/handle-store.ts +3 -2
- package/src/server/request-context.ts +42 -3
- package/src/server/request-headers.ts +32 -0
- package/src/server/rewrite-trail.ts +55 -9
- package/src/ssr/ssr-root.tsx +6 -10
- package/src/testing/dispatch.ts +4 -5
- package/src/testing/e2e/index.ts +1 -0
- package/src/testing/flight.entry.ts +1 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/internal/client-urls-projection.ts +31 -11
- package/src/testing/render-route.tsx +83 -40
- package/src/testing/run-loader.ts +26 -2
- package/src/testing/run-middleware.ts +10 -0
- package/src/testing/run-revalidate.ts +13 -6
- package/src/testing/serve-shell-request.ts +77 -4
- package/src/testing/shell-status.ts +40 -0
- package/src/testing/vitest-stubs/version.ts +1 -0
- package/src/testing/vitest.ts +74 -7
- package/src/thenable-status.ts +0 -11
- package/src/types/handler-context.ts +12 -1
- package/src/types/loader-types.ts +4 -3
- package/src/types/route-entry.ts +2 -1
- package/src/urls/include-helper.ts +38 -11
- package/src/urls/path-helper-types.ts +4 -2
- package/src/urls/path-helper.ts +16 -3
- package/src/vite/discovery/client-urls-projection.ts +205 -16
- package/src/vite/discovery/discover-routers.ts +2 -2
- package/src/vite/discovery/prerender-collection.ts +2 -6
- package/src/vite/discovery/state.ts +26 -0
- package/src/vite/plugins/client-urls-revalidate-hoist.ts +190 -77
- package/src/vite/plugins/expose-internal-ids.ts +46 -40
- package/src/vite/plugins/hoist/source.ts +8 -7
- package/src/vite/plugins/transition-when-hoist.ts +1 -0
- package/src/vite/plugins/version-plugin.ts +7 -0
- package/src/vite/plugins/version.d.ts +7 -0
- package/src/vite/plugins/virtual-entries.ts +20 -0
- package/src/vite/rango.ts +4 -1
- package/src/vite/router-discovery.ts +39 -7
- package/src/vite/utils/prerender-utils.ts +17 -0
package/src/segment-system.tsx
CHANGED
|
@@ -11,7 +11,7 @@ import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
|
11
11
|
import type { ResolvedSegment, RootLayoutProps } from "./types.js";
|
|
12
12
|
import { isRenderableLoading } from "./types/boundaries.js";
|
|
13
13
|
import { isPlaceholder } from "./types/segments.js";
|
|
14
|
-
import { decodeLoaderResults } from "./decode-loader-results.js";
|
|
14
|
+
import { decodeLoaderResults, isLoaderError } from "./decode-loader-results.js";
|
|
15
15
|
import { LoaderRedirect } from "./loader-redirect.js";
|
|
16
16
|
import { invariant } from "./errors.js";
|
|
17
17
|
import {
|
|
@@ -26,10 +26,8 @@ import { INTERNAL_RANGO_DEBUG } from "./internal-debug.js";
|
|
|
26
26
|
import { INTERNAL_RANGO_SUSPENSE_AUDIT } from "./internal-suspense-audit.js";
|
|
27
27
|
import { getBoundaryContent } from "./segment-boundary-content.js";
|
|
28
28
|
import { asStream } from "./browser/defer.js";
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
getMemoizedLoaderPromise,
|
|
32
|
-
} from "./segment-loader-promise.js";
|
|
29
|
+
import { getMemoizedLoaderPromise } from "./segment-loader-promise.js";
|
|
30
|
+
import { loaderParentId } from "./browser/react/filter-segment-order.js";
|
|
33
31
|
import {
|
|
34
32
|
auditSegmentElement,
|
|
35
33
|
finishTreeAudit,
|
|
@@ -106,6 +104,60 @@ export function restoreParallelLoaderMarkers(
|
|
|
106
104
|
return nextSegments ?? segments;
|
|
107
105
|
}
|
|
108
106
|
|
|
107
|
+
/**
|
|
108
|
+
* An intercept's loaders arrive as loader segments of its slot
|
|
109
|
+
* (`${slot.id}D${i}.${$$id}`, router/intercept-resolution.ts). Folded back
|
|
110
|
+
* onto the slot before the tree walk, as one aggregate the slot's
|
|
111
|
+
* LoaderBoundary reads (client.tsx renderSlotContent): walked as loaders they
|
|
112
|
+
* would attach to the slot's parent, whose subtree (the page under the modal)
|
|
113
|
+
* would change shape and remount. Any other loader segment passes through; a
|
|
114
|
+
* list with nothing to fold is returned as it is, synchronously.
|
|
115
|
+
* The slot is a new object (rule 6, suspense-contract.md); its aggregate is
|
|
116
|
+
* memoized on the loaders' data (rule 1). Settled before the commit when
|
|
117
|
+
* nothing in it is deferred and the slot has no loading() or the lane settles
|
|
118
|
+
* loaders (forceAwait, action): a modal with no loading() commits whole, as
|
|
119
|
+
* when the server awaited its loaders.
|
|
120
|
+
*/
|
|
121
|
+
export function foldInterceptLoaders(
|
|
122
|
+
segments: ResolvedSegment[],
|
|
123
|
+
settleLoaders: boolean,
|
|
124
|
+
): ResolvedSegment[] | Promise<ResolvedSegment[]> {
|
|
125
|
+
const loadersBySlot = new Map<string, ResolvedSegment[]>();
|
|
126
|
+
const rest: ResolvedSegment[] = [];
|
|
127
|
+
for (const segment of segments) {
|
|
128
|
+
// Only a slot's loader has a slot id, `${shortCode}.@${name}`, as parent.
|
|
129
|
+
const parentId =
|
|
130
|
+
segment.type === "loader" && segment.id.includes(".@")
|
|
131
|
+
? loaderParentId(segment.id)
|
|
132
|
+
: "";
|
|
133
|
+
if (parentId.includes(".@")) pushToGroup(loadersBySlot, parentId, segment);
|
|
134
|
+
else rest.push(segment);
|
|
135
|
+
}
|
|
136
|
+
if (loadersBySlot.size === 0) return segments;
|
|
137
|
+
const fold = async (
|
|
138
|
+
slot: ResolvedSegment,
|
|
139
|
+
loaders: ResolvedSegment[],
|
|
140
|
+
): Promise<ResolvedSegment> => {
|
|
141
|
+
const aggregate = getMemoizedLoaderPromise(loaders);
|
|
142
|
+
return {
|
|
143
|
+
...slot,
|
|
144
|
+
loaderIds: loaders.map((l) => l.loaderId!),
|
|
145
|
+
loaderDataPromise: settlesLoaders(
|
|
146
|
+
loaders,
|
|
147
|
+
settleLoaders || !isRenderableLoading(slot.loading),
|
|
148
|
+
)
|
|
149
|
+
? await aggregate
|
|
150
|
+
: aggregate,
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
return Promise.all(
|
|
154
|
+
rest.map((slot) => {
|
|
155
|
+
const loaders = loadersBySlot.get(slot.id);
|
|
156
|
+
return loaders ? fold(slot, loaders) : slot;
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
109
161
|
/**
|
|
110
162
|
* Options for renderSegments
|
|
111
163
|
*/
|
|
@@ -252,19 +304,21 @@ function wrapDefaultOutletContent(
|
|
|
252
304
|
*
|
|
253
305
|
* The settled results also go through decodeLoaderResults here: a settled
|
|
254
306
|
* redirect()/notFound() (or an error with an errorBoundary() fallback) must
|
|
255
|
-
* be resolved to its node while the tree is built,
|
|
256
|
-
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
307
|
+
* be resolved to its node while the tree is built, because the read-site
|
|
308
|
+
* throw has no boundary on the document lane — flagged content renders
|
|
309
|
+
* without Suspense and a layout reader sits above every boundary, so the
|
|
310
|
+
* throw is a Fizz shell error (500). `settledFallback` is that node; the
|
|
311
|
+
* caller plants it and, for a redirect, replaces the whole page (an ancestor
|
|
312
|
+
* reading the loader would still throw).
|
|
261
313
|
*
|
|
262
|
-
* `settle` is
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
314
|
+
* `settle` is every client lane that commits whole (forceAwait: back/forward,
|
|
315
|
+
* a cached or fully-prefetched click, stale revalidation, leaving an
|
|
316
|
+
* intercept; and the action lane): every loader but a deferred one
|
|
317
|
+
* (`prefetch: false`, which keeps its hole for the read site to suspend on)
|
|
318
|
+
* is settled like a flagged one, so the commit shows no fallback. A loader
|
|
319
|
+
* error is left to its read site there, its errorBoundary() fallback riding
|
|
320
|
+
* the throw, as on a navigation: the nearest boundary to the read catches it
|
|
321
|
+
* (#1129, dc-t65). notFound()/redirect() keep their build-time node.
|
|
268
322
|
*/
|
|
269
323
|
async function buildLoaderStreams(
|
|
270
324
|
loaders: ResolvedSegment[],
|
|
@@ -283,34 +337,31 @@ async function buildLoaderStreams(
|
|
|
283
337
|
const value = await l.loaderData;
|
|
284
338
|
streams[l.loaderId!] = value;
|
|
285
339
|
if (l.awaitBeforeFlush) (awaitedIds ??= []).push(l.loaderId!);
|
|
340
|
+
if (settle && isLoaderError(value)) continue;
|
|
286
341
|
(settledIds ??= []).push(l.loaderId!);
|
|
287
342
|
(settledValues ??= []).push(value);
|
|
288
343
|
} else {
|
|
289
344
|
streams[l.loaderId!] = l.loaderData;
|
|
290
345
|
}
|
|
291
346
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
undefined;
|
|
298
|
-
} catch (error) {
|
|
299
|
-
if (!settle) throw error;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
347
|
+
// Only the document lane can throw here: a settle lane left its errors out.
|
|
348
|
+
const settledFallback = settledIds
|
|
349
|
+
? (decodeLoaderResults(settledValues!, settledIds).errorFallback ??
|
|
350
|
+
undefined)
|
|
351
|
+
: undefined;
|
|
302
352
|
return { streams, awaitedIds, settledFallback };
|
|
303
353
|
}
|
|
304
354
|
|
|
305
355
|
/**
|
|
306
|
-
*
|
|
356
|
+
* Whether a loader set is settled before the commit: its lane asks for it
|
|
357
|
+
* and nothing in it is deferred. A deferred loader's (`prefetch: false`)
|
|
307
358
|
* `loaderData` is a hole (browser/defer.ts) the browser resolves when the
|
|
308
359
|
* fill request lands (browser/partial-update.ts fillHoles), so no lane may
|
|
309
360
|
* await the set as a whole. The
|
|
310
361
|
* tree build would wait for a request that is only sent once it returns.
|
|
311
362
|
*/
|
|
312
|
-
function
|
|
313
|
-
return loaders.some(isPlaceholder);
|
|
363
|
+
function settlesLoaders(loaders: ResolvedSegment[], lane: boolean): boolean {
|
|
364
|
+
return lane && !loaders.some(isPlaceholder);
|
|
314
365
|
}
|
|
315
366
|
|
|
316
367
|
function isLoaderRedirectNode(node: ReactNode): boolean {
|
|
@@ -378,10 +429,9 @@ export async function renderSegments(
|
|
|
378
429
|
transitionGatedOff,
|
|
379
430
|
} = options || {};
|
|
380
431
|
// forceAwait and action lanes settle loader data before the commit, except
|
|
381
|
-
// a
|
|
432
|
+
// a deferred loader (settlesLoaders), and hand each settled entry to its
|
|
433
|
+
// read (buildLoaderStreams). Only the document lane decodes in the build.
|
|
382
434
|
const settleLoaders = !!(forceAwait || isAction);
|
|
383
|
-
const settleAll = (loaders: ResolvedSegment[]): boolean =>
|
|
384
|
-
settleLoaders && !hasDeferred(loaders);
|
|
385
435
|
|
|
386
436
|
const segDebug = INTERNAL_RANGO_DEBUG;
|
|
387
437
|
const segDebugStart = segDebug ? performance.now() : 0;
|
|
@@ -395,10 +445,16 @@ export async function renderSegments(
|
|
|
395
445
|
}
|
|
396
446
|
|
|
397
447
|
const temporalLazyRefs: Promise<any>[] = [];
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
448
|
+
// Back/forward restores an intercept entry unsplit (navigation-bridge.ts).
|
|
449
|
+
// Awaited only when there is something to fold: no extra microtask.
|
|
450
|
+
let folded = foldInterceptLoaders(segments, settleLoaders);
|
|
451
|
+
if (folded instanceof Promise) folded = await folded;
|
|
452
|
+
const normalizedSegments = restoreParallelLoaderMarkers(folded);
|
|
453
|
+
let normalizedInterceptSegments =
|
|
454
|
+
interceptSegments && foldInterceptLoaders(interceptSegments, settleLoaders);
|
|
455
|
+
if (normalizedInterceptSegments instanceof Promise) {
|
|
456
|
+
normalizedInterceptSegments = await normalizedInterceptSegments;
|
|
457
|
+
}
|
|
402
458
|
|
|
403
459
|
/**
|
|
404
460
|
* Registers promises from lazy/async components for awaiting.
|
|
@@ -423,7 +479,9 @@ export async function renderSegments(
|
|
|
423
479
|
);
|
|
424
480
|
}
|
|
425
481
|
// Separate segments by type, passing intercept segments for explicit injection
|
|
426
|
-
const tree =
|
|
482
|
+
const tree = [
|
|
483
|
+
...segmentTreeWalk(normalizedSegments, normalizedInterceptSegments),
|
|
484
|
+
];
|
|
427
485
|
// Dev only: the wrapper chain per segment, compared with the previous
|
|
428
486
|
// tree's (suspense-audit.ts, I5).
|
|
429
487
|
const treeAudit =
|
|
@@ -452,7 +510,8 @@ export async function renderSegments(
|
|
|
452
510
|
);
|
|
453
511
|
// Render content segments as siblings
|
|
454
512
|
let content: ReactNode = null;
|
|
455
|
-
for (
|
|
513
|
+
for (let i = 0; i < tree.length; i++) {
|
|
514
|
+
const node = tree[i]!;
|
|
456
515
|
invariant(
|
|
457
516
|
node.segment.type === "layout" ||
|
|
458
517
|
node.segment.type === "route" ||
|
|
@@ -504,7 +563,7 @@ export async function renderSegments(
|
|
|
504
563
|
.join(",")
|
|
505
564
|
: "";
|
|
506
565
|
// Route identity the per-route remount used to provide; group routes key
|
|
507
|
-
// by the group instead and pass it to the error boundary
|
|
566
|
+
// by the group instead and pass it to the error boundary's reset.
|
|
508
567
|
const idParamsKey = paramStr ? `${id}-${paramStr}` : id;
|
|
509
568
|
const key = clientGroup ? `cg:${clientGroup}` : idParamsKey;
|
|
510
569
|
|
|
@@ -630,14 +689,19 @@ export async function renderSegments(
|
|
|
630
689
|
// lane changes remount the subtree (docs/tree-structure.md).
|
|
631
690
|
if (loaderEntries.length > 0 || clientGroup) {
|
|
632
691
|
nodeContent = createElement(StreamedLoaderErrorBoundary, {
|
|
633
|
-
|
|
692
|
+
// Leaf first: tree.slice(i) is this segment and its ancestors. A
|
|
693
|
+
// read walks up the chain, so this boundary can catch an ancestor
|
|
694
|
+
// loader's marker and resets when that loader's data changes too.
|
|
695
|
+
reset: [
|
|
696
|
+
idParamsKey,
|
|
697
|
+
...tree.slice(i).flatMap((n) => n.loaders.map((l) => l.loaderData)),
|
|
698
|
+
],
|
|
634
699
|
children: nodeContent,
|
|
635
700
|
});
|
|
636
701
|
}
|
|
637
702
|
|
|
638
703
|
if (!clientGroup && loading !== undefined && loading !== null) {
|
|
639
704
|
const loaderDataPromise = getMemoizedLoaderPromise(loaderEntries);
|
|
640
|
-
let boundaryLoaderData: Promise<any[]> | any[] = loaderDataPromise;
|
|
641
705
|
// SPIKE (streaming useLoader): per-loader streams for the boundary's
|
|
642
706
|
// readers. MUST come from the individual loader refs — splitting the
|
|
643
707
|
// aggregate client-side is wrong (Promise.all resolves at the slowest
|
|
@@ -648,19 +712,7 @@ export async function renderSegments(
|
|
|
648
712
|
// for content-suspending routes (segment-loader-promise.ts).
|
|
649
713
|
let boundaryLoaderStreams: Record<string, unknown> | undefined;
|
|
650
714
|
let boundaryAwaitedLoaderIds: string[] | undefined;
|
|
651
|
-
if (
|
|
652
|
-
const awaitStart = segDebug ? performance.now() : 0;
|
|
653
|
-
boundaryLoaderData =
|
|
654
|
-
loaderDataPromise instanceof Promise
|
|
655
|
-
? await loaderDataPromise
|
|
656
|
-
: loaderDataPromise;
|
|
657
|
-
if (segDebug) {
|
|
658
|
-
segDebugLog(`segment ${id}: loaders awaited (forceAwait/action)`, {
|
|
659
|
-
loaderIds,
|
|
660
|
-
ms: Math.round(performance.now() - awaitStart),
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
} else if (loaderEntries.length > 0) {
|
|
715
|
+
if (loaderEntries.length > 0) {
|
|
664
716
|
let settledFallback: ReactNode | undefined;
|
|
665
717
|
({
|
|
666
718
|
streams: boundaryLoaderStreams,
|
|
@@ -668,7 +720,9 @@ export async function renderSegments(
|
|
|
668
720
|
settledFallback,
|
|
669
721
|
} = await buildLoaderStreams(loaderEntries, settleLoaders));
|
|
670
722
|
if (settledFallback !== undefined) {
|
|
671
|
-
|
|
723
|
+
// Only the document lane replaces the whole page; a client lane
|
|
724
|
+
// keeps its layouts and the redirect replaces this segment's content.
|
|
725
|
+
if (!settleLoaders && isLoaderRedirectNode(settledFallback)) {
|
|
672
726
|
content = settledFallback;
|
|
673
727
|
break;
|
|
674
728
|
}
|
|
@@ -687,7 +741,7 @@ export async function renderSegments(
|
|
|
687
741
|
: LoaderBoundary,
|
|
688
742
|
{
|
|
689
743
|
key: `loader-boundary-${key}`,
|
|
690
|
-
loaderDataPromise
|
|
744
|
+
loaderDataPromise,
|
|
691
745
|
loaderIds,
|
|
692
746
|
loaderStreams: boundaryLoaderStreams,
|
|
693
747
|
awaitedLoaderIds: boundaryAwaitedLoaderIds,
|
|
@@ -726,35 +780,13 @@ export async function renderSegments(
|
|
|
726
780
|
// per-loader UNDECODED results (possibly pending promises) through
|
|
727
781
|
// OutletProvider.loaderStreams — useLoader suspends at the read site to
|
|
728
782
|
// the nearest CONSUMER Suspense boundary. forceAwait (popstate, stale
|
|
729
|
-
// revalidation, fully-prefetched) and action
|
|
730
|
-
//
|
|
731
|
-
|
|
783
|
+
// revalidation, fully-prefetched) and the action lane await each loader
|
|
784
|
+
// in buildLoaderStreams so those commits stay whole with no fallback
|
|
785
|
+
// flash, and still hand each entry to its read.
|
|
732
786
|
let errorFallback: ReactNode = null;
|
|
733
787
|
let loaderStreams: Record<string, unknown> | undefined;
|
|
734
788
|
let awaitedLoaderIds: string[] | undefined;
|
|
735
|
-
if (
|
|
736
|
-
const layoutAwaitStart = segDebug ? performance.now() : 0;
|
|
737
|
-
const resolvedData = await buildLoaderPromise(layoutLoaders);
|
|
738
|
-
if (segDebug) {
|
|
739
|
-
segDebugLog(`segment ${id}: layout loaders awaited (blocking)`, {
|
|
740
|
-
loaderIds: layoutLoaderIds,
|
|
741
|
-
ms: Math.round(performance.now() - layoutAwaitStart),
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
const decodeStart = segDebug ? performance.now() : 0;
|
|
745
|
-
({ loaderData, errorFallback } = decodeLoaderResults(
|
|
746
|
-
resolvedData,
|
|
747
|
-
layoutLoaderIds,
|
|
748
|
-
));
|
|
749
|
-
if (segDebug) {
|
|
750
|
-
const decodeMs = Math.round(performance.now() - decodeStart);
|
|
751
|
-
if (decodeMs > 0) {
|
|
752
|
-
segDebugLog(`segment ${id}: loader results decoded`, {
|
|
753
|
-
ms: decodeMs,
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
} else if (layoutLoaders.length > 0) {
|
|
789
|
+
if (layoutLoaders.length > 0) {
|
|
758
790
|
let settledFallback: ReactNode | undefined;
|
|
759
791
|
({
|
|
760
792
|
streams: loaderStreams,
|
|
@@ -762,7 +794,9 @@ export async function renderSegments(
|
|
|
762
794
|
settledFallback,
|
|
763
795
|
} = await buildLoaderStreams(layoutLoaders, settleLoaders));
|
|
764
796
|
if (settledFallback !== undefined) {
|
|
765
|
-
|
|
797
|
+
// Only the document lane replaces the whole page; a client lane
|
|
798
|
+
// keeps its layouts and the redirect replaces this segment's content.
|
|
799
|
+
if (!settleLoaders && isLoaderRedirectNode(settledFallback)) {
|
|
766
800
|
content = settledFallback;
|
|
767
801
|
break;
|
|
768
802
|
}
|
|
@@ -813,10 +847,11 @@ export async function renderSegments(
|
|
|
813
847
|
// Flagged (ssr:false) delivery still holds: when every owned
|
|
814
848
|
// loader paid the pre-flush await, settle the aggregate so
|
|
815
849
|
// LoaderResolver decodes the array without a Flight-chunk use().
|
|
816
|
-
const settleParallelAggregate =
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
850
|
+
const settleParallelAggregate = settlesLoaders(
|
|
851
|
+
ownedLoaders,
|
|
852
|
+
settleLoaders ||
|
|
853
|
+
ownedLoaders.every((l) => l.awaitBeforeFlush === true),
|
|
854
|
+
);
|
|
820
855
|
if (settleParallelAggregate) {
|
|
821
856
|
const parallelAwaitStart = segDebug ? performance.now() : 0;
|
|
822
857
|
p.loaderDataPromise =
|
|
@@ -855,8 +890,6 @@ export async function renderSegments(
|
|
|
855
890
|
content: outletContent,
|
|
856
891
|
segment: node.segment,
|
|
857
892
|
parallel: node.parallel,
|
|
858
|
-
loaderData:
|
|
859
|
-
Object.keys(loaderData).length > 0 ? loaderData : undefined,
|
|
860
893
|
loaderStreams,
|
|
861
894
|
awaitedLoaderIds,
|
|
862
895
|
pending: outletPending,
|
|
@@ -974,20 +1007,6 @@ export async function renderSegments(
|
|
|
974
1007
|
* @param segments - Main segments from the route tree
|
|
975
1008
|
* @param interceptSegments - Optional intercept segments to inject
|
|
976
1009
|
*/
|
|
977
|
-
// Loader segment ids have the grammar `${parentId}D${index}.${loaderId}`.
|
|
978
|
-
// parentId is the parent shortCode (M/L/P/R/C + digits, never "D") for normal
|
|
979
|
-
// loaders, or `${shortCode}.${slotName}` for intercept-slot loaders, where the
|
|
980
|
-
// slot name is user-controlled (`@${string}`) and may contain an uppercase "D"
|
|
981
|
-
// (e.g. "@Detail"). Strip from the first `D<index>.` separator so the slot name
|
|
982
|
-
// is preserved; splitting on a bare "D" mis-cut "@Detail" to "@" and silently
|
|
983
|
-
// dropped the loader's data. The first-`D<index>.` strip is only correct because
|
|
984
|
-
// slot names cannot contain "." -- assertValidSlotName (route-definition/
|
|
985
|
-
// dsl-helpers.ts) rejects a "." at definition time, so a name like "@D3.foo"
|
|
986
|
-
// (which WOULD mis-cut here) can never reach this function.
|
|
987
|
-
function loaderParentId(loaderSegmentId: string): string {
|
|
988
|
-
return loaderSegmentId.replace(/D\d+\..*$/, "");
|
|
989
|
-
}
|
|
990
|
-
|
|
991
1010
|
// Append a value to the array stored under `key`, creating the array on first
|
|
992
1011
|
// use. Single Map lookup (vs the has/get!().push double-lookup idiom).
|
|
993
1012
|
function pushToGroup<K, V>(map: Map<K, V[]>, key: K, value: V): void {
|
|
@@ -1032,18 +1051,14 @@ function* segmentTreeWalk(
|
|
|
1032
1051
|
}
|
|
1033
1052
|
}
|
|
1034
1053
|
|
|
1035
|
-
// INTERCEPT SEGMENTS: Explicitly inject into parent's parallel array
|
|
1036
|
-
//
|
|
1054
|
+
// INTERCEPT SEGMENTS: Explicitly inject into parent's parallel array.
|
|
1055
|
+
// Their loaders are on the slot already (foldInterceptLoaders).
|
|
1037
1056
|
if (interceptSegments && interceptSegments.length > 0) {
|
|
1038
1057
|
for (const intercept of interceptSegments) {
|
|
1039
1058
|
if (intercept.type === "parallel" && intercept.slot) {
|
|
1040
1059
|
// Extract parent ID from intercept ID (e.g., "M4L0L0L2.@modal" → "M4L0L0L2")
|
|
1041
1060
|
const parentId = intercept.id.split(".")[0];
|
|
1042
1061
|
pushToGroup(parallelsByParent, parentId, intercept);
|
|
1043
|
-
} else if (intercept.type === "loader") {
|
|
1044
|
-
// Intercept loaders - extract parent from loader ID (slot name preserved)
|
|
1045
|
-
const parentId = loaderParentId(intercept.id);
|
|
1046
|
-
pushToGroup(loadersByParent, parentId, intercept);
|
|
1047
1062
|
}
|
|
1048
1063
|
}
|
|
1049
1064
|
}
|
|
@@ -1062,15 +1077,6 @@ function* segmentTreeWalk(
|
|
|
1062
1077
|
const parallel = parallelsByParent.get(segment.id) || [];
|
|
1063
1078
|
const loaders = loadersByParent.get(segment.id) || [];
|
|
1064
1079
|
|
|
1065
|
-
// Also include loaders from parallel segments (e.g., intercept loaders)
|
|
1066
|
-
// These have parent IDs like "M9L0L1.@modal" which match the parallel segment ID
|
|
1067
|
-
for (const p of parallel) {
|
|
1068
|
-
const parallelLoaders = loadersByParent.get(p.id);
|
|
1069
|
-
if (parallelLoaders) {
|
|
1070
|
-
loaders.push(...parallelLoaders);
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
1080
|
yield { segment, parallel, loaders };
|
|
1075
1081
|
}
|
|
1076
1082
|
}
|
package/src/server/context.ts
CHANGED
|
@@ -92,6 +92,12 @@ export type EntryPropCommon = {
|
|
|
92
92
|
cache?: EntryCacheConfig;
|
|
93
93
|
/** URL prefix from include() scope, used for MountContext on client */
|
|
94
94
|
mountPath?: string;
|
|
95
|
+
/**
|
|
96
|
+
* Include scope the entry's handler and loaders resolve `ctx.reverse(".name")`
|
|
97
|
+
* in. Structural entries only (layout, cache, parallel, wrapping
|
|
98
|
+
* middleware); a route resolves from its own name. See ReverseScope.
|
|
99
|
+
*/
|
|
100
|
+
reverseScope?: ReverseScope;
|
|
95
101
|
/** clientUrls() group key (PathOptions.clientGroup); route entries only. */
|
|
96
102
|
clientGroup?: string;
|
|
97
103
|
};
|
|
@@ -163,6 +169,13 @@ export type LoaderEntry = {
|
|
|
163
169
|
* (router/segment-resolution/prefetch-deferral.ts).
|
|
164
170
|
*/
|
|
165
171
|
prefetch?: false;
|
|
172
|
+
/**
|
|
173
|
+
* The declaring layout's / parallel's / cache()'s include scope, for the
|
|
174
|
+
* loader body's `ctx.reverse(".name")` (its DSL kickoff carries it,
|
|
175
|
+
* runInsideLoaderScope). Unset on a route's loaders: they resolve from the
|
|
176
|
+
* route's name.
|
|
177
|
+
*/
|
|
178
|
+
reverseScope?: ReverseScope;
|
|
166
179
|
};
|
|
167
180
|
|
|
168
181
|
/**
|
|
@@ -233,7 +246,6 @@ export type InterceptEntry = {
|
|
|
233
246
|
slotName: `@${string}`; // e.g., "@modal"
|
|
234
247
|
routeName: string; // e.g., "card"
|
|
235
248
|
handler: ReactNode | Handler<any, any, any>;
|
|
236
|
-
middleware: MiddlewareFn<any, any>[];
|
|
237
249
|
loader: LoaderEntry[];
|
|
238
250
|
loading?: ReactNode | false;
|
|
239
251
|
transition?: TransitionConfig;
|
|
@@ -327,10 +339,22 @@ export type EntryData =
|
|
|
327
339
|
EntryPropSegments &
|
|
328
340
|
EntryPropRender);
|
|
329
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Where an include()'s handler runs in the route tree. Hidden include-scope
|
|
344
|
+
* names (`$prefix_*`) derive from it, never from counter state, so build
|
|
345
|
+
* discovery and runtime expansion name every include the same (#1104).
|
|
346
|
+
*/
|
|
347
|
+
export interface IncludeScopePosition {
|
|
348
|
+
/** Sibling indexes of the enclosing includes, "_"-joined ("" at a mount root). */
|
|
349
|
+
scopePath?: string;
|
|
350
|
+
/** The scopePath tail since the nearest include that added a name segment. */
|
|
351
|
+
hiddenScopePath?: string;
|
|
352
|
+
}
|
|
353
|
+
|
|
330
354
|
/**
|
|
331
355
|
* Tracked include info for build-time manifest generation
|
|
332
356
|
*/
|
|
333
|
-
export interface TrackedInclude {
|
|
357
|
+
export interface TrackedInclude extends IncludeScopePosition {
|
|
334
358
|
prefix: string;
|
|
335
359
|
fullPrefix: string;
|
|
336
360
|
namePrefix?: string;
|
|
@@ -352,7 +376,7 @@ export type CachedHeaderScope = {
|
|
|
352
376
|
/**
|
|
353
377
|
* Context stored in AsyncLocalStorage
|
|
354
378
|
*/
|
|
355
|
-
interface HelperContext {
|
|
379
|
+
interface HelperContext extends IncludeScopePosition {
|
|
356
380
|
manifest: Map<string, EntryData>;
|
|
357
381
|
namespace: string;
|
|
358
382
|
parent: EntryData | null;
|
|
@@ -597,6 +621,8 @@ export const getContext = (): {
|
|
|
597
621
|
trackedIncludes: store.trackedIncludes,
|
|
598
622
|
cacheProfiles: store.cacheProfiles,
|
|
599
623
|
includeScope: store.includeScope,
|
|
624
|
+
scopePath: store.scopePath,
|
|
625
|
+
hiddenScopePath: store.hiddenScopePath,
|
|
600
626
|
// cachedHeaderScope (and insideCacheScope) deliberately NOT copied —
|
|
601
627
|
// the header guard's middleware exemption depends on the latch dying
|
|
602
628
|
// with the funnel scope (see assertCachedHeaderWriteAllowed).
|
|
@@ -641,6 +667,8 @@ export const getContext = (): {
|
|
|
641
667
|
rootScoped: store?.rootScoped,
|
|
642
668
|
trackedIncludes: store?.trackedIncludes,
|
|
643
669
|
cacheProfiles: store?.cacheProfiles,
|
|
670
|
+
scopePath: store?.scopePath,
|
|
671
|
+
hiddenScopePath: store?.hiddenScopePath,
|
|
644
672
|
},
|
|
645
673
|
callback,
|
|
646
674
|
);
|
|
@@ -763,6 +791,54 @@ export function getRootScoped(): boolean {
|
|
|
763
791
|
return store?.rootScoped ?? true;
|
|
764
792
|
}
|
|
765
793
|
|
|
794
|
+
/**
|
|
795
|
+
* The include scope a segment's dot-local `ctx.reverse(".name")` resolves in.
|
|
796
|
+
*
|
|
797
|
+
* One handler ctx serves every segment of a request and is built from the
|
|
798
|
+
* matched route's name (match-api.ts), so a parent module's layout resolved
|
|
799
|
+
* `.settings` from the matched CHILD module's prefix first: on
|
|
800
|
+
* `/acme/web/issues` the account layout's `.settings` hit the project
|
|
801
|
+
* module's route. Structural entries (layout, cache, parallel, wrapping
|
|
802
|
+
* middleware) and the loaders they declare capture the scope where the DSL
|
|
803
|
+
* defined them. Their handler gets a ctx whose `reverse` carries it
|
|
804
|
+
* (withReverseScope, router/handler-context.ts), and their loaders' DSL
|
|
805
|
+
* kickoff carries it in the loader scope (runInsideLoaderScope) for the
|
|
806
|
+
* loader ctx the executor builds. Global names and mount-param autofill do
|
|
807
|
+
* not change.
|
|
808
|
+
*/
|
|
809
|
+
export interface ReverseScope {
|
|
810
|
+
/** Include name prefix ("account.project"); undefined outside named includes. */
|
|
811
|
+
readonly prefix: string | undefined;
|
|
812
|
+
/** getRootScoped() where the segment was defined: bare-name fallback allowed. */
|
|
813
|
+
readonly rootScoped: boolean;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
/** The ReverseScope of the DSL position being evaluated. */
|
|
817
|
+
export function getReverseScope(): ReverseScope {
|
|
818
|
+
return { prefix: getNamePrefix(), rootScoped: getRootScoped() };
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* A createReverseFunction result (router/handler-context.ts). The internal
|
|
823
|
+
* 4th argument resolves dot names in a segment's ReverseScope instead of the
|
|
824
|
+
* route's name.
|
|
825
|
+
*/
|
|
826
|
+
export type ScopableReverse = (
|
|
827
|
+
name: string,
|
|
828
|
+
hrefParams?: Record<string, string>,
|
|
829
|
+
search?: Record<string, unknown>,
|
|
830
|
+
segmentScope?: ReverseScope,
|
|
831
|
+
) => string;
|
|
832
|
+
|
|
833
|
+
/** `reverse` with dot names resolved in `scope`, if any. */
|
|
834
|
+
export function scopeReverse(
|
|
835
|
+
reverse: ScopableReverse,
|
|
836
|
+
scope: ReverseScope | undefined,
|
|
837
|
+
): ScopableReverse {
|
|
838
|
+
if (scope === undefined) return reverse;
|
|
839
|
+
return (name, hrefParams, search) => reverse(name, hrefParams, search, scope);
|
|
840
|
+
}
|
|
841
|
+
|
|
766
842
|
/**
|
|
767
843
|
* Stamp build-time scope identity onto a Static() definition at mount time.
|
|
768
844
|
* The bake collector (prerender-collection.ts) renders defs OUTSIDE any
|
|
@@ -909,6 +985,8 @@ interface LoaderScope {
|
|
|
909
985
|
active: true;
|
|
910
986
|
/** $$id of the DSL loader this scope resolves (runInsideLoaderScope). */
|
|
911
987
|
loaderId?: string;
|
|
988
|
+
/** The declaring entry's ReverseScope (LoaderEntry.reverseScope). */
|
|
989
|
+
reverseScope?: ReverseScope;
|
|
912
990
|
}
|
|
913
991
|
const loaderScopeALS: AsyncLocalStorage<LoaderScope> = ((globalThis as any)[
|
|
914
992
|
LOADER_SCOPE_KEY
|
|
@@ -1472,8 +1550,21 @@ export function assertCachedHeaderWriteAllowed(
|
|
|
1472
1550
|
* are bypassed because loaders are always fresh (never cached) and
|
|
1473
1551
|
* their side effects (setCookie, header, etc.) are safe.
|
|
1474
1552
|
*/
|
|
1475
|
-
export function runInsideLoaderScope<T>(
|
|
1476
|
-
|
|
1553
|
+
export function runInsideLoaderScope<T>(
|
|
1554
|
+
fn: () => T,
|
|
1555
|
+
loaderId?: string,
|
|
1556
|
+
reverseScope?: ReverseScope,
|
|
1557
|
+
): T {
|
|
1558
|
+
return loaderScopeALS.run({ active: true, loaderId, reverseScope }, fn);
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* The ReverseScope of the DSL loader kickoff around this call: a loader the
|
|
1563
|
+
* kickoff starts, and one its body starts with ctx.use(), resolve dot names
|
|
1564
|
+
* in it. A handler's ctx.use(Loader) runs outside any kickoff: none.
|
|
1565
|
+
*/
|
|
1566
|
+
export function getLoaderReverseScope(): ReverseScope | undefined {
|
|
1567
|
+
return loaderScopeALS.getStore()?.reverseScope;
|
|
1477
1568
|
}
|
|
1478
1569
|
|
|
1479
1570
|
/**
|
|
@@ -47,8 +47,9 @@ function createLateHandlePushError(
|
|
|
47
47
|
): Error {
|
|
48
48
|
const error = new Error(
|
|
49
49
|
`Handle "${handleName}" for segment "${segmentId}" was pushed after handle collection completed. ` +
|
|
50
|
-
`This usually means an async
|
|
51
|
-
`Push
|
|
50
|
+
`This usually means an async component pushed it after an await. ` +
|
|
51
|
+
`Push from the route or layout handler, or reserve the slot there with ` +
|
|
52
|
+
`ctx.use(Handle).defer() and resolve it from the component.`,
|
|
52
53
|
);
|
|
53
54
|
error.name = "LateHandlePushError";
|
|
54
55
|
return error;
|