@rangojs/router 0.5.2 → 0.7.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/dist/bin/rango.js +343 -125
- package/dist/types/browser/react/use-router.d.ts +10 -3
- package/dist/types/browser/react/use-search-params.d.ts +57 -10
- package/dist/types/browser/types.d.ts +22 -0
- package/dist/types/build/merge-full-manifests.d.ts +3 -0
- package/dist/types/build/route-trie.d.ts +4 -73
- package/dist/types/build/route-types/per-module-writer.d.ts +6 -4
- package/dist/types/build/route-types/router-processing.d.ts +2 -3
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/taint.d.ts +12 -6
- package/dist/types/client-urls/client-root.d.ts +38 -0
- package/dist/types/client-urls/client-urls.d.ts +5 -0
- package/dist/types/client-urls/navigation.d.ts +38 -0
- package/dist/types/client-urls/revalidation-protocol.d.ts +25 -0
- package/dist/types/client-urls/server-projection.d.ts +71 -0
- package/dist/types/client-urls/types.d.ts +147 -0
- package/dist/types/client.d.ts +12 -4
- package/dist/types/client.rsc.d.ts +4 -1
- package/dist/types/decode-loader-results.d.ts +37 -0
- package/dist/types/errors.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +27 -0
- package/dist/types/outlet-context.d.ts +12 -0
- package/dist/types/outlet-provider.d.ts +3 -1
- package/dist/types/redirect-origin.d.ts +4 -0
- package/dist/types/route-content-wrapper.d.ts +42 -1
- package/dist/types/route-definition/helpers-types.d.ts +13 -2
- package/dist/types/router/error-handling.d.ts +35 -1
- package/dist/types/router/intercept-resolution.d.ts +12 -0
- package/dist/types/router/loader-resolution.d.ts +24 -2
- package/dist/types/router/revalidation.d.ts +7 -0
- package/dist/types/router/route-trie-builder.d.ts +77 -0
- package/dist/types/router/router-interfaces.d.ts +20 -0
- package/dist/types/router/segment-resolution/helpers.d.ts +1 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -9
- package/dist/types/router/trie-matching.d.ts +1 -1
- package/dist/types/rsc/manifest-init.d.ts +5 -5
- package/dist/types/rsc/shell-capture.d.ts +9 -0
- package/dist/types/rsc/shell-serve.d.ts +11 -0
- package/dist/types/rsc/types.d.ts +30 -0
- package/dist/types/segment-system.d.ts +2 -0
- package/dist/types/server/context.d.ts +10 -0
- package/dist/types/server/handle-store.d.ts +34 -3
- package/dist/types/server/request-context.d.ts +11 -1
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +22 -0
- package/dist/types/ssr/ssr-root.d.ts +10 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +16 -6
- package/dist/types/testing/run-loader.d.ts +9 -0
- package/dist/types/types/boundaries.d.ts +22 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/loader-types.d.ts +57 -5
- package/dist/types/types/segments.d.ts +7 -0
- package/dist/types/urls/path-helper-types.d.ts +13 -4
- package/dist/types/vite/discovery/client-urls-projection.d.ts +53 -0
- package/dist/types/vite/discovery/discover-routers.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +8 -1
- package/dist/types/vite/plugins/client-ref-dedup.d.ts +0 -11
- package/dist/vite/index.js +6159 -2959
- package/package.json +6 -5
- package/skills/breadcrumbs/SKILL.md +39 -9
- package/skills/catalog.json +7 -1
- package/skills/client-urls/SKILL.md +338 -0
- package/skills/comparison/references/framework-comparison.md +23 -9
- package/skills/hooks/SKILL.md +2 -2
- package/skills/hooks/data.md +11 -2
- package/skills/hooks/handle-and-actions.md +7 -0
- package/skills/hooks/outlets.md +26 -5
- package/skills/hooks/urls.md +40 -3
- package/skills/loader/SKILL.md +132 -20
- package/skills/migrate-nextjs/SKILL.md +70 -10
- package/skills/migrate-react-router/SKILL.md +49 -13
- package/skills/migrate-react-router/component-migration.md +18 -13
- package/skills/migrate-react-router/data-and-actions.md +14 -3
- package/skills/migrate-react-router/route-mapping.md +15 -2
- package/skills/parallel/SKILL.md +32 -1
- package/skills/ppr/SKILL.md +16 -6
- package/skills/prerender/SKILL.md +8 -4
- package/skills/rango/SKILL.md +21 -17
- package/skills/react-compiler/SKILL.md +3 -3
- package/skills/route/SKILL.md +5 -2
- package/skills/router-setup/SKILL.md +16 -2
- package/skills/scripts/SKILL.md +16 -6
- package/skills/shell-manifest/SKILL.md +16 -7
- package/skills/testing/SKILL.md +2 -2
- package/skills/testing/client-components.md +6 -0
- package/skills/testing/handles.md +30 -8
- package/skills/testing/loader.md +51 -49
- package/skills/testing/middleware.md +1 -1
- package/skills/theme/SKILL.md +8 -5
- package/src/bin/rango.ts +7 -3
- package/src/browser/navigation-bridge.ts +6 -0
- package/src/browser/navigation-client.ts +5 -0
- package/src/browser/partial-update.ts +65 -13
- package/src/browser/react/use-router.ts +40 -11
- package/src/browser/react/use-search-params.ts +140 -17
- package/src/browser/rsc-router.tsx +59 -0
- package/src/browser/server-action-bridge.ts +26 -0
- package/src/browser/types.ts +22 -0
- package/src/build/merge-full-manifests.ts +161 -0
- package/src/build/route-trie.ts +9 -332
- package/src/build/route-types/include-resolution.ts +66 -11
- package/src/build/route-types/per-module-writer.ts +11 -6
- package/src/build/route-types/router-processing.ts +184 -153
- package/src/build/runtime-discovery.ts +23 -12
- package/src/cache/cache-exec-scope.ts +47 -0
- package/src/cache/cache-runtime.ts +24 -25
- package/src/cache/taint.ts +28 -9
- package/src/client-urls/client-root.tsx +168 -0
- package/src/client-urls/client-urls.ts +776 -0
- package/src/client-urls/navigation.ts +237 -0
- package/src/client-urls/revalidation-protocol.ts +56 -0
- package/src/client-urls/server-projection.ts +670 -0
- package/src/client-urls/types.ts +201 -0
- package/src/client.rsc.tsx +12 -0
- package/src/client.tsx +49 -6
- package/src/decode-loader-results.ts +113 -0
- package/src/errors.ts +14 -0
- package/src/handles/deferred-resolution.ts +14 -7
- package/src/index.ts +1 -0
- package/src/loader-redirect.tsx +64 -0
- package/src/outlet-context.ts +12 -0
- package/src/outlet-provider.tsx +15 -1
- package/src/redirect-origin.ts +29 -0
- package/src/route-content-wrapper.tsx +96 -3
- package/src/route-definition/dsl-helpers.ts +28 -3
- package/src/route-definition/helpers-types.ts +13 -0
- package/src/route-definition/redirect.ts +17 -18
- package/src/router/error-handling.ts +65 -11
- package/src/router/intercept-resolution.ts +29 -0
- package/src/router/loader-resolution.ts +261 -28
- package/src/router/match-result.ts +7 -0
- package/src/router/revalidation.ts +24 -11
- package/src/router/route-trie-builder.ts +334 -0
- package/src/router/router-interfaces.ts +38 -0
- package/src/router/segment-resolution/fresh.ts +55 -2
- package/src/router/segment-resolution/helpers.ts +9 -11
- package/src/router/segment-resolution/loader-cache.ts +34 -23
- package/src/router/segment-resolution/loader-mask.ts +9 -9
- package/src/router/segment-resolution/revalidation.ts +23 -2
- package/src/router/trie-matching.ts +3 -3
- package/src/router.ts +46 -1
- package/src/rsc/full-payload.ts +6 -0
- package/src/rsc/handler.ts +10 -7
- package/src/rsc/loader-fetch.ts +2 -2
- package/src/rsc/manifest-init.ts +28 -9
- package/src/rsc/rsc-rendering.ts +15 -1
- package/src/rsc/shell-capture.ts +12 -0
- package/src/rsc/shell-serve.ts +15 -2
- package/src/rsc/ssr-setup.ts +10 -1
- package/src/rsc/types.ts +31 -2
- package/src/segment-system.tsx +83 -26
- package/src/server/context.ts +10 -0
- package/src/server/cookie-store.ts +19 -19
- package/src/server/handle-store.ts +185 -48
- package/src/server/request-context.ts +30 -6
- package/src/server.ts +7 -0
- package/src/ssr/index.tsx +37 -2
- package/src/ssr/ssr-root.tsx +29 -2
- package/src/testing/dom.entry.ts +1 -1
- package/src/testing/render-route.tsx +22 -8
- package/src/testing/run-loader.ts +51 -13
- package/src/types/boundaries.ts +19 -0
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +60 -5
- package/src/types/segments.ts +7 -0
- package/src/urls/include-helper.ts +22 -4
- package/src/urls/path-helper-types.ts +17 -1
- package/src/use-loader.tsx +67 -6
- package/src/vite/discovery/client-urls-projection.ts +322 -0
- package/src/vite/discovery/discover-routers.ts +43 -17
- package/src/vite/discovery/state.ts +11 -1
- package/src/vite/discovery/virtual-module-codegen.ts +20 -0
- package/src/vite/plugins/client-ref-dedup.ts +281 -19
- package/src/vite/plugins/expose-action-id.ts +45 -23
- package/src/vite/plugins/virtual-entries.ts +12 -3
- package/src/vite/router-discovery.ts +163 -12
|
@@ -129,6 +129,14 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
129
129
|
|
|
130
130
|
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
131
131
|
|
|
132
|
+
// Pin `_currentSegmentId` to the OWNING entry before the kickoffs below —
|
|
133
|
+
// createLoaderExecutor captures it synchronously for ctx.use(Handle) push
|
|
134
|
+
// attribution. Same pin as resolveLoaders (fresh.ts); this REVALIDATION
|
|
135
|
+
// funnel is the navigation/action lane's kickoff site, where nothing else
|
|
136
|
+
// assigns the id first (pushes silently vanished: an id outside
|
|
137
|
+
// matched/segmentOrder is dropped by collectHandleData).
|
|
138
|
+
(ctx as InternalHandlerContext<any, TEnv>)._currentSegmentId = shortCode;
|
|
139
|
+
|
|
132
140
|
const loaderMeta = loaderEntries.map((loaderEntry, i) => ({
|
|
133
141
|
loaderEntry,
|
|
134
142
|
loader: loaderEntry.loader,
|
|
@@ -232,7 +240,9 @@ export async function resolveLoadersWithRevalidation<TEnv>(
|
|
|
232
240
|
loaderEntry,
|
|
233
241
|
ctx,
|
|
234
242
|
ctx.pathname,
|
|
235
|
-
bakeLane
|
|
243
|
+
bakeLane || loaderEntry.awaitBeforeFlush === true
|
|
244
|
+
? segmentId
|
|
245
|
+
: null,
|
|
236
246
|
),
|
|
237
247
|
),
|
|
238
248
|
entry,
|
|
@@ -605,7 +615,17 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
605
615
|
// For non-empty client sets, consult user revalidate fns. When the slot
|
|
606
616
|
// is unknown to the client, override the type-derived default so the
|
|
607
617
|
// soft chain seeds with the right "new segment" / "parent-chain" value.
|
|
608
|
-
|
|
618
|
+
//
|
|
619
|
+
// A "new segment" seed is floored: the client has no cached copy of this
|
|
620
|
+
// slot, so a user `false` would render component:null and leave it blank
|
|
621
|
+
// rather than keep anything. Sibling resolvers (loaders, layout/route
|
|
622
|
+
// entries, orphan layouts) get the same guarantee by short-circuiting
|
|
623
|
+
// without consulting user fns at all; #482 made this path consult them so
|
|
624
|
+
// a "skip-parent-chain" seed could still be raised, so floor instead of
|
|
625
|
+
// short-circuiting — user fns run, and may only raise.
|
|
626
|
+
let defaultOverride:
|
|
627
|
+
| { value: boolean; reason: string; floor?: boolean }
|
|
628
|
+
| undefined;
|
|
609
629
|
if (!clientSegmentIds.has(parallelId)) {
|
|
610
630
|
const value =
|
|
611
631
|
parentChainDefault === "force-render"
|
|
@@ -614,6 +634,7 @@ export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
|
614
634
|
defaultOverride = {
|
|
615
635
|
value,
|
|
616
636
|
reason: value ? "new-segment" : "skip-parent-chain",
|
|
637
|
+
floor: value,
|
|
617
638
|
};
|
|
618
639
|
}
|
|
619
640
|
|
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
TrieNode,
|
|
10
10
|
TrieLeaf,
|
|
11
11
|
NegotiateVariant,
|
|
12
|
-
} from "
|
|
12
|
+
} from "./route-trie-builder.js";
|
|
13
13
|
import { safeDecodeURIComponent } from "./url-params.js";
|
|
14
14
|
|
|
15
15
|
export interface TrieMatchResult {
|
|
@@ -219,8 +219,8 @@ function walkTrie(
|
|
|
219
219
|
|
|
220
220
|
if (node.xp) {
|
|
221
221
|
// node.xp keys are pre-sorted longest-suffix-first at build time
|
|
222
|
-
// (route-trie.ts sortSuffixParams), so the first match is the most
|
|
223
|
-
// suffix: `/app.min.js` matches `:file.min.js` before `:file.js`.
|
|
222
|
+
// (route-trie-builder.ts sortSuffixParams), so the first match is the most
|
|
223
|
+
// specific suffix: `/app.min.js` matches `:file.min.js` before `:file.js`.
|
|
224
224
|
for (const suffix in node.xp) {
|
|
225
225
|
if (segment.endsWith(suffix) && segment.length > suffix.length) {
|
|
226
226
|
const paramValue = segment.slice(0, -suffix.length);
|
package/src/router.ts
CHANGED
|
@@ -3,6 +3,11 @@ import { createCacheScope } from "./cache/cache-scope.js";
|
|
|
3
3
|
import { resolveCacheProfiles } from "./cache/profile-registry.js";
|
|
4
4
|
import { isCachedFunction } from "./cache/taint.js";
|
|
5
5
|
import { assertClientComponent } from "./component-utils.js";
|
|
6
|
+
import {
|
|
7
|
+
isClientUrlPatterns,
|
|
8
|
+
isClientUrlReference,
|
|
9
|
+
} from "./client-urls/server-projection.js";
|
|
10
|
+
import type { ClientUrlPatterns } from "./client-urls/types.js";
|
|
6
11
|
import { DefaultDocument } from "./components/DefaultDocument.js";
|
|
7
12
|
import type { SerializedManifest } from "./debug.js";
|
|
8
13
|
import {
|
|
@@ -100,6 +105,7 @@ import type {
|
|
|
100
105
|
Rango,
|
|
101
106
|
RangoInternal,
|
|
102
107
|
RouterRequestInput,
|
|
108
|
+
UrlPatternMount,
|
|
103
109
|
} from "./router/router-interfaces.js";
|
|
104
110
|
|
|
105
111
|
// Extracted closure functions
|
|
@@ -330,6 +336,7 @@ export function createRouter<TEnv = any>(
|
|
|
330
336
|
|
|
331
337
|
// Store reference to urlpatterns for runtime manifest generation
|
|
332
338
|
let storedUrlPatterns: UrlPatterns<TEnv, any> | null = null;
|
|
339
|
+
const urlpatternMounts: UrlPatternMount<TEnv>[] = [];
|
|
333
340
|
|
|
334
341
|
// Global middleware storage
|
|
335
342
|
const globalMiddleware: MiddlewareEntry<TEnv>[] = [];
|
|
@@ -572,6 +579,13 @@ export function createRouter<TEnv = any>(
|
|
|
572
579
|
}
|
|
573
580
|
}
|
|
574
581
|
: undefined,
|
|
582
|
+
// notFound() resolution deps: the streamed envelope carries the
|
|
583
|
+
// server-rendered not-found UI, mirroring the consumption lane's
|
|
584
|
+
// boundary resolution (segment-resolution/helpers.ts).
|
|
585
|
+
{
|
|
586
|
+
findNearestNotFoundBoundary,
|
|
587
|
+
notFoundComponent: notFound,
|
|
588
|
+
},
|
|
575
589
|
);
|
|
576
590
|
|
|
577
591
|
// Emit loader.end after the promise settles (fire-and-forget)
|
|
@@ -756,7 +770,30 @@ export function createRouter<TEnv = any>(
|
|
|
756
770
|
id: routerId,
|
|
757
771
|
basename,
|
|
758
772
|
|
|
759
|
-
routes(
|
|
773
|
+
routes(
|
|
774
|
+
patternsOrBuilder:
|
|
775
|
+
| UrlPatterns<TEnv>
|
|
776
|
+
| UrlBuilder<TEnv>
|
|
777
|
+
| ClientUrlPatterns,
|
|
778
|
+
): any {
|
|
779
|
+
// Pure-client mounting shorthand: a clientUrls() definition passed
|
|
780
|
+
// directly NORMALIZES to a root include in the canonical urls() tree —
|
|
781
|
+
// exactly `include("/", definition, { name: "" })`, keeping local route
|
|
782
|
+
// names bare and local patterns app-absolute. This is sugar over the
|
|
783
|
+
// ONE composition model, not a second registration path: it rides the
|
|
784
|
+
// same lazy include materialization, so no ordering, one-definition, or
|
|
785
|
+
// deferral rules exist. Prefixing, wrapping RSC layouts, and middleware
|
|
786
|
+
// scope still come from mounting through include() in urls() yourself.
|
|
787
|
+
if (
|
|
788
|
+
isClientUrlPatterns(patternsOrBuilder) ||
|
|
789
|
+
isClientUrlReference(patternsOrBuilder)
|
|
790
|
+
) {
|
|
791
|
+
const clientSource = patternsOrBuilder as ClientUrlPatterns;
|
|
792
|
+
patternsOrBuilder = urls(({ include }) => [
|
|
793
|
+
include("/", clientSource, { name: "" }),
|
|
794
|
+
]) as UrlPatterns<TEnv>;
|
|
795
|
+
}
|
|
796
|
+
|
|
760
797
|
// Wrap builder functions in urls() automatically
|
|
761
798
|
const urlPatterns: UrlPatterns<TEnv> =
|
|
762
799
|
typeof patternsOrBuilder === "function"
|
|
@@ -766,6 +803,10 @@ export function createRouter<TEnv = any>(
|
|
|
766
803
|
// Store reference for runtime manifest generation
|
|
767
804
|
storedUrlPatterns = urlPatterns;
|
|
768
805
|
const currentMountIndex = mountIndex++;
|
|
806
|
+
urlpatternMounts.push({
|
|
807
|
+
patterns: urlPatterns,
|
|
808
|
+
mountIndex: currentMountIndex,
|
|
809
|
+
});
|
|
769
810
|
|
|
770
811
|
// Create manifest and patterns maps for route registration
|
|
771
812
|
const manifest = new Map<string, EntryData>();
|
|
@@ -1095,6 +1136,10 @@ export function createRouter<TEnv = any>(
|
|
|
1095
1136
|
return storedUrlPatterns ?? undefined;
|
|
1096
1137
|
},
|
|
1097
1138
|
|
|
1139
|
+
get __urlpatternMounts() {
|
|
1140
|
+
return urlpatternMounts;
|
|
1141
|
+
},
|
|
1142
|
+
|
|
1098
1143
|
// Expose source file for per-router type generation
|
|
1099
1144
|
__sourceFile,
|
|
1100
1145
|
|
package/src/rsc/full-payload.ts
CHANGED
|
@@ -44,11 +44,17 @@ export function buildFullPayload(
|
|
|
44
44
|
resolvedIds: m.resolvedIds,
|
|
45
45
|
params: m.params,
|
|
46
46
|
isPartial: false,
|
|
47
|
+
interceptTargets: m.interceptTargets,
|
|
47
48
|
rootLayout: ctx.router.rootLayout,
|
|
48
49
|
// Full render: resolve deferred handle values server-side so SSR markup and
|
|
49
50
|
// the first sync useHandle read see resolved values. Partial payloads
|
|
50
51
|
// (rsc-rendering.ts) keep streaming (handleStore.stream()).
|
|
51
52
|
handles: resolvedHandleStream(handleStore),
|
|
53
|
+
// Post-handler-barrier pushes (streaming loader ctx.handle() writes)
|
|
54
|
+
// stream here; the client applies them after hydration. Instantly
|
|
55
|
+
// complete when the loader lane is idle — including PPR shell capture,
|
|
56
|
+
// where masked loaders never run.
|
|
57
|
+
handlesLate: handleStore.streamLate(),
|
|
52
58
|
version: ctx.version,
|
|
53
59
|
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
54
60
|
prefetchCacheSize: ctx.router.prefetchCacheSize,
|
package/src/rsc/handler.ts
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* and progressive enhancement (no-JS form submissions).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { createElement } from "react";
|
|
11
10
|
import { isRouteNotFoundError } from "../errors.js";
|
|
12
11
|
import { matchMiddleware, executeMiddleware } from "../router/middleware.js";
|
|
13
12
|
import {
|
|
@@ -50,7 +49,10 @@ import { generateNonce, nonce as nonceToken } from "./nonce.js";
|
|
|
50
49
|
import { VERSION } from "@rangojs/router:version";
|
|
51
50
|
import type { ErrorPhase } from "../types.js";
|
|
52
51
|
import type { RouterRequestInput } from "../router/router-interfaces.js";
|
|
53
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
invokeOnError,
|
|
54
|
+
resolveDefaultNotFound,
|
|
55
|
+
} from "../router/error-handling.js";
|
|
54
56
|
import {
|
|
55
57
|
createReverseFunction,
|
|
56
58
|
stripInternalParams,
|
|
@@ -1309,11 +1311,12 @@ export function createRSCHandler<
|
|
|
1309
1311
|
handledByBoundary: true,
|
|
1310
1312
|
});
|
|
1311
1313
|
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1314
|
+
// No boundary to consult: an unmatched route has no entry chain, so
|
|
1315
|
+
// this always lands on the router option or the shared default.
|
|
1316
|
+
const notFoundComponent = resolveDefaultNotFound(
|
|
1317
|
+
router.notFound,
|
|
1318
|
+
url.pathname,
|
|
1319
|
+
);
|
|
1317
1320
|
|
|
1318
1321
|
const notFoundSegment = {
|
|
1319
1322
|
id: "notFound",
|
package/src/rsc/loader-fetch.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { getLoaderLazy } from "../server/loader-registry.js";
|
|
15
|
-
import {
|
|
15
|
+
import { isDataNotFoundError } from "../errors.js";
|
|
16
16
|
import { executeLoaderMiddleware } from "../router/middleware.js";
|
|
17
17
|
import { getRequestContext } from "../server/request-context.js";
|
|
18
18
|
import { observePhase, PHASES } from "../router/instrument.js";
|
|
@@ -289,7 +289,7 @@ export async function handleLoaderFetch<TEnv>(
|
|
|
289
289
|
// so it does not match the branch above. Map it to a 404 before the generic
|
|
290
290
|
// 500 coercion so a no-middleware fetchable loader's notFound() is honored
|
|
291
291
|
// (the with-middleware path resolves it through the notFoundBoundary).
|
|
292
|
-
if (error
|
|
292
|
+
if (isDataNotFoundError(error)) {
|
|
293
293
|
return finalizeResponse(
|
|
294
294
|
createResponseWithMergedHeaders(null, { status: 404 }),
|
|
295
295
|
);
|
package/src/rsc/manifest-init.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Router Manifest Initialization
|
|
3
3
|
*
|
|
4
|
-
* Builds a fresh route trie from
|
|
5
|
-
* where the manifest exists but the trie needs rebuilding.
|
|
4
|
+
* Builds a fresh route trie from every registered URL-pattern mount for
|
|
5
|
+
* dev/HMR scenarios where the manifest exists but the trie needs rebuilding.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
setRouterTrie,
|
|
14
14
|
setRouterPrecomputedEntries,
|
|
15
15
|
} from "../route-map-builder.js";
|
|
16
|
+
import { mergeFullManifests } from "../build/merge-full-manifests.js";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Build a fresh route trie from router
|
|
19
|
-
* per-router cache. Also sets the per-router manifest and merges into
|
|
20
|
-
*
|
|
19
|
+
* Build a fresh route trie from the router's URL-pattern mounts and store it in
|
|
20
|
+
* the per-router cache. Also sets the per-router manifest and merges into the
|
|
21
|
+
* global manifest for reverse()/href().
|
|
21
22
|
*
|
|
22
23
|
* Called when manifest data may exist but the per-router trie is missing,
|
|
23
24
|
* which happens in dev mode after HMR: the virtual module sets the manifest
|
|
@@ -30,10 +31,28 @@ export async function buildRouterTrieFromUrlpatterns(
|
|
|
30
31
|
): Promise<void> {
|
|
31
32
|
const { generateManifestFull } =
|
|
32
33
|
await import("../build/generate-manifest.js");
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
const mounts =
|
|
35
|
+
router.__urlpatternMounts ??
|
|
36
|
+
(router.urlpatterns
|
|
37
|
+
? [{ patterns: router.urlpatterns, mountIndex: undefined }]
|
|
38
|
+
: []);
|
|
39
|
+
// mount.mountIndex mirrors the live router's per-mount counter partitioning
|
|
40
|
+
// (context.ts getShortCode `M{i}` prefixes), so entries generated here carry
|
|
41
|
+
// the same shortCodes as runtime registration. Build discovery
|
|
42
|
+
// (discover-routers.ts / runtime-discovery.ts) numbers mounts GLOBALLY across
|
|
43
|
+
// routers instead — that divergence is invisible: shortCodes never leave
|
|
44
|
+
// their realm, and route names (the trie keys) carry no mount index (unnamed
|
|
45
|
+
// routes are `$path_*`, see path-helper.ts).
|
|
46
|
+
const mountManifests = [];
|
|
47
|
+
for (const mount of mounts) {
|
|
48
|
+
mountManifests.push(
|
|
49
|
+
await generateManifestFull(mount.patterns, mount.mountIndex, {
|
|
50
|
+
routerId: router.id,
|
|
51
|
+
...(router.basename ? { urlPrefix: router.basename } : {}),
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
const generated = mergeFullManifests(mountManifests);
|
|
37
56
|
// Build the trie through the SAME shared helper the production discovery uses
|
|
38
57
|
// (discover-routers.ts), so the dev runtime-rebuilt trie and the prod
|
|
39
58
|
// serialized trie cannot drift. buildPerRouterTrie returns null when there
|
package/src/rsc/rsc-rendering.ts
CHANGED
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
SHELL_STATUS_HEADER,
|
|
58
58
|
resolvePprConfig,
|
|
59
59
|
buildShellKey,
|
|
60
|
+
shellSearchSeed,
|
|
60
61
|
isValidShellHit,
|
|
61
62
|
hasIntactShellPayload,
|
|
62
63
|
base64ToBytes,
|
|
@@ -153,12 +154,14 @@ function buildNavigationShellKey(
|
|
|
153
154
|
function createShellCaptureDescriptor(
|
|
154
155
|
ctx: HandlerContext<any>,
|
|
155
156
|
key: string,
|
|
157
|
+
searchSeed: string,
|
|
156
158
|
pprConfig: ResolvedPprConfig,
|
|
157
159
|
store: SegmentCacheStore<any>,
|
|
158
160
|
navigationOnly?: true,
|
|
159
161
|
): ShellCaptureDescriptor {
|
|
160
162
|
return {
|
|
161
163
|
key,
|
|
164
|
+
searchSeed,
|
|
162
165
|
buildVersion: ctx.version,
|
|
163
166
|
ttl: pprConfig.ttl,
|
|
164
167
|
swr: pprConfig.swr,
|
|
@@ -512,7 +515,13 @@ function* shellServePlan<TEnv>(
|
|
|
512
515
|
) {
|
|
513
516
|
return { kind: "pass" };
|
|
514
517
|
}
|
|
515
|
-
const descriptor = createShellCaptureDescriptor(
|
|
518
|
+
const descriptor = createShellCaptureDescriptor(
|
|
519
|
+
ctx,
|
|
520
|
+
key,
|
|
521
|
+
shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
522
|
+
pprConfig,
|
|
523
|
+
store,
|
|
524
|
+
);
|
|
516
525
|
|
|
517
526
|
if (
|
|
518
527
|
cached &&
|
|
@@ -699,6 +708,7 @@ function* preparePayloadPlan<TEnv>(
|
|
|
699
708
|
params: result.params,
|
|
700
709
|
isPartial: true,
|
|
701
710
|
slots: result.slots,
|
|
711
|
+
interceptTargets: result.interceptTargets,
|
|
702
712
|
handles: handleStore.stream(),
|
|
703
713
|
version: ctx.version,
|
|
704
714
|
prefetchCacheTTL: ctx.router.prefetchCacheTTL,
|
|
@@ -993,6 +1003,7 @@ function scheduleNavigationShellCapture<TEnv>(
|
|
|
993
1003
|
ctx,
|
|
994
1004
|
partialCaptureKey ??
|
|
995
1005
|
buildNavigationShellKey(url, reqCtx._searchParamsFilter),
|
|
1006
|
+
shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
996
1007
|
pprConfig,
|
|
997
1008
|
store,
|
|
998
1009
|
true,
|
|
@@ -1510,6 +1521,9 @@ function serveShellHit(
|
|
|
1510
1521
|
ssrModule.resumeShellHTML!(rscStream, {
|
|
1511
1522
|
postponed: entry.postponed,
|
|
1512
1523
|
nonce: undefined,
|
|
1524
|
+
// The shell key's own search — identical to the capture seed for
|
|
1525
|
+
// this key, so the resume tree matches the captured tree.
|
|
1526
|
+
search: shellSearchSeed(url, reqCtx._searchParamsFilter),
|
|
1513
1527
|
}),
|
|
1514
1528
|
);
|
|
1515
1529
|
};
|
package/src/rsc/shell-capture.ts
CHANGED
|
@@ -907,6 +907,15 @@ export function gateFlightForCapture(
|
|
|
907
907
|
*/
|
|
908
908
|
export interface ShellCaptureDescriptor {
|
|
909
909
|
key: string;
|
|
910
|
+
/**
|
|
911
|
+
* The key's search portion (shellSearchSeed: `?`-prefixed sorted search,
|
|
912
|
+
* cache.searchParams filter applied, or ""). Seeds the capture render's SSR
|
|
913
|
+
* store so static-part `useSearchParams` reads bake markup consistent with
|
|
914
|
+
* the shell's own key; the resume pass derives the identical string from
|
|
915
|
+
* the HIT request. Computed next to the key so the two cannot drift.
|
|
916
|
+
* Omitted (build-time bare-pathname captures) = search-less, same as "".
|
|
917
|
+
*/
|
|
918
|
+
searchSeed?: string;
|
|
910
919
|
/**
|
|
911
920
|
* The RSC handler's build version (HandlerContext.version), stamped into the
|
|
912
921
|
* stored entry as ShellCacheEntry.buildVersion — the serve-side
|
|
@@ -1753,6 +1762,9 @@ async function captureAndStoreShell(
|
|
|
1753
1762
|
captureShellHTML(gate.stream, {
|
|
1754
1763
|
quiesce,
|
|
1755
1764
|
maxWaitMs: capture.captureTimeout ?? SHELL_CAPTURE_MAX_WAIT_MS,
|
|
1765
|
+
// The shell key's own search seeds the capture render's store —
|
|
1766
|
+
// static-part search reads bake what the key names.
|
|
1767
|
+
search: capture.searchSeed,
|
|
1756
1768
|
}),
|
|
1757
1769
|
);
|
|
1758
1770
|
} catch (error) {
|
package/src/rsc/shell-serve.ts
CHANGED
|
@@ -129,9 +129,22 @@ export function resolvePprConfig(
|
|
|
129
129
|
* and lookup makes every shell request a permanent miss.
|
|
130
130
|
*/
|
|
131
131
|
export function buildShellKey(url: URL, filter?: SearchParamsFilter): string {
|
|
132
|
+
return `${url.host}${url.pathname}${shellSearchSeed(url, filter)}:shell`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The shell key's search portion (`?`-prefixed sorted search with the
|
|
137
|
+
* cache.searchParams filter applied, or "") — ALSO the string the capture and
|
|
138
|
+
* resume SSR renders seed their store location with (SSRRenderOptions.search
|
|
139
|
+
* / ShellCaptureOptions.search / ShellResumeOptions.search). Search is part
|
|
140
|
+
* of shell identity, so static-part `useSearchParams` reads render exactly
|
|
141
|
+
* what the key names; deriving seed and key from this one helper is what
|
|
142
|
+
* keeps capture, resume, and lookup byte-agreed (drift = replay mismatch or
|
|
143
|
+
* permanent MISS).
|
|
144
|
+
*/
|
|
145
|
+
export function shellSearchSeed(url: URL, filter?: SearchParamsFilter): string {
|
|
132
146
|
const sorted = sortedSearchString(url.searchParams, filter);
|
|
133
|
-
|
|
134
|
-
return `${url.host}${url.pathname}${searchSuffix}:shell`;
|
|
147
|
+
return sorted ? `?${sorted}` : "";
|
|
135
148
|
}
|
|
136
149
|
|
|
137
150
|
/**
|
package/src/rsc/ssr-setup.ts
CHANGED
|
@@ -111,7 +111,16 @@ export function createSsrHtmlStage<TEnv>(
|
|
|
111
111
|
);
|
|
112
112
|
return {
|
|
113
113
|
render: () =>
|
|
114
|
-
|
|
114
|
+
// search: the LIVE request's query string, threaded out-of-band into
|
|
115
|
+
// the SSR pass so useSearchParams sees real values during document
|
|
116
|
+
// renders. Deliberately not payload metadata: cached/prerendered
|
|
117
|
+
// payloads replay captured metadata, and search is not route
|
|
118
|
+
// identity — it must always come from the request being served.
|
|
119
|
+
ssrModule.renderHTML(flight.stream, {
|
|
120
|
+
...options.render,
|
|
121
|
+
streamMode,
|
|
122
|
+
search: options.url.search,
|
|
123
|
+
}),
|
|
115
124
|
...(options.init && { init: options.init }),
|
|
116
125
|
};
|
|
117
126
|
};
|
package/src/rsc/types.ts
CHANGED
|
@@ -35,10 +35,27 @@ export interface RscPayload {
|
|
|
35
35
|
/** Merged route params from the matched route */
|
|
36
36
|
params?: Record<string, string>;
|
|
37
37
|
slots?: Record<string, SlotState>;
|
|
38
|
+
/**
|
|
39
|
+
* Intercept TARGET route names reachable from this location as a
|
|
40
|
+
* navigation origin (see MatchResult.interceptTargets). The browser-local
|
|
41
|
+
* clientUrls matcher declines optimistic presentation for these.
|
|
42
|
+
*/
|
|
43
|
+
interceptTargets?: string[];
|
|
38
44
|
/** Root layout component for browser-side re-renders (client component reference) */
|
|
39
45
|
rootLayout?: React.ComponentType<{ children: React.ReactNode }>;
|
|
40
46
|
/** Handle data accumulated across route segments (async generator that yields on each push) */
|
|
41
47
|
handles?: AsyncGenerator<HandleData, void, unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Document-lane late handle channel: full-state updates for pushes landing
|
|
50
|
+
* AFTER the handler barrier (streaming loader bodies writing handles
|
|
51
|
+
* mid-body). `handles` above is drained to completion in blocking
|
|
52
|
+
* positions (SSR seed, pre-hydration), so late pushes need this separate
|
|
53
|
+
* generator, consumed non-blocking post-hydration (rsc-router.tsx).
|
|
54
|
+
* Empty/instantly-complete when no auxiliary-lane work is pending at the
|
|
55
|
+
* handler barrier. Full payloads only — partial payloads' `handles`
|
|
56
|
+
* generator streams to full settle already.
|
|
57
|
+
*/
|
|
58
|
+
handlesLate?: AsyncGenerator<HandleData, void, unknown>;
|
|
42
59
|
/** RSC version string for cache invalidation */
|
|
43
60
|
version?: string;
|
|
44
61
|
/** Cloudflare dev worker generation used for stale-document convergence. */
|
|
@@ -158,6 +175,18 @@ export interface SSRRenderOptions {
|
|
|
158
175
|
* - `"allReady"` — await `stream.allReady` before returning.
|
|
159
176
|
*/
|
|
160
177
|
streamMode?: import("../router/router-options.js").SSRStreamMode;
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* The live request's query string (`url.search`, `?`-prefixed or empty).
|
|
181
|
+
* Seeds the SSR navigation store so `useSearchParams` (and
|
|
182
|
+
* `useNavigation().location`) carry real values during document renders.
|
|
183
|
+
* Out-of-band by design — never payload metadata, which cached/prerendered
|
|
184
|
+
* payloads replay; search is not route identity. The build-time prerender
|
|
185
|
+
* pass passes none — build shells capture bare pathnames and serve
|
|
186
|
+
* search-less requests only (runtime ppr captures seed the shell key's
|
|
187
|
+
* search; see ShellCaptureOptions.search in the SSR entry).
|
|
188
|
+
*/
|
|
189
|
+
search?: string;
|
|
161
190
|
}
|
|
162
191
|
|
|
163
192
|
/**
|
|
@@ -179,7 +208,7 @@ export interface SSRModule {
|
|
|
179
208
|
*/
|
|
180
209
|
captureShellHTML?: (
|
|
181
210
|
rscStream: ReadableStream<Uint8Array>,
|
|
182
|
-
options: { quiesce: Promise<void>; maxWaitMs?: number },
|
|
211
|
+
options: { quiesce: Promise<void>; maxWaitMs?: number; search?: string },
|
|
183
212
|
) => Promise<{ prelude: Uint8Array; postponed: string | null } | null>;
|
|
184
213
|
|
|
185
214
|
/**
|
|
@@ -192,7 +221,7 @@ export interface SSRModule {
|
|
|
192
221
|
*/
|
|
193
222
|
resumeShellHTML?: (
|
|
194
223
|
rscStream: ReadableStream<Uint8Array>,
|
|
195
|
-
options: { postponed: string | null; nonce?: string },
|
|
224
|
+
options: { postponed: string | null; nonce?: string; search?: string },
|
|
196
225
|
) => Promise<ReadableStream<Uint8Array>>;
|
|
197
226
|
}
|
|
198
227
|
|