@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
|
@@ -47,6 +47,8 @@ export interface ShellFrameHead {
|
|
|
47
47
|
ta?: number;
|
|
48
48
|
/** ShellCacheEntry.initialTheme */
|
|
49
49
|
i?: string;
|
|
50
|
+
/** ShellCacheEntry.pathname (a visitor-keyed shell's rendered location). */
|
|
51
|
+
lp?: string;
|
|
50
52
|
/** ShellCacheEntry.docKey; navigation replay needs it after any round trip. */
|
|
51
53
|
dk?: string;
|
|
52
54
|
/** ShellCacheEntry.prunedRecords (diagnostic). */
|
|
@@ -79,6 +81,7 @@ function isShellFrameHead(value: unknown): value is ShellFrameHead {
|
|
|
79
81
|
head.t.every((tag) => typeof tag === "string"))) &&
|
|
80
82
|
(head.ta === undefined || isFiniteNumber(head.ta)) &&
|
|
81
83
|
(head.i === undefined || typeof head.i === "string") &&
|
|
84
|
+
(head.lp === undefined || typeof head.lp === "string") &&
|
|
82
85
|
(head.dk === undefined || typeof head.dk === "string") &&
|
|
83
86
|
(head.pr === undefined || typeof head.pr === "string") &&
|
|
84
87
|
(head.no === undefined || head.no === true) &&
|
package/src/cache/types.ts
CHANGED
|
@@ -468,6 +468,14 @@ export interface ShellCacheEntry {
|
|
|
468
468
|
* ThemeProvider.
|
|
469
469
|
*/
|
|
470
470
|
initialTheme?: string;
|
|
471
|
+
/**
|
|
472
|
+
* The payload pathname of a shell keyed by the visitor's URL
|
|
473
|
+
* (`rewrite(...).shell("visitor")`, visitorShellKey); absent when the
|
|
474
|
+
* capture rendered its key's own URL. The serve tail replays it as it does
|
|
475
|
+
* `initialTheme`, and the serve gate serves the entry only to a request
|
|
476
|
+
* that renders this location.
|
|
477
|
+
*/
|
|
478
|
+
pathname?: string;
|
|
471
479
|
/**
|
|
472
480
|
* The CAPTURE DATA SNAPSHOT, in stored/serialized form: the doc segment
|
|
473
481
|
* record every HIT replays the handler layer from (`docKey`) and the
|
|
@@ -271,6 +271,8 @@ interface VercelShellEnvelope {
|
|
|
271
271
|
t?: string[];
|
|
272
272
|
/** initialTheme the capture render was built with (resume theme fidelity). */
|
|
273
273
|
i?: string;
|
|
274
|
+
/** ShellCacheEntry.pathname (a visitor-keyed shell's rendered location). */
|
|
275
|
+
lp?: string;
|
|
274
276
|
/** Capture data snapshot (ShellCacheEntry.snapshot). */
|
|
275
277
|
sn: ShellSnapshotRecord[];
|
|
276
278
|
/**
|
|
@@ -1158,6 +1160,7 @@ export class VercelCacheStore<
|
|
|
1158
1160
|
reactVersion: env.rv,
|
|
1159
1161
|
buildVersion: env.bv,
|
|
1160
1162
|
initialTheme: env.i,
|
|
1163
|
+
pathname: env.lp,
|
|
1161
1164
|
snapshot: env.sn,
|
|
1162
1165
|
docKey: env.dk,
|
|
1163
1166
|
prunedRecords: env.pr,
|
|
@@ -1326,6 +1329,7 @@ export class VercelCacheStore<
|
|
|
1326
1329
|
e: Math.min(expiresAt, now + retentionTtl * 1000),
|
|
1327
1330
|
t: safeTags.length > 0 ? safeTags : undefined,
|
|
1328
1331
|
i: entry.initialTheme,
|
|
1332
|
+
lp: entry.pathname,
|
|
1329
1333
|
sn: entry.snapshot,
|
|
1330
1334
|
dk: entry.docKey,
|
|
1331
1335
|
pr: entry.prunedRecords,
|
|
@@ -1743,7 +1747,7 @@ export class VercelCacheStore<
|
|
|
1743
1747
|
|
|
1744
1748
|
private asShellEnvelope(raw: unknown): VercelShellEnvelope | null {
|
|
1745
1749
|
if (!isRecord(raw)) return null;
|
|
1746
|
-
const { p, po, rv, bv, c, s, e, t, i, sn, dk, pr, no } = raw;
|
|
1750
|
+
const { p, po, rv, bv, c, s, e, t, i, lp, sn, dk, pr, no } = raw;
|
|
1747
1751
|
if (typeof rv !== "string" || typeof bv !== "string") return null;
|
|
1748
1752
|
// Document half: required unless navigationOnly (`no`), which stores
|
|
1749
1753
|
// neither field. Tolerate a legacy navigationOnly envelope that still
|
|
@@ -1766,6 +1770,7 @@ export class VercelCacheStore<
|
|
|
1766
1770
|
e,
|
|
1767
1771
|
t: Array.isArray(t) ? (t as string[]) : undefined,
|
|
1768
1772
|
i: typeof i === "string" ? i : undefined,
|
|
1773
|
+
lp: typeof lp === "string" ? lp : undefined,
|
|
1769
1774
|
// A non-array field reads as no records.
|
|
1770
1775
|
sn: Array.isArray(sn) ? (sn as ShellSnapshotRecord[]) : [],
|
|
1771
1776
|
dk: typeof dk === "string" ? dk : undefined,
|
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
ClientPathOptions,
|
|
10
10
|
ClientTransitionConfig,
|
|
11
11
|
ClientUrlBuilder,
|
|
12
|
+
ClientUrlComponent,
|
|
12
13
|
ClientUrlHelpers,
|
|
13
14
|
ClientUrlInterceptRecord,
|
|
14
15
|
ClientUrlItem,
|
|
@@ -224,10 +225,60 @@ function rejectItems(
|
|
|
224
225
|
}
|
|
225
226
|
}
|
|
226
227
|
|
|
228
|
+
const STRUCTURAL_ITEMS = new Set<InternalItem["type"]>([
|
|
229
|
+
"path",
|
|
230
|
+
"layout",
|
|
231
|
+
"intercept",
|
|
232
|
+
]);
|
|
233
|
+
const LEAF_ITEMS = new Set<InternalItem["type"]>(["loader", "loading"]);
|
|
234
|
+
const PATH_USE_ITEMS = new Set<InternalItem["type"]>([
|
|
235
|
+
"loader",
|
|
236
|
+
"loading",
|
|
237
|
+
"transition",
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* A mounted component's own items (`Dashboard.use = () => [...]`, the server
|
|
242
|
+
* handler.use pattern), validated for the mount site, followed by the site's
|
|
243
|
+
* `explicit` items, as mergeHandlerUse orders them on the server
|
|
244
|
+
* (route-definition/resolve-handler-use.ts): an explicit loading() or
|
|
245
|
+
* transition() wins, loaders accumulate.
|
|
246
|
+
*/
|
|
247
|
+
function componentUse(
|
|
248
|
+
component: ClientUrlComponent,
|
|
249
|
+
allowed: ReadonlySet<InternalItem["type"]>,
|
|
250
|
+
explicit: InternalItem[],
|
|
251
|
+
): InternalItem[] {
|
|
252
|
+
const use = component.use;
|
|
253
|
+
if (use === undefined) return explicit;
|
|
254
|
+
const label = `${component.name}.use`;
|
|
255
|
+
const items = runUse(use, label);
|
|
256
|
+
if (process.env.NODE_ENV !== "production") {
|
|
257
|
+
const structural = items.find((item) => STRUCTURAL_ITEMS.has(item.type));
|
|
258
|
+
if (structural) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`clientUrls() does not support ${structural.type}() inside ${label}: ` +
|
|
261
|
+
"routes come from the clientUrls() builder's own items, which " +
|
|
262
|
+
"route types and .gen.ts are generated from. Declare it there.",
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
rejectItems(items, allowed, label);
|
|
267
|
+
if (process.env.NODE_ENV !== "production") assertTransitionCount(items);
|
|
268
|
+
return [...items, ...explicit];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Dev-only: in production the last transition() wins (addPathRecord). */
|
|
272
|
+
function assertTransitionCount(items: readonly InternalItem[]): void {
|
|
273
|
+
if (items.filter((item) => item.type === "transition").length > 1) {
|
|
274
|
+
throw new Error("clientUrls() path() received more than one transition()");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
227
278
|
function assertNamedComponent(
|
|
228
279
|
component: unknown,
|
|
229
280
|
helper: "path" | "layout" | "intercept",
|
|
230
|
-
): asserts component is
|
|
281
|
+
): asserts component is ClientUrlComponent {
|
|
231
282
|
// Build-time handler wrappers get a TARGETED rejection ahead of the generic
|
|
232
283
|
// shape check: their handler code cannot live in a "use client" bundle and
|
|
233
284
|
// their render runs at build, so accepting one could only fail later and
|
|
@@ -276,10 +327,139 @@ function assertLoaderDefinition(
|
|
|
276
327
|
}
|
|
277
328
|
}
|
|
278
329
|
|
|
330
|
+
// Stateless item constructors: the builder callback hands these out, and
|
|
331
|
+
// `@rangojs/router/client` exports them for a component's `.use`.
|
|
332
|
+
|
|
333
|
+
export const loader: ClientUrlHelpers["loader"] = (
|
|
334
|
+
definition,
|
|
335
|
+
optionsOrUse,
|
|
336
|
+
maybeUse,
|
|
337
|
+
) => {
|
|
338
|
+
assertLoaderDefinition(definition);
|
|
339
|
+
// Same options-or-use disambiguation as the server DSL loader(): the
|
|
340
|
+
// 2-arg loader(Def, use) form is untouched.
|
|
341
|
+
if (typeof optionsOrUse === "function" && maybeUse !== undefined) {
|
|
342
|
+
throw new Error(
|
|
343
|
+
"clientUrls() loader() received two use() callbacks. Pass loader(Def, options, use) or loader(Def, use).",
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
const options = typeof optionsOrUse === "function" ? undefined : optionsOrUse;
|
|
347
|
+
const use = typeof optionsOrUse === "function" ? optionsOrUse : maybeUse;
|
|
348
|
+
if ((options as { stream?: unknown } | undefined)?.stream !== undefined) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
"clientUrls() loader() stream was replaced: use loader(Def, { ssr: false }) — the same knob as loading(fallback, { ssr: false }) — to await the loader before first flush on document requests.",
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
// Inline, not errors.ts assertBooleanOption: this module is in the
|
|
354
|
+
// client router chunk, where the helper costs 20 B gzip.
|
|
355
|
+
if (options?.ssr !== undefined && typeof options.ssr !== "boolean") {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`clientUrls() loader() ssr must be a boolean (got ${JSON.stringify(options.ssr)}). Omit it (or pass true) to stream on every render.`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
if (
|
|
361
|
+
process.env.NODE_ENV !== "production" &&
|
|
362
|
+
options?.prefetch !== undefined &&
|
|
363
|
+
typeof options.prefetch !== "boolean"
|
|
364
|
+
) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
`clientUrls() loader() prefetch must be a boolean (got ${JSON.stringify(options.prefetch)}). Omit it (or pass true) to run the loader in prefetches.`,
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
const items = use ? runUse(use, "loader use") : [];
|
|
370
|
+
rejectItems(items, new Set(["revalidate"]), "loader");
|
|
371
|
+
return toPublicItem({
|
|
372
|
+
[ITEM_KIND]: true,
|
|
373
|
+
type: "loader",
|
|
374
|
+
definition,
|
|
375
|
+
revalidate: items
|
|
376
|
+
.filter(
|
|
377
|
+
(item): item is Extract<InternalItem, { type: "revalidate" }> =>
|
|
378
|
+
item.type === "revalidate",
|
|
379
|
+
)
|
|
380
|
+
.map((item) => item.fn),
|
|
381
|
+
...(options?.ssr === false ? { ssr: false as const } : {}),
|
|
382
|
+
...(options?.prefetch === false ? { prefetch: false as const } : {}),
|
|
383
|
+
});
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
export const revalidate: ClientUrlHelpers["revalidate"] = (fn) => {
|
|
387
|
+
// Dev-only: client-urls-revalidate-hoist.ts rejects the rest at build.
|
|
388
|
+
if (
|
|
389
|
+
process.env.NODE_ENV !== "production" &&
|
|
390
|
+
typeof fn !== "function" &&
|
|
391
|
+
typeof fn !== "boolean" &&
|
|
392
|
+
!isHoistedRevalidateRef(fn)
|
|
393
|
+
) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"clientUrls() revalidate() expects a predicate function or a boolean",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return toPublicItem({
|
|
399
|
+
[ITEM_KIND]: true,
|
|
400
|
+
type: "revalidate",
|
|
401
|
+
fn,
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
export const loading: ClientUrlHelpers["loading"] = (component) =>
|
|
406
|
+
toPublicItem({ [ITEM_KIND]: true, type: "loading", component });
|
|
407
|
+
|
|
408
|
+
export const transition: ClientUrlHelpers["transition"] = (config) => {
|
|
409
|
+
if (typeof config !== "object" || config === null || Array.isArray(config)) {
|
|
410
|
+
throw new Error("clientUrls() transition() expects a configuration object");
|
|
411
|
+
}
|
|
412
|
+
for (const key of Reflect.ownKeys(config)) {
|
|
413
|
+
if (!TRANSITION_CONFIG_KEYS.has(key)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
`clientUrls() transition() option ${JSON.stringify(String(key))} is not supported`,
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
if (config.name !== undefined && typeof config.name !== "string") {
|
|
420
|
+
throw new Error("clientUrls() transition() name must be a string");
|
|
421
|
+
}
|
|
422
|
+
if (config.when !== undefined && typeof config.when !== "function") {
|
|
423
|
+
throw new TypeError(
|
|
424
|
+
process.env.NODE_ENV !== "production"
|
|
425
|
+
? `clientUrls() transition() when must be a function (it runs in the browser), got ${config.when === null ? "null" : typeof config.when}`
|
|
426
|
+
: "clientUrls() transition() when must be a function",
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (
|
|
430
|
+
config.viewTransition !== undefined &&
|
|
431
|
+
config.viewTransition !== "auto" &&
|
|
432
|
+
config.viewTransition !== false
|
|
433
|
+
) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
'clientUrls() transition() viewTransition must be "auto" or false',
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
for (const key of ["enter", "exit", "update", "share", "default"] as const) {
|
|
439
|
+
const value = config[key];
|
|
440
|
+
if (value === undefined || typeof value === "string") continue;
|
|
441
|
+
const isStringMap =
|
|
442
|
+
typeof value === "object" &&
|
|
443
|
+
value !== null &&
|
|
444
|
+
!Array.isArray(value) &&
|
|
445
|
+
Object.values(value).every((entry) => typeof entry === "string");
|
|
446
|
+
if (!isStringMap) {
|
|
447
|
+
throw new Error(
|
|
448
|
+
`clientUrls() transition() ${key} must be a string or a string map of transition types`,
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return toPublicItem({
|
|
453
|
+
[ITEM_KIND]: true,
|
|
454
|
+
type: "transition",
|
|
455
|
+
config: { ...config },
|
|
456
|
+
});
|
|
457
|
+
};
|
|
458
|
+
|
|
279
459
|
function createHelpers(): ClientUrlHelpers {
|
|
280
460
|
const path = ((
|
|
281
461
|
pattern: string,
|
|
282
|
-
component:
|
|
462
|
+
component: ClientUrlComponent,
|
|
283
463
|
optionsOrUse?: ClientPathOptions | ClientUrlUse,
|
|
284
464
|
maybeUse?: ClientUrlUse,
|
|
285
465
|
): ClientUrlItem => {
|
|
@@ -334,24 +514,20 @@ function createHelpers(): ClientUrlHelpers {
|
|
|
334
514
|
}
|
|
335
515
|
|
|
336
516
|
const items = use ? runUse(use, "path use") : [];
|
|
337
|
-
rejectItems(items,
|
|
338
|
-
if (
|
|
339
|
-
throw new Error(
|
|
340
|
-
"clientUrls() path() received more than one transition()",
|
|
341
|
-
);
|
|
342
|
-
}
|
|
517
|
+
rejectItems(items, PATH_USE_ITEMS, "path");
|
|
518
|
+
if (process.env.NODE_ENV !== "production") assertTransitionCount(items);
|
|
343
519
|
return toPublicItem({
|
|
344
520
|
[ITEM_KIND]: true,
|
|
345
521
|
type: "path",
|
|
346
522
|
pattern,
|
|
347
523
|
component,
|
|
348
524
|
options,
|
|
349
|
-
use: items,
|
|
525
|
+
use: componentUse(component, PATH_USE_ITEMS, items),
|
|
350
526
|
});
|
|
351
527
|
}) as ClientUrlHelpers["path"];
|
|
352
528
|
|
|
353
529
|
const layout = ((
|
|
354
|
-
component:
|
|
530
|
+
component: ClientUrlComponent,
|
|
355
531
|
children: ClientUrlUse,
|
|
356
532
|
): ClientUrlItem => {
|
|
357
533
|
assertNamedComponent(component, "layout");
|
|
@@ -361,147 +537,16 @@ function createHelpers(): ClientUrlHelpers {
|
|
|
361
537
|
new Set(["path", "layout", "loader", "loading", "intercept"]),
|
|
362
538
|
"layout",
|
|
363
539
|
);
|
|
540
|
+
// Merged here, before flattenLayout's applyConfig creates the layout's
|
|
541
|
+
// loader records once for every route below it (layoutLoaders identity).
|
|
364
542
|
return toPublicItem({
|
|
365
543
|
[ITEM_KIND]: true,
|
|
366
544
|
type: "layout",
|
|
367
545
|
component,
|
|
368
|
-
children: items,
|
|
546
|
+
children: componentUse(component, LEAF_ITEMS, items),
|
|
369
547
|
});
|
|
370
548
|
}) as ClientUrlHelpers["layout"];
|
|
371
549
|
|
|
372
|
-
const loader: ClientUrlHelpers["loader"] = (
|
|
373
|
-
definition,
|
|
374
|
-
optionsOrUse,
|
|
375
|
-
maybeUse,
|
|
376
|
-
) => {
|
|
377
|
-
assertLoaderDefinition(definition);
|
|
378
|
-
// Same options-or-use disambiguation as the server DSL loader(): the
|
|
379
|
-
// 2-arg loader(Def, use) form is untouched.
|
|
380
|
-
if (typeof optionsOrUse === "function" && maybeUse !== undefined) {
|
|
381
|
-
throw new Error(
|
|
382
|
-
"clientUrls() loader() received two use() callbacks. Pass loader(Def, options, use) or loader(Def, use).",
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
const options =
|
|
386
|
-
typeof optionsOrUse === "function" ? undefined : optionsOrUse;
|
|
387
|
-
const use = typeof optionsOrUse === "function" ? optionsOrUse : maybeUse;
|
|
388
|
-
if ((options as { stream?: unknown } | undefined)?.stream !== undefined) {
|
|
389
|
-
throw new Error(
|
|
390
|
-
"clientUrls() loader() stream was replaced: use loader(Def, { ssr: false }) — the same knob as loading(fallback, { ssr: false }) — to await the loader before first flush on document requests.",
|
|
391
|
-
);
|
|
392
|
-
}
|
|
393
|
-
// Inline, not errors.ts assertBooleanOption: this module is in the
|
|
394
|
-
// client router chunk, where the helper costs 20 B gzip.
|
|
395
|
-
if (options?.ssr !== undefined && typeof options.ssr !== "boolean") {
|
|
396
|
-
throw new Error(
|
|
397
|
-
`clientUrls() loader() ssr must be a boolean (got ${JSON.stringify(options.ssr)}). Omit it (or pass true) to stream on every render.`,
|
|
398
|
-
);
|
|
399
|
-
}
|
|
400
|
-
if (
|
|
401
|
-
process.env.NODE_ENV !== "production" &&
|
|
402
|
-
options?.prefetch !== undefined &&
|
|
403
|
-
typeof options.prefetch !== "boolean"
|
|
404
|
-
) {
|
|
405
|
-
throw new Error(
|
|
406
|
-
`clientUrls() loader() prefetch must be a boolean (got ${JSON.stringify(options.prefetch)}). Omit it (or pass true) to run the loader in prefetches.`,
|
|
407
|
-
);
|
|
408
|
-
}
|
|
409
|
-
const items = use ? runUse(use, "loader use") : [];
|
|
410
|
-
rejectItems(items, new Set(["revalidate"]), "loader");
|
|
411
|
-
return toPublicItem({
|
|
412
|
-
[ITEM_KIND]: true,
|
|
413
|
-
type: "loader",
|
|
414
|
-
definition,
|
|
415
|
-
revalidate: items
|
|
416
|
-
.filter(
|
|
417
|
-
(item): item is Extract<InternalItem, { type: "revalidate" }> =>
|
|
418
|
-
item.type === "revalidate",
|
|
419
|
-
)
|
|
420
|
-
.map((item) => item.fn),
|
|
421
|
-
...(options?.ssr === false ? { ssr: false as const } : {}),
|
|
422
|
-
...(options?.prefetch === false ? { prefetch: false as const } : {}),
|
|
423
|
-
});
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
const revalidate: ClientUrlHelpers["revalidate"] = (fn) => {
|
|
427
|
-
// The build leaves a marker in place of the predicate it moved to the
|
|
428
|
-
// server (vite/plugins/client-urls-revalidate-hoist.ts).
|
|
429
|
-
if (typeof fn !== "function" && !isHoistedRevalidateRef(fn)) {
|
|
430
|
-
throw new Error("clientUrls() revalidate() expects a predicate function");
|
|
431
|
-
}
|
|
432
|
-
return toPublicItem({
|
|
433
|
-
[ITEM_KIND]: true,
|
|
434
|
-
type: "revalidate",
|
|
435
|
-
fn,
|
|
436
|
-
});
|
|
437
|
-
};
|
|
438
|
-
|
|
439
|
-
const loading: ClientUrlHelpers["loading"] = (component) =>
|
|
440
|
-
toPublicItem({ [ITEM_KIND]: true, type: "loading", component });
|
|
441
|
-
|
|
442
|
-
const transition: ClientUrlHelpers["transition"] = (config) => {
|
|
443
|
-
if (
|
|
444
|
-
typeof config !== "object" ||
|
|
445
|
-
config === null ||
|
|
446
|
-
Array.isArray(config)
|
|
447
|
-
) {
|
|
448
|
-
throw new Error(
|
|
449
|
-
"clientUrls() transition() expects a configuration object",
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
for (const key of Reflect.ownKeys(config)) {
|
|
453
|
-
if (!TRANSITION_CONFIG_KEYS.has(key)) {
|
|
454
|
-
throw new Error(
|
|
455
|
-
`clientUrls() transition() option ${JSON.stringify(String(key))} is not supported`,
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
if (config.name !== undefined && typeof config.name !== "string") {
|
|
460
|
-
throw new Error("clientUrls() transition() name must be a string");
|
|
461
|
-
}
|
|
462
|
-
if (config.when !== undefined && typeof config.when !== "function") {
|
|
463
|
-
throw new TypeError(
|
|
464
|
-
process.env.NODE_ENV !== "production"
|
|
465
|
-
? `clientUrls() transition() when must be a function (it runs in the browser), got ${config.when === null ? "null" : typeof config.when}`
|
|
466
|
-
: "clientUrls() transition() when must be a function",
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
if (
|
|
470
|
-
config.viewTransition !== undefined &&
|
|
471
|
-
config.viewTransition !== "auto" &&
|
|
472
|
-
config.viewTransition !== false
|
|
473
|
-
) {
|
|
474
|
-
throw new Error(
|
|
475
|
-
'clientUrls() transition() viewTransition must be "auto" or false',
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
for (const key of [
|
|
479
|
-
"enter",
|
|
480
|
-
"exit",
|
|
481
|
-
"update",
|
|
482
|
-
"share",
|
|
483
|
-
"default",
|
|
484
|
-
] as const) {
|
|
485
|
-
const value = config[key];
|
|
486
|
-
if (value === undefined || typeof value === "string") continue;
|
|
487
|
-
const isStringMap =
|
|
488
|
-
typeof value === "object" &&
|
|
489
|
-
value !== null &&
|
|
490
|
-
!Array.isArray(value) &&
|
|
491
|
-
Object.values(value).every((entry) => typeof entry === "string");
|
|
492
|
-
if (!isStringMap) {
|
|
493
|
-
throw new Error(
|
|
494
|
-
`clientUrls() transition() ${key} must be a string or a string map of transition types`,
|
|
495
|
-
);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
return toPublicItem({
|
|
499
|
-
[ITEM_KIND]: true,
|
|
500
|
-
type: "transition",
|
|
501
|
-
config: { ...config },
|
|
502
|
-
});
|
|
503
|
-
};
|
|
504
|
-
|
|
505
550
|
const intercept: ClientUrlHelpers["intercept"] = (
|
|
506
551
|
slotName,
|
|
507
552
|
targetName,
|
|
@@ -523,8 +568,9 @@ function createHelpers(): ClientUrlHelpers {
|
|
|
523
568
|
);
|
|
524
569
|
}
|
|
525
570
|
assertNamedComponent(component, "intercept");
|
|
526
|
-
const
|
|
527
|
-
rejectItems(
|
|
571
|
+
const own = use ? runUse(use, "intercept use") : [];
|
|
572
|
+
rejectItems(own, LEAF_ITEMS, "intercept");
|
|
573
|
+
const items = componentUse(component, LEAF_ITEMS, own);
|
|
528
574
|
for (const item of items) {
|
|
529
575
|
// Intercept loaders run on soft navigations only — a document-render
|
|
530
576
|
// await can never apply, so accepting the flag would be silently inert.
|
|
@@ -608,11 +654,12 @@ function addPathRecord(
|
|
|
608
654
|
const routeContext = applyConfig(context, item.use);
|
|
609
655
|
const options = item.options ? Object.freeze({ ...item.options }) : undefined;
|
|
610
656
|
// Route-scoped only: transition() is rejected outside path() use callbacks,
|
|
611
|
-
// so there is no layout-level inheritance to fold in.
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
657
|
+
// so there is no layout-level inheritance to fold in. The last one wins:
|
|
658
|
+
// the explicit use callback's over the component's `.use`.
|
|
659
|
+
let transitionItem: Extract<InternalItem, { type: "transition" }> | undefined;
|
|
660
|
+
for (const useItem of item.use) {
|
|
661
|
+
if (useItem.type === "transition") transitionItem = useItem;
|
|
662
|
+
}
|
|
616
663
|
routes.push(
|
|
617
664
|
Object.freeze({
|
|
618
665
|
id: `client-route-${routes.length}`,
|
|
@@ -703,12 +750,13 @@ function collectIntercepts(
|
|
|
703
750
|
let loading: ReactNode | undefined;
|
|
704
751
|
for (const useItem of item.use) {
|
|
705
752
|
if (useItem.type === "loader") {
|
|
706
|
-
// `prefetch` is not carried: an intercept prefetch defers nothing,
|
|
707
|
-
// so the flag is accepted and ignored, as on a urls() intercept().
|
|
708
753
|
loaders.push(
|
|
709
754
|
Object.freeze({
|
|
710
755
|
loader: useItem.definition,
|
|
711
756
|
revalidate: Object.freeze([...useItem.revalidate]),
|
|
757
|
+
...(useItem.prefetch === false
|
|
758
|
+
? { prefetch: false as const }
|
|
759
|
+
: {}),
|
|
712
760
|
}),
|
|
713
761
|
);
|
|
714
762
|
} else if (useItem.type === "loading") {
|
|
@@ -824,6 +872,7 @@ export function buildClientUrlsTrie(
|
|
|
824
872
|
export type {
|
|
825
873
|
ClientTransitionConfig,
|
|
826
874
|
ClientUrlBuilder,
|
|
875
|
+
ClientUrlComponent,
|
|
827
876
|
ClientUrlHelpers,
|
|
828
877
|
ClientUrlInterceptRecord,
|
|
829
878
|
ClientUrlItem,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { toShouldRevalidateFn } from "../router/revalidation.js";
|
|
1
2
|
import type { ShouldRevalidateFn } from "../types.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -6,7 +7,9 @@ import type { ShouldRevalidateFn } from "../types.js";
|
|
|
6
7
|
* group's module loads in the RSC realm
|
|
7
8
|
* (vite/plugins/client-urls-revalidate-hoist.ts); materialized loader stubs
|
|
8
9
|
* read it (server-projection.ts). A key hashes the file and the predicate's
|
|
9
|
-
* syntax, so a re-registration under one key is the same predicate.
|
|
10
|
+
* syntax, so a re-registration under one key is the same predicate. A
|
|
11
|
+
* hoisted boolean (`revalidate(FLAG)`) is stored as its constant predicate;
|
|
12
|
+
* any other value is left unregistered for materialization to name.
|
|
10
13
|
*
|
|
11
14
|
* Server-only build output, not router state: no router hook reads it
|
|
12
15
|
* (AGENTS.md Hard rule 8).
|
|
@@ -14,9 +17,15 @@ import type { ShouldRevalidateFn } from "../types.js";
|
|
|
14
17
|
const revalidators = new Map<string, ShouldRevalidateFn<any, any>>();
|
|
15
18
|
|
|
16
19
|
export function registerClientUrlRevalidators(
|
|
17
|
-
fns: Readonly<Record<string, ShouldRevalidateFn<any, any
|
|
20
|
+
fns: Readonly<Record<string, ShouldRevalidateFn<any, any> | boolean>>,
|
|
18
21
|
): void {
|
|
19
|
-
for (const [key, fn] of Object.entries(fns))
|
|
22
|
+
for (const [key, fn] of Object.entries(fns)) {
|
|
23
|
+
if (typeof fn === "function" || typeof fn === "boolean") {
|
|
24
|
+
revalidators.set(key, toShouldRevalidateFn(fn));
|
|
25
|
+
} else {
|
|
26
|
+
revalidators.delete(key);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
export function getClientUrlRevalidator(
|