@rangojs/router 0.24.4 → 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-urls.d.ts +6 -2
- package/dist/types/client-urls/revalidate-registry.d.ts +1 -1
- package/dist/types/client-urls/server-projection.d.ts +9 -5
- package/dist/types/client-urls/types.d.ts +29 -16
- 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 +3 -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/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 +185 -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 +112 -84
- 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-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-urls.ts +202 -153
- package/src/client-urls/revalidate-registry.ts +12 -3
- package/src/client-urls/server-projection.ts +137 -66
- package/src/client-urls/types.ts +30 -15
- 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 +3 -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/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
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
type RequestContext,
|
|
40
40
|
} from "../server/request-context.js";
|
|
41
41
|
import { createReverseFunction } from "../router/handler-context.js";
|
|
42
|
+
import { scopeReverse, type ReverseScope } from "../server/context.js";
|
|
42
43
|
import { NOCACHE_SYMBOL } from "../cache/taint.js";
|
|
43
44
|
import { defineRewrittenFrom } from "../server/rewrite-trail.js";
|
|
44
45
|
import { getFetchableLoader } from "../server/fetchable-loader-store.js";
|
|
@@ -134,6 +135,13 @@ export interface RunLoaderOptions<TEnv = any> {
|
|
|
134
135
|
routeMap?: Record<string, string>;
|
|
135
136
|
/** Matched route name for scoped `.name` reverse resolution. */
|
|
136
137
|
routeName?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Include name prefix of the module whose `layout()` / `parallel()` /
|
|
140
|
+
* `cache()` declares the loader (`"account"`, or `""` for the root module).
|
|
141
|
+
* `ctx.reverse(".name")` then resolves in that module, as such a loader's
|
|
142
|
+
* does in production, instead of from `routeName`.
|
|
143
|
+
*/
|
|
144
|
+
declaredIn?: string;
|
|
137
145
|
/** HTTP method surfaced as `ctx.method`. Defaults to "GET". */
|
|
138
146
|
method?: string;
|
|
139
147
|
/** Request body surfaced as `ctx.body`. */
|
|
@@ -279,9 +287,21 @@ function runWithLoaderContext<R>(
|
|
|
279
287
|
const loaderSeeds = new Map<unknown, unknown>(opts.loaders ?? []);
|
|
280
288
|
let renderedResolved = false;
|
|
281
289
|
|
|
290
|
+
// A layout's / parallel's loader: `.name` resolves in its declaring module.
|
|
291
|
+
const reverseScope: ReverseScope | undefined =
|
|
292
|
+
opts.declaredIn === undefined
|
|
293
|
+
? undefined
|
|
294
|
+
: { prefix: opts.declaredIn || undefined, rootScoped: !opts.declaredIn };
|
|
282
295
|
return runWithRequestContext(reqCtx, () => {
|
|
283
296
|
const reverse = opts.routeMap
|
|
284
|
-
?
|
|
297
|
+
? scopeReverse(
|
|
298
|
+
createReverseFunction(
|
|
299
|
+
opts.routeMap,
|
|
300
|
+
opts.routeName,
|
|
301
|
+
opts.params ?? {},
|
|
302
|
+
),
|
|
303
|
+
reverseScope,
|
|
304
|
+
)
|
|
285
305
|
: ((() => {
|
|
286
306
|
throw new Error(
|
|
287
307
|
"ctx.reverse() requires the `routeMap` option in runLoader(). " +
|
|
@@ -289,7 +309,10 @@ function runWithLoaderContext<R>(
|
|
|
289
309
|
);
|
|
290
310
|
}) as TestLoaderContext["reverse"]);
|
|
291
311
|
|
|
292
|
-
const loaderCtx: TestLoaderContext & {
|
|
312
|
+
const loaderCtx: TestLoaderContext & {
|
|
313
|
+
_routeName?: string;
|
|
314
|
+
_reverseScope?: ReverseScope;
|
|
315
|
+
} = {
|
|
293
316
|
params: opts.params ?? {},
|
|
294
317
|
routeParams: (opts.params ?? {}) as Record<string, string>,
|
|
295
318
|
request: reqCtx.request,
|
|
@@ -346,6 +369,7 @@ function runWithLoaderContext<R>(
|
|
|
346
369
|
formData: opts.formData,
|
|
347
370
|
reverse: reverse as TestLoaderContext["reverse"],
|
|
348
371
|
_routeName: reqCtx._routeName,
|
|
372
|
+
_reverseScope: reverseScope,
|
|
349
373
|
rendered:
|
|
350
374
|
opts.rendered !== undefined && opts.rendered !== false
|
|
351
375
|
? async () => {
|
|
@@ -106,6 +106,13 @@ export interface RunMiddlewareRewrite {
|
|
|
106
106
|
pathname: string;
|
|
107
107
|
search: string;
|
|
108
108
|
routeName?: string;
|
|
109
|
+
/**
|
|
110
|
+
* Present, `true`, only when the rewrite was thrown with
|
|
111
|
+
* `.disallowMutations()`: production refuses it for a non-GET, non-HEAD
|
|
112
|
+
* request in global middleware, before the action runs, instead of moving
|
|
113
|
+
* the mutation to the target.
|
|
114
|
+
*/
|
|
115
|
+
mutationsDisallowed?: true;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
118
|
/**
|
|
@@ -240,6 +247,9 @@ export async function runMiddleware<TEnv = any>(
|
|
|
240
247
|
pathname: target.pathname,
|
|
241
248
|
search: target.search,
|
|
242
249
|
...("name" in error.target && { routeName: error.target.name }),
|
|
250
|
+
...(error.mutationsDisallowed && {
|
|
251
|
+
mutationsDisallowed: true as const,
|
|
252
|
+
}),
|
|
243
253
|
};
|
|
244
254
|
return new Response(null, {
|
|
245
255
|
status: ctx.res.status,
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
* a re-implementation. Pass an array to test a chain in declaration order.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
evaluateRevalidation,
|
|
15
|
+
toShouldRevalidateFn,
|
|
16
|
+
} from "../router/revalidation.js";
|
|
14
17
|
import { runWithRequestContext } from "../server/request-context.js";
|
|
15
18
|
import type { ResolvedSegment, ShouldRevalidateFn } from "../types.js";
|
|
16
19
|
import {
|
|
@@ -81,12 +84,16 @@ export interface RunRevalidateOptions<TEnv = any> {
|
|
|
81
84
|
const DEFAULT_URL = "http://localhost/";
|
|
82
85
|
|
|
83
86
|
/**
|
|
84
|
-
* Run one revalidate()
|
|
85
|
-
* the router does for a request
|
|
86
|
-
*
|
|
87
|
+
* Run one revalidate() argument, or a chain in declaration order, the way
|
|
88
|
+
* the router does for a request: a predicate, or `true` / `false` as
|
|
89
|
+
* `revalidate()` takes them. Resolves to the final decision: true re-runs
|
|
90
|
+
* the segment, false keeps the client's copy.
|
|
87
91
|
*/
|
|
88
92
|
export async function runRevalidate<TEnv = any>(
|
|
89
|
-
fn:
|
|
93
|
+
fn:
|
|
94
|
+
| ShouldRevalidateFn<any, TEnv>
|
|
95
|
+
| boolean
|
|
96
|
+
| readonly (ShouldRevalidateFn<any, TEnv> | boolean)[],
|
|
90
97
|
opts: RunRevalidateOptions<TEnv> = {},
|
|
91
98
|
): Promise<boolean> {
|
|
92
99
|
const currentUrl = toURL(opts.currentUrl, new URL(DEFAULT_URL));
|
|
@@ -124,7 +131,7 @@ export async function runRevalidate<TEnv = any>(
|
|
|
124
131
|
nextUrl,
|
|
125
132
|
revalidations: fns.map((entry, index) => ({
|
|
126
133
|
name: `runRevalidate#${index}`,
|
|
127
|
-
fn: entry,
|
|
134
|
+
fn: toShouldRevalidateFn(entry),
|
|
128
135
|
})),
|
|
129
136
|
routeKey: opts.routeName ?? "",
|
|
130
137
|
context: (opts.context ?? ctx) as RevalidateArgs["context"],
|
|
@@ -62,6 +62,8 @@ import { FILL_PARAM, PREFETCH_HEADER } from "../prefetch-markers.js";
|
|
|
62
62
|
import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
|
|
63
63
|
import { withoutPushedBy } from "../handles/pushed-by.js";
|
|
64
64
|
import type { HandleData } from "../server/handle-store.js";
|
|
65
|
+
import type { ResolvedSegment } from "../types.js";
|
|
66
|
+
import { revalidationErrorOf } from "../decode-loader-results.js";
|
|
65
67
|
import { _getRequestContext } from "../server/request-context.js";
|
|
66
68
|
import {
|
|
67
69
|
parsePprReplayStatus,
|
|
@@ -97,15 +99,22 @@ export interface ServeShellRequestOptions<TEnv = any> {
|
|
|
97
99
|
* entries are deferred: `result.readDeferred()` lists them. `fill: true`
|
|
98
100
|
* sends the request the browser follows an adopted prefetch with
|
|
99
101
|
* (`_rsc_fill`): pass the ids the prefetch delivered, without the deferred
|
|
100
|
-
* ones, as `segments`, and the same URL as `from`.
|
|
102
|
+
* ones, as `segments`, and the same URL as `from`. `interceptSource` sends
|
|
103
|
+
* the intercept source (`X-RSC-Router-Intercept-Source`) the browser sends
|
|
104
|
+
* from an open modal and with the fill of an intercept, whose `from` is
|
|
105
|
+
* that source. `rewriteTarget` sends the `metadata.rewriteTarget` the page at
|
|
106
|
+
* `from` carried when middleware rewrote it (`_rsc_rt`), as the browser
|
|
107
|
+
* does: the server then diffs against the route that rendered that page.
|
|
101
108
|
*/
|
|
102
109
|
partial?:
|
|
103
110
|
| true
|
|
104
111
|
| {
|
|
105
112
|
from?: string;
|
|
113
|
+
rewriteTarget?: string;
|
|
106
114
|
segments?: readonly string[];
|
|
107
115
|
prefetch?: boolean;
|
|
108
116
|
fill?: boolean;
|
|
117
|
+
interceptSource?: string;
|
|
109
118
|
};
|
|
110
119
|
/**
|
|
111
120
|
* POST this form as a submit before hydration: the no-JS request the
|
|
@@ -139,6 +148,8 @@ export interface ServeShellRequestAction {
|
|
|
139
148
|
args?: readonly unknown[];
|
|
140
149
|
/** The segment ids the page holds (`_rsc_segments`). */
|
|
141
150
|
segments?: readonly string[];
|
|
151
|
+
/** The page's payload `metadata.rewriteTarget`, as for `partial`. */
|
|
152
|
+
rewriteTarget?: string;
|
|
142
153
|
}
|
|
143
154
|
|
|
144
155
|
/** A response's handle data, as the browser consumes it. */
|
|
@@ -157,6 +168,25 @@ export interface ShellRequestHandles {
|
|
|
157
168
|
late: HandleData[];
|
|
158
169
|
}
|
|
159
170
|
|
|
171
|
+
/**
|
|
172
|
+
* An `action` request's answer, as browser/server-action-bridge.ts reads it.
|
|
173
|
+
*/
|
|
174
|
+
export interface ShellRequestAction {
|
|
175
|
+
/** False when the action threw: the call rejects with `result`. */
|
|
176
|
+
ok: boolean;
|
|
177
|
+
/** What the call resolves (or rejects) with. */
|
|
178
|
+
result: unknown;
|
|
179
|
+
/**
|
|
180
|
+
* The error of the first loader that threw while it re-ran in the action's
|
|
181
|
+
* revalidation, the value `useAction(action).revalidationError` reports.
|
|
182
|
+
* That loader throws at its `useLoader` read, where the nearest boundary
|
|
183
|
+
* catches it, and the call still resolves. `null` when every loader
|
|
184
|
+
* answered (a `notFound()` or `redirect()` is not an error), or `ok` is
|
|
185
|
+
* false, as `useAction` reports it.
|
|
186
|
+
*/
|
|
187
|
+
revalidationError: Error | null;
|
|
188
|
+
}
|
|
189
|
+
|
|
160
190
|
/** Result of {@link serveShellRequest}. */
|
|
161
191
|
export interface ServeShellRequestResult {
|
|
162
192
|
/** The handler's Response. Its body is already read: see `body`. */
|
|
@@ -182,9 +212,12 @@ export interface ServeShellRequestResult {
|
|
|
182
212
|
* The shell key production resolved for this request: for a document the
|
|
183
213
|
* serve path read and captured (a MISS or a HIT), the key it used, request
|
|
184
214
|
* partition included (a route partitioned by `cache({ key })` or the
|
|
185
|
-
* store's `keyGenerator`)
|
|
186
|
-
*
|
|
187
|
-
*
|
|
215
|
+
* store's `keyGenerator`), and for a request middleware rewrote with
|
|
216
|
+
* `rewrite(...).shell("visitor")` the visitor's key, as
|
|
217
|
+
* `visitorShellCacheKey` builds it. Otherwise (a route without `ppr`, a
|
|
218
|
+
* request passed to axis 1 before its key was resolved, a rewritten
|
|
219
|
+
* request whose rewrite chose no shell, a partial request) the document
|
|
220
|
+
* URL's key without a partition, as `shellCacheKey` builds it.
|
|
188
221
|
*/
|
|
189
222
|
key: string;
|
|
190
223
|
/**
|
|
@@ -217,6 +250,13 @@ export interface ServeShellRequestResult {
|
|
|
217
250
|
* hydrates without it.
|
|
218
251
|
*/
|
|
219
252
|
readPayloadFormState(): Promise<unknown>;
|
|
253
|
+
/**
|
|
254
|
+
* For an `action` request, the call's answer as the browser reads it: its
|
|
255
|
+
* result and the `revalidationError` its loaders' reruns report.
|
|
256
|
+
* Undefined when no Flight was rendered or the payload carries no action
|
|
257
|
+
* result.
|
|
258
|
+
*/
|
|
259
|
+
readAction(): Promise<ShellRequestAction | undefined>;
|
|
220
260
|
}
|
|
221
261
|
|
|
222
262
|
type ShellHandler = ReturnType<typeof createRSCHandler>;
|
|
@@ -352,6 +392,24 @@ async function decodePayloadHandles(
|
|
|
352
392
|
};
|
|
353
393
|
}
|
|
354
394
|
|
|
395
|
+
/** An action payload's answer, read as the browser's action bridge reads it. */
|
|
396
|
+
async function decodeAction(
|
|
397
|
+
flight: string,
|
|
398
|
+
): Promise<ShellRequestAction | undefined> {
|
|
399
|
+
const { returnValue, metadata } = await decodePayload<{
|
|
400
|
+
returnValue?: { ok: boolean; data: unknown };
|
|
401
|
+
metadata?: { segments?: ResolvedSegment[] };
|
|
402
|
+
}>(flight);
|
|
403
|
+
if (!returnValue) return undefined;
|
|
404
|
+
return {
|
|
405
|
+
ok: returnValue.ok,
|
|
406
|
+
result: returnValue.data,
|
|
407
|
+
revalidationError:
|
|
408
|
+
(returnValue.ok && (await revalidationErrorOf(metadata?.segments))) ||
|
|
409
|
+
null,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
355
413
|
/** The ids of the segments a payload marks deferred. */
|
|
356
414
|
async function decodeDeferredIds(flight: string): Promise<string[]> {
|
|
357
415
|
const { metadata } = await decodePayload<{
|
|
@@ -522,6 +580,9 @@ async function buildActionRequest(
|
|
|
522
580
|
const target = new URL(url);
|
|
523
581
|
target.searchParams.set("_rsc_action", action.id);
|
|
524
582
|
target.searchParams.set("_rsc_segments", (action.segments ?? []).join(","));
|
|
583
|
+
if (action.rewriteTarget) {
|
|
584
|
+
target.searchParams.set("_rsc_rt", action.rewriteTarget);
|
|
585
|
+
}
|
|
525
586
|
headers.set("rsc-action", action.id);
|
|
526
587
|
if (!headers.has("X-RSC-Router-Client-Path")) {
|
|
527
588
|
headers.set("X-RSC-Router-Client-Path", url.href);
|
|
@@ -565,13 +626,16 @@ async function buildRequest(
|
|
|
565
626
|
}
|
|
566
627
|
const {
|
|
567
628
|
from = "/",
|
|
629
|
+
rewriteTarget,
|
|
568
630
|
segments = [],
|
|
569
631
|
prefetch = false,
|
|
570
632
|
fill = false,
|
|
633
|
+
interceptSource,
|
|
571
634
|
} = partial === true ? {} : partial;
|
|
572
635
|
const target = new URL(url);
|
|
573
636
|
target.searchParams.set("_rsc_partial", "true");
|
|
574
637
|
target.searchParams.set("_rsc_segments", segments.join(","));
|
|
638
|
+
if (rewriteTarget) target.searchParams.set("_rsc_rt", rewriteTarget);
|
|
575
639
|
// As the browser sends them: browser/navigation-client.ts (fill) and
|
|
576
640
|
// browser/prefetch/fetch.ts (prefetch).
|
|
577
641
|
if (fill) target.searchParams.set(FILL_PARAM, "1");
|
|
@@ -581,6 +645,12 @@ async function buildRequest(
|
|
|
581
645
|
if (!headers.has("X-RSC-Router-Client-Path")) {
|
|
582
646
|
headers.set("X-RSC-Router-Client-Path", new URL(from, url).href);
|
|
583
647
|
}
|
|
648
|
+
if (interceptSource !== undefined) {
|
|
649
|
+
headers.set(
|
|
650
|
+
"X-RSC-Router-Intercept-Source",
|
|
651
|
+
new URL(interceptSource, url).href,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
584
654
|
if (!headers.has(SEGMENT_FRAGMENT_CAPABILITY_HEADER)) {
|
|
585
655
|
headers.set(SEGMENT_FRAGMENT_CAPABILITY_HEADER, "1");
|
|
586
656
|
}
|
|
@@ -725,5 +795,8 @@ export async function serveShellRequest<TEnv = any>(
|
|
|
725
795
|
}>(flight);
|
|
726
796
|
return payload.formState;
|
|
727
797
|
},
|
|
798
|
+
async readAction() {
|
|
799
|
+
return flight ? decodeAction(flight) : undefined;
|
|
800
|
+
},
|
|
728
801
|
};
|
|
729
802
|
}
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
SHELL_STATUS_HEADER,
|
|
40
40
|
buildShellKey,
|
|
41
41
|
partitionShellKey,
|
|
42
|
+
visitorShellKey,
|
|
42
43
|
type PprReplayBypassReason,
|
|
43
44
|
} from "../rsc/shell-capture-constants.js";
|
|
44
45
|
import {
|
|
@@ -119,6 +120,45 @@ export function shellCacheKey(
|
|
|
119
120
|
resolved,
|
|
120
121
|
compileSearchParamsFilter(searchParams),
|
|
121
122
|
);
|
|
123
|
+
return withPartition(key, partition);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Shell store key of a request middleware rewrote with
|
|
128
|
+
* `throw rewrite(...).shell("visitor")`: the visitor's `url`, partitioned by
|
|
129
|
+
* the rewrite's `target` (its path and search), as production builds it
|
|
130
|
+
* (`visitorShellKey`, `rsc/shell-capture-constants.ts`). `searchParams` is
|
|
131
|
+
* the target route's `cache.searchParams` config, applied to the visitor's
|
|
132
|
+
* search; `partition` is the target route's own, as for
|
|
133
|
+
* {@link shellCacheKey}, composed after the target's.
|
|
134
|
+
*
|
|
135
|
+
* ```ts
|
|
136
|
+
* const hit = await serveShellRequest(router, "/summer", { cacheStore });
|
|
137
|
+
* expect(hit.key).toBe(visitorShellCacheKey(router, "/summer", "/item/1"));
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export function visitorShellCacheKey(
|
|
141
|
+
router: { readonly id: string },
|
|
142
|
+
url: URL | string,
|
|
143
|
+
target: URL | string,
|
|
144
|
+
searchParams?: CacheSearchParams,
|
|
145
|
+
partition?: string | readonly string[] | ShellCachePartition,
|
|
146
|
+
): string {
|
|
147
|
+
const visitor =
|
|
148
|
+
typeof url === "string" ? new URL(url, "http://localhost") : url;
|
|
149
|
+
const key = visitorShellKey(
|
|
150
|
+
router.id,
|
|
151
|
+
visitor,
|
|
152
|
+
new URL(target, visitor),
|
|
153
|
+
compileSearchParamsFilter(searchParams),
|
|
154
|
+
);
|
|
155
|
+
return withPartition(key, partition);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function withPartition(
|
|
159
|
+
key: string,
|
|
160
|
+
partition: string | readonly string[] | ShellCachePartition | undefined,
|
|
161
|
+
): string {
|
|
122
162
|
if (partition === undefined) return key;
|
|
123
163
|
const { keys = [], generated = [] }: ShellCachePartition =
|
|
124
164
|
typeof partition === "string"
|
package/src/testing/vitest.ts
CHANGED
|
@@ -76,8 +76,10 @@
|
|
|
76
76
|
* a focused include, or use e2e.
|
|
77
77
|
*/
|
|
78
78
|
|
|
79
|
+
import { readFile } from "node:fs/promises";
|
|
79
80
|
import { createRequire } from "node:module";
|
|
80
81
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
82
|
+
import { parseLang } from "../vite/plugins/hoist/literal.js";
|
|
81
83
|
import {
|
|
82
84
|
isUseCacheCandidate,
|
|
83
85
|
transformUseCache,
|
|
@@ -156,6 +158,41 @@ const CLIENT_URLS_INSTALLER_PATH: string = here(
|
|
|
156
158
|
"src/testing/internal/client-urls-projection.ts",
|
|
157
159
|
).replaceAll("\\", "/");
|
|
158
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Whether a clientUrls() source's import loads as source too: a
|
|
163
|
+
* `"use client"` module, or a barrel (imports and `export ... from` only).
|
|
164
|
+
* Vite loads lazily, as in the transform: this module must load where it is
|
|
165
|
+
* not installed (installed-vitest-config.test.ts).
|
|
166
|
+
*/
|
|
167
|
+
async function loadsAsSource(code: string, file: string): Promise<boolean> {
|
|
168
|
+
if (!code.includes("use client") && !code.includes("export")) return false;
|
|
169
|
+
const { parseAstAsync } = await import("vite");
|
|
170
|
+
const { hasDirective } = await import("@vitejs/plugin-rsc/transforms");
|
|
171
|
+
type Body = Parameters<typeof hasDirective>[0];
|
|
172
|
+
let body: Body;
|
|
173
|
+
try {
|
|
174
|
+
// The file as it is on disk, TypeScript included.
|
|
175
|
+
body = (
|
|
176
|
+
(await parseAstAsync(code, { lang: parseLang(file) })) as unknown as {
|
|
177
|
+
body: Body;
|
|
178
|
+
}
|
|
179
|
+
).body;
|
|
180
|
+
} catch {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
if (hasDirective(body, "use client")) return true;
|
|
184
|
+
const nodes = body as unknown as { type: string; source?: unknown }[];
|
|
185
|
+
return (
|
|
186
|
+
nodes.some((node) => node.type !== "ImportDeclaration") &&
|
|
187
|
+
nodes.every(
|
|
188
|
+
(node) =>
|
|
189
|
+
node.type === "ImportDeclaration" ||
|
|
190
|
+
node.type === "ExportAllDeclaration" ||
|
|
191
|
+
(node.type === "ExportNamedDeclaration" && node.source != null),
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
159
196
|
/**
|
|
160
197
|
* Appended to a transformed `"use client"` clientUrls() module: install the
|
|
161
198
|
* projection of its own source under its default export's reference id
|
|
@@ -270,11 +307,24 @@ export function rangoTestConfig(
|
|
|
270
307
|
}
|
|
271
308
|
|
|
272
309
|
/** A minimal Vite plugin shape (avoids a hard dependency on Vite's types). */
|
|
310
|
+
/** The part of a Vite plugin context {@link rangoUseClientTransform} resolves with. */
|
|
311
|
+
interface SourceResolveContext {
|
|
312
|
+
resolve(
|
|
313
|
+
source: string,
|
|
314
|
+
importer?: string,
|
|
315
|
+
options?: { skipSelf?: boolean },
|
|
316
|
+
): Promise<{ id: string; external?: boolean | string } | null>;
|
|
317
|
+
}
|
|
318
|
+
|
|
273
319
|
interface FlightTransformPlugin {
|
|
274
320
|
name: string;
|
|
275
321
|
resolveId: {
|
|
276
322
|
order: "pre";
|
|
277
|
-
handler(
|
|
323
|
+
handler(
|
|
324
|
+
this: SourceResolveContext,
|
|
325
|
+
source: string,
|
|
326
|
+
importer: string | undefined,
|
|
327
|
+
): Promise<string | undefined>;
|
|
278
328
|
};
|
|
279
329
|
transform(
|
|
280
330
|
code: string,
|
|
@@ -347,19 +397,36 @@ export function rangoUseClientTransform(): FlightTransformPlugin {
|
|
|
347
397
|
resolveId: {
|
|
348
398
|
// Before Vite's resolver, which resolves the bare specifier first.
|
|
349
399
|
order: "pre",
|
|
350
|
-
handler(source, importer) {
|
|
400
|
+
async handler(source, importer) {
|
|
401
|
+
if (!importer?.includes(CLIENT_URLS_SOURCE_QUERY)) return undefined;
|
|
351
402
|
// A clientUrls() module's source resolves the router's client entry
|
|
352
403
|
// as the RSC environment does (the react-server condition): this
|
|
353
404
|
// project resolves it to client.tsx, a "use client" module whose
|
|
354
405
|
// clientUrls is a client reference here. Both entries export the
|
|
355
|
-
// same clientUrls.
|
|
406
|
+
// same clientUrls and helpers.
|
|
407
|
+
if (source === "@rangojs/router/client") return CLIENT_ENTRY_RSC_PATH;
|
|
408
|
+
// Its "use client" project imports load as source too, as the app's
|
|
409
|
+
// SSR copy runs them: a component's `.use` and its name are
|
|
410
|
+
// unreadable on a client reference. So do the barrels between them,
|
|
411
|
+
// which hold no state; any other module stays one shared instance.
|
|
412
|
+
const resolved = await this.resolve(source, importer, {
|
|
413
|
+
skipSelf: true,
|
|
414
|
+
});
|
|
415
|
+
if (!resolved || resolved.external) return undefined;
|
|
416
|
+
const { id } = resolved;
|
|
417
|
+
const file = id.split("?", 1)[0]!;
|
|
356
418
|
if (
|
|
357
|
-
|
|
358
|
-
|
|
419
|
+
id.startsWith("\0") ||
|
|
420
|
+
id.includes("/node_modules/") ||
|
|
421
|
+
id.includes(CLIENT_URLS_SOURCE_QUERY) ||
|
|
422
|
+
!/\.[cm]?[jt]sx?$/.test(file)
|
|
359
423
|
) {
|
|
360
|
-
return
|
|
424
|
+
return undefined;
|
|
361
425
|
}
|
|
362
|
-
|
|
426
|
+
const code = await readFile(file, "utf-8").catch(() => "");
|
|
427
|
+
return (await loadsAsSource(code, file))
|
|
428
|
+
? `${id}${id.includes("?") ? "&" : "?"}${CLIENT_URLS_SOURCE_QUERY}`
|
|
429
|
+
: undefined;
|
|
363
430
|
},
|
|
364
431
|
},
|
|
365
432
|
async transform(code, id) {
|
package/src/thenable-status.ts
CHANGED
|
@@ -24,14 +24,3 @@ export const SETTLED_THENABLE: Promise<undefined> = Object.assign(
|
|
|
24
24
|
Promise.resolve(undefined),
|
|
25
25
|
{ status: "fulfilled", value: undefined, _debugInfo: [] },
|
|
26
26
|
);
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* The value React reads from `value` without suspending: `value` itself when
|
|
30
|
-
* it is not a promise, a promise's `value` once it carries React's or Flight's
|
|
31
|
-
* `status: "fulfilled"`. Undefined while it is pending or rejected.
|
|
32
|
-
*/
|
|
33
|
-
export function settledValue<T>(value: T | Promise<T>): T | undefined {
|
|
34
|
-
if (!(value instanceof Promise)) return value;
|
|
35
|
-
const thenable = value as Promise<T> & { status?: string; value?: T };
|
|
36
|
-
return thenable.status === "fulfilled" ? thenable.value : undefined;
|
|
37
|
-
}
|
|
@@ -89,7 +89,13 @@ type AutofillSearchFromEntry<TMap, TKey> = TKey extends keyof TMap
|
|
|
89
89
|
: Record<string, unknown>
|
|
90
90
|
: Record<string, unknown>;
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* `ScopedReverseFunction` whose global-name params are optional: the runtime
|
|
94
|
+
* fills missing ones from the matched route's params (`createReverseFunction`
|
|
95
|
+
* in `router/handler-context.ts`, explicit params win). Shared by handler,
|
|
96
|
+
* loader and request-context `reverse`.
|
|
97
|
+
*/
|
|
98
|
+
export type AutofillAwareReverseFunction<TLocalRoutes, TGlobalRoutes> =
|
|
93
99
|
ScopedReverseFunction<TLocalRoutes, TGlobalRoutes> & {
|
|
94
100
|
<TName extends keyof TGlobalRoutes & string>(
|
|
95
101
|
name: TName,
|
|
@@ -449,6 +455,11 @@ export type InternalHandlerContext<
|
|
|
449
455
|
_responseType?: string;
|
|
450
456
|
/** Route name for cache key scoping (prevents cross-route collisions). */
|
|
451
457
|
_routeName?: string;
|
|
458
|
+
/**
|
|
459
|
+
* The segment's ReverseScope on a layout's / parallel's ctx and its
|
|
460
|
+
* loaders' (withReverseScope): keys a "use cache" call taking the ctx.
|
|
461
|
+
*/
|
|
462
|
+
_reverseScope?: import("../server/context.js").ReverseScope;
|
|
452
463
|
/**
|
|
453
464
|
* @internal The on-demand overlay holds a "removed" marker for this request
|
|
454
465
|
* (tryPrerenderLookup, cache-lookup.ts); gateOnDemandProducer reads it. On
|
|
@@ -2,7 +2,7 @@ import type { ContextVar } from "../context-var.js";
|
|
|
2
2
|
import type { Handle } from "../handle.js";
|
|
3
3
|
import type { HandlePush } from "../defer.js";
|
|
4
4
|
import type { MiddlewareFn } from "../router/middleware.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type { AutofillAwareReverseFunction } from "./handler-context.js";
|
|
6
6
|
import type { SearchSchema, ResolveSearchSchema } from "../search-params.js";
|
|
7
7
|
import type { UseItems, LoaderUseItem } from "../route-types.js";
|
|
8
8
|
import type {
|
|
@@ -134,9 +134,10 @@ export type LoaderContext<
|
|
|
134
134
|
/**
|
|
135
135
|
* Generate URLs from route names.
|
|
136
136
|
* Same scoped reverse as route handlers — `.name` resolves within the
|
|
137
|
-
* current include() scope, `name` resolves globally
|
|
137
|
+
* current include() scope, `name` resolves globally, and params the matched
|
|
138
|
+
* route already carries (e.g. include() mount params) are filled in.
|
|
138
139
|
*/
|
|
139
|
-
reverse:
|
|
140
|
+
reverse: AutofillAwareReverseFunction<
|
|
140
141
|
Record<string, string>,
|
|
141
142
|
DefaultReverseRouteMap
|
|
142
143
|
>;
|
package/src/types/route-entry.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { AllUseItems } from "../route-types.js";
|
|
2
|
+
import type { IncludeScopePosition } from "../server/context.js";
|
|
2
3
|
import type { TrailingSlashMode, ResolvedRouteMap } from "./route-config.js";
|
|
3
4
|
|
|
4
|
-
export interface LazyIncludeContext {
|
|
5
|
+
export interface LazyIncludeContext extends IncludeScopePosition {
|
|
5
6
|
urlPrefix: string;
|
|
6
7
|
namePrefix: string | undefined;
|
|
7
8
|
parent: unknown; // EntryData - avoid circular import
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
getUrlPrefix,
|
|
4
4
|
getNamePrefix,
|
|
5
5
|
requireDslContext,
|
|
6
|
+
type IncludeScopePosition,
|
|
6
7
|
} from "../server/context";
|
|
7
8
|
import {
|
|
8
9
|
INTERNAL_INCLUDE_SCOPE_PREFIX,
|
|
@@ -21,13 +22,32 @@ function hasExplicitNameOption(options: IncludeOptions | undefined): boolean {
|
|
|
21
22
|
return !!options && Object.prototype.hasOwnProperty.call(options, "name");
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
/**
|
|
26
|
+
* This include's index among the includes its handler run has called. The
|
|
27
|
+
* counter is keyed by the run's scopePath, which no ancestor run's counter
|
|
28
|
+
* snapshot holds, so every run counts from 0 in every realm (build discovery,
|
|
29
|
+
* runtime expansion, render-time manifest).
|
|
30
|
+
*/
|
|
31
|
+
function nextIncludePosition(
|
|
25
32
|
counters: Record<string, number>,
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
scopePath: string | undefined,
|
|
34
|
+
): number {
|
|
35
|
+
const key = `__include_position__:${scopePath ?? ""}`;
|
|
28
36
|
const index = counters[key] ?? 0;
|
|
29
37
|
counters[key] = index + 1;
|
|
30
|
-
return
|
|
38
|
+
return index;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function joinScopePath(parent: string | undefined, index: number): string {
|
|
42
|
+
return parent ? `${parent}_${index}` : `${index}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function joinNames(
|
|
46
|
+
prefix: string | undefined,
|
|
47
|
+
segment: string | undefined,
|
|
48
|
+
): string | undefined {
|
|
49
|
+
if (!segment) return prefix;
|
|
50
|
+
return prefix ? `${prefix}.${segment}` : segment;
|
|
31
51
|
}
|
|
32
52
|
|
|
33
53
|
/**
|
|
@@ -100,15 +120,20 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
|
100
120
|
? capturedUrlPrefix + prefix.slice(1)
|
|
101
121
|
: capturedUrlPrefix + prefix
|
|
102
122
|
: prefix;
|
|
123
|
+
// Route names are a pure function of the route tree: an unnamed include's
|
|
124
|
+
// hidden segment is its position path since the nearest include that added
|
|
125
|
+
// a name segment (a transparent { name: "" } adds none), e.g. `$prefix_1_0`.
|
|
126
|
+
const index = nextIncludePosition(ctx.counters, ctx.scopePath);
|
|
127
|
+
const hiddenSoFar = joinScopePath(ctx.hiddenScopePath, index);
|
|
103
128
|
const nextSegment = hasExplicitName
|
|
104
129
|
? explicitName
|
|
105
|
-
:
|
|
106
|
-
const fullNamePrefix =
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
130
|
+
: `${INTERNAL_INCLUDE_SCOPE_PREFIX}${hiddenSoFar}`;
|
|
131
|
+
const fullNamePrefix = joinNames(capturedNamePrefix, nextSegment);
|
|
132
|
+
const position: IncludeScopePosition = {
|
|
133
|
+
scopePath: joinScopePath(ctx.scopePath, index),
|
|
134
|
+
// A name segment restarts the hidden path for this include's children.
|
|
135
|
+
hiddenScopePath: nextSegment ? "" : hiddenSoFar,
|
|
136
|
+
};
|
|
112
137
|
|
|
113
138
|
// Track this include for build-time manifest generation
|
|
114
139
|
if (ctx.trackedIncludes) {
|
|
@@ -118,6 +143,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
|
118
143
|
namePrefix: fullNamePrefix,
|
|
119
144
|
patterns,
|
|
120
145
|
lazy: true,
|
|
146
|
+
...position,
|
|
121
147
|
});
|
|
122
148
|
}
|
|
123
149
|
|
|
@@ -171,6 +197,7 @@ export function createIncludeHelper<TEnv>(): IncludeFn<TEnv> {
|
|
|
171
197
|
rootScoped: capturedRootScoped,
|
|
172
198
|
routerId: ctx.routerId,
|
|
173
199
|
includeScope,
|
|
200
|
+
...position,
|
|
174
201
|
},
|
|
175
202
|
} as IncludeItem;
|
|
176
203
|
};
|
|
@@ -346,9 +346,11 @@ export type PathHelpers<TEnv> = {
|
|
|
346
346
|
};
|
|
347
347
|
|
|
348
348
|
/**
|
|
349
|
-
* Control when a segment should revalidate during navigation
|
|
349
|
+
* Control when a segment should revalidate during navigation.
|
|
350
|
+
* `revalidate(true)` / `revalidate(false)` are shorthand for
|
|
351
|
+
* `revalidate(() => true)` / `revalidate(() => false)`.
|
|
350
352
|
*/
|
|
351
|
-
revalidate: (fn: ShouldRevalidateFn<any, TEnv>) => RevalidateItem;
|
|
353
|
+
revalidate: (fn: ShouldRevalidateFn<any, TEnv> | boolean) => RevalidateItem;
|
|
352
354
|
|
|
353
355
|
/**
|
|
354
356
|
* Attach a data loader to the current route/layout.
|