@rangojs/router 0.0.0-experimental.20 → 0.0.0-experimental.20dbba0c
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -0
- package/README.md +172 -50
- package/dist/bin/rango.js +138 -50
- package/dist/vite/index.js +1160 -508
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +17 -16
- package/skills/breadcrumbs/SKILL.md +252 -0
- package/skills/cache-guide/SKILL.md +32 -0
- package/skills/caching/SKILL.md +49 -8
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/handler-use/SKILL.md +362 -0
- package/skills/hooks/SKILL.md +61 -51
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +20 -0
- package/skills/layout/SKILL.md +22 -0
- package/skills/links/SKILL.md +91 -17
- package/skills/loader/SKILL.md +107 -24
- package/skills/middleware/SKILL.md +34 -3
- package/skills/migrate-nextjs/SKILL.md +560 -0
- package/skills/migrate-react-router/SKILL.md +765 -0
- package/skills/parallel/SKILL.md +185 -0
- package/skills/prerender/SKILL.md +112 -70
- package/skills/rango/SKILL.md +24 -23
- package/skills/response-routes/SKILL.md +8 -0
- package/skills/route/SKILL.md +58 -4
- package/skills/router-setup/SKILL.md +95 -5
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/typesafety/SKILL.md +38 -24
- package/src/__internal.ts +92 -0
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +175 -17
- package/src/browser/navigation-client.ts +177 -44
- package/src/browser/navigation-store.ts +68 -9
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +113 -17
- package/src/browser/prefetch/cache.ts +275 -28
- package/src/browser/prefetch/fetch.ts +191 -46
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +98 -14
- package/src/browser/react/NavigationProvider.tsx +89 -14
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/use-handle.ts +9 -58
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +11 -1
- package/src/browser/react/use-router.ts +29 -9
- package/src/browser/rsc-router.tsx +177 -66
- package/src/browser/scroll-restoration.ts +41 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/server-action-bridge.ts +8 -6
- package/src/browser/types.ts +73 -5
- package/src/build/generate-manifest.ts +6 -6
- package/src/build/generate-route-types.ts +3 -0
- package/src/build/route-trie.ts +67 -25
- package/src/build/route-types/include-resolution.ts +8 -1
- package/src/build/route-types/router-processing.ts +223 -74
- package/src/build/route-types/scan-filter.ts +8 -1
- package/src/cache/cache-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +48 -7
- package/src/cache/cf/cf-cache-store.ts +455 -15
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +2 -1
- package/src/client.tsx +85 -276
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/handle.ts +40 -0
- package/src/handles/breadcrumbs.ts +66 -0
- package/src/handles/index.ts +1 -0
- package/src/host/index.ts +0 -3
- package/src/index.rsc.ts +9 -36
- package/src/index.ts +79 -70
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +57 -15
- package/src/prerender.ts +138 -77
- package/src/response-utils.ts +28 -0
- package/src/reverse.ts +27 -2
- package/src/route-definition/dsl-helpers.ts +240 -40
- package/src/route-definition/helpers-types.ts +67 -19
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +18 -0
- package/src/router/content-negotiation.ts +100 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +129 -26
- package/src/router/intercept-resolution.ts +11 -4
- package/src/router/lazy-includes.ts +10 -7
- package/src/router/loader-resolution.ts +160 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -193
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +94 -17
- package/src/router/match-middleware/cache-store.ts +53 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +103 -18
- package/src/router/metrics.ts +238 -13
- package/src/router/middleware-types.ts +48 -27
- package/src/router/middleware.ts +201 -86
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +77 -11
- package/src/router/prerender-match.ts +114 -10
- package/src/router/preview-match.ts +30 -102
- package/src/router/request-classification.ts +310 -0
- package/src/router/revalidation.ts +27 -7
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +50 -5
- package/src/router/router-options.ts +50 -19
- package/src/router/segment-resolution/fresh.ts +215 -19
- package/src/router/segment-resolution/helpers.ts +30 -25
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +454 -301
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/trie-matching.ts +30 -6
- package/src/router/types.ts +1 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +89 -17
- package/src/rsc/handler.ts +563 -364
- package/src/rsc/helpers.ts +69 -41
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/progressive-enhancement.ts +37 -10
- package/src/rsc/response-route-handler.ts +14 -1
- package/src/rsc/rsc-rendering.ts +47 -44
- package/src/rsc/server-action.ts +24 -10
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +11 -1
- package/src/search-params.ts +16 -13
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +109 -23
- package/src/server/context.ts +174 -19
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +218 -65
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +4 -0
- package/src/static-handler.ts +18 -6
- package/src/theme/index.ts +4 -13
- package/src/types/cache-types.ts +4 -4
- package/src/types/handler-context.ts +140 -72
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-config.ts +17 -8
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +2 -5
- package/src/urls/include-helper.ts +24 -14
- package/src/urls/path-helper-types.ts +39 -6
- package/src/urls/path-helper.ts +48 -13
- package/src/urls/pattern-types.ts +12 -0
- package/src/urls/response-types.ts +18 -16
- package/src/use-loader.tsx +77 -5
- package/src/vite/discovery/bundle-postprocess.ts +61 -89
- package/src/vite/discovery/discover-routers.ts +7 -4
- package/src/vite/discovery/prerender-collection.ts +162 -88
- package/src/vite/discovery/state.ts +17 -13
- package/src/vite/index.ts +8 -3
- package/src/vite/plugin-types.ts +51 -79
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/expose-id-utils.ts +12 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +30 -0
- package/src/vite/plugins/expose-internal-ids.ts +257 -40
- package/src/vite/plugins/performance-tracks.ts +88 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +190 -217
- package/src/vite/router-discovery.ts +241 -45
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/package-resolution.ts +34 -1
- package/src/vite/utils/prerender-utils.ts +97 -5
- package/src/vite/utils/shared-utils.ts +3 -2
- package/skills/testing/SKILL.md +0 -226
- package/src/route-definition/route-function.ts +0 -119
package/src/server/context.ts
CHANGED
|
@@ -26,6 +26,7 @@ export interface PerformanceMetric {
|
|
|
26
26
|
label: string; // e.g., "route-matching", "loader:UserLoader"
|
|
27
27
|
duration: number; // milliseconds
|
|
28
28
|
startTime: number; // relative to request start
|
|
29
|
+
depth?: number; // nesting level for hierarchical display (0 = top-level)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -156,10 +157,24 @@ export type InterceptEntry = {
|
|
|
156
157
|
when: InterceptWhenFn[]; // Selector conditions - all must return true to intercept
|
|
157
158
|
};
|
|
158
159
|
|
|
160
|
+
export interface ParallelEntryData
|
|
161
|
+
extends EntryPropCommon, EntryPropDatas, EntryPropSegments {
|
|
162
|
+
type: "parallel";
|
|
163
|
+
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
164
|
+
loading?: ReactNode | false;
|
|
165
|
+
transition?: TransitionConfig;
|
|
166
|
+
/** Set when any parallel slot is a Static definition */
|
|
167
|
+
isStaticPrerender?: true;
|
|
168
|
+
/** Per-slot static handler $$ids for build-time store lookup */
|
|
169
|
+
staticHandlerIds?: Record<string, string>;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export type ParallelEntries = Partial<Record<`@${string}`, ParallelEntryData>>;
|
|
173
|
+
|
|
159
174
|
export type EntryPropSegments = {
|
|
160
175
|
loader: LoaderEntry[];
|
|
161
176
|
layout: EntryData[];
|
|
162
|
-
parallel:
|
|
177
|
+
parallel: ParallelEntries; // slot -> parallel entry (same entry may back multiple slots)
|
|
163
178
|
intercept: InterceptEntry[]; // intercept definitions for soft navigation
|
|
164
179
|
};
|
|
165
180
|
|
|
@@ -176,8 +191,12 @@ export type EntryData =
|
|
|
176
191
|
/** Original PrerenderHandlerDefinition (for build-time getParams access) */
|
|
177
192
|
prerenderDef?: {
|
|
178
193
|
getParams?: (ctx: any) => Promise<any[]> | any[];
|
|
179
|
-
options?: {
|
|
194
|
+
options?: { concurrency?: number };
|
|
180
195
|
};
|
|
196
|
+
/** Set when route is wrapped with Passthrough() — has a separate live handler */
|
|
197
|
+
isPassthrough?: true;
|
|
198
|
+
/** Live handler for runtime fallback (only set on Passthrough routes) */
|
|
199
|
+
liveHandler?: Handler<any, any, any>;
|
|
181
200
|
/** Set when handler is a Static definition (build-time only) */
|
|
182
201
|
isStaticPrerender?: true;
|
|
183
202
|
/** Static handler $$id for build-time store lookup */
|
|
@@ -199,18 +218,7 @@ export type EntryData =
|
|
|
199
218
|
} & EntryPropCommon &
|
|
200
219
|
EntryPropDatas &
|
|
201
220
|
EntryPropSegments)
|
|
202
|
-
|
|
|
203
|
-
type: "parallel";
|
|
204
|
-
handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
|
|
205
|
-
loading?: ReactNode | false;
|
|
206
|
-
transition?: TransitionConfig;
|
|
207
|
-
/** Set when any parallel slot is a Static definition */
|
|
208
|
-
isStaticPrerender?: true;
|
|
209
|
-
/** Per-slot static handler $$ids for build-time store lookup */
|
|
210
|
-
staticHandlerIds?: Record<string, string>;
|
|
211
|
-
} & EntryPropCommon &
|
|
212
|
-
EntryPropDatas &
|
|
213
|
-
EntryPropSegments)
|
|
221
|
+
| ParallelEntryData
|
|
214
222
|
| ({
|
|
215
223
|
type: "cache";
|
|
216
224
|
/** Cache entries create cache boundaries and render like layouts (with Outlet) */
|
|
@@ -269,6 +277,25 @@ interface HelperContext {
|
|
|
269
277
|
string,
|
|
270
278
|
import("../cache/profile-registry.js").CacheProfile
|
|
271
279
|
>;
|
|
280
|
+
/** True when resolving handlers inside a cache() DSL boundary.
|
|
281
|
+
* Read by ctx.get() to guard non-cacheable variable reads. */
|
|
282
|
+
insideCacheScope?: boolean;
|
|
283
|
+
/**
|
|
284
|
+
* Include scope string applied to direct-descendant shortCodes.
|
|
285
|
+
*
|
|
286
|
+
* Each `include(...)` call allocates a sibling-positional token like `I0`,
|
|
287
|
+
* `I1` from its parent's include counter and stores the composed scope
|
|
288
|
+
* (`${parentScope}I${idx}`) in its lazyContext. When the include's handler
|
|
289
|
+
* evaluates lazily, the store's `includeScope` is set from that context so
|
|
290
|
+
* every direct-descendant shortCode is generated as
|
|
291
|
+
* `${parent.shortCode}${includeScope}${prefix}${index}` — preventing
|
|
292
|
+
* collisions with siblings declared outside the include.
|
|
293
|
+
*
|
|
294
|
+
* The scope is NOT propagated through `store.run(...)`, so layouts /
|
|
295
|
+
* parallels / caches inside the include absorb the scope into their own
|
|
296
|
+
* shortCodes and their children start fresh.
|
|
297
|
+
*/
|
|
298
|
+
includeScope?: string;
|
|
272
299
|
}
|
|
273
300
|
// Use a global symbol key so the AsyncLocalStorage instance survives HMR
|
|
274
301
|
// module re-evaluation. Without this, Vite's RSC module runner may create
|
|
@@ -371,6 +398,8 @@ export const getContext = (): {
|
|
|
371
398
|
const mountPrefix =
|
|
372
399
|
store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
|
|
373
400
|
|
|
401
|
+
const includeScope = store.includeScope ?? "";
|
|
402
|
+
|
|
374
403
|
if (!parent) {
|
|
375
404
|
// Root entry: prefix with mount index and use mount-scoped counter
|
|
376
405
|
const counterKey = mountPrefix
|
|
@@ -381,12 +410,16 @@ export const getContext = (): {
|
|
|
381
410
|
store.counters[counterKey] = index + 1;
|
|
382
411
|
return `${mountPrefix}${prefix}${index}`;
|
|
383
412
|
} else {
|
|
384
|
-
// Child entry: use parent-scoped counter
|
|
385
|
-
|
|
413
|
+
// Child entry: use parent-scoped counter with includeScope appended.
|
|
414
|
+
// When we're evaluating a lazy include's direct children, includeScope
|
|
415
|
+
// is a per-include token like "I0" / "I1I0" that partitions the
|
|
416
|
+
// parent's counter namespace so routes inside one include cannot
|
|
417
|
+
// collide with siblings declared outside it.
|
|
418
|
+
const counterKey = `${parent.shortCode}${includeScope}_${type}`;
|
|
386
419
|
store.counters[counterKey] ??= 0;
|
|
387
420
|
const index = store.counters[counterKey];
|
|
388
421
|
store.counters[counterKey] = index + 1;
|
|
389
|
-
return `${parent.shortCode}${prefix}${index}`;
|
|
422
|
+
return `${parent.shortCode}${includeScope}${prefix}${index}`;
|
|
390
423
|
}
|
|
391
424
|
},
|
|
392
425
|
runWithStore: <T>(
|
|
@@ -413,6 +446,7 @@ export const getContext = (): {
|
|
|
413
446
|
rootScoped: store.rootScoped,
|
|
414
447
|
trackedIncludes: store.trackedIncludes,
|
|
415
448
|
cacheProfiles: store.cacheProfiles,
|
|
449
|
+
includeScope: store.includeScope,
|
|
416
450
|
},
|
|
417
451
|
callback,
|
|
418
452
|
);
|
|
@@ -552,6 +586,80 @@ export function getRootScoped(): boolean {
|
|
|
552
586
|
// Export HelperContext type for use in other modules
|
|
553
587
|
export type { HelperContext };
|
|
554
588
|
|
|
589
|
+
/**
|
|
590
|
+
* Return an isolated copy of a lazy include's captured parent entry.
|
|
591
|
+
*
|
|
592
|
+
* DSL helpers (loader(), middleware(), etc.) mutate ctx.parent in place.
|
|
593
|
+
* Multiple include() scopes capture the *same* syntheticMapRoot as their
|
|
594
|
+
* parent, so without isolation one include's loaders/middleware leak into
|
|
595
|
+
* every other route that shares that root.
|
|
596
|
+
*
|
|
597
|
+
* The clone is shallow: only the mutable arrays are copied so each
|
|
598
|
+
* include pushes to its own list. The rest of the entry (id, shortCode,
|
|
599
|
+
* parent pointer, handler) stays shared, which is correct and cheap.
|
|
600
|
+
*/
|
|
601
|
+
export function getIsolatedLazyParent(
|
|
602
|
+
captured: EntryData | null | undefined,
|
|
603
|
+
): EntryData | null {
|
|
604
|
+
if (!captured) return null;
|
|
605
|
+
return {
|
|
606
|
+
...captured,
|
|
607
|
+
loader: [...captured.loader],
|
|
608
|
+
middleware: [...captured.middleware],
|
|
609
|
+
revalidate: [...captured.revalidate],
|
|
610
|
+
errorBoundary: [...captured.errorBoundary],
|
|
611
|
+
notFoundBoundary: [...captured.notFoundBoundary],
|
|
612
|
+
layout: [...captured.layout],
|
|
613
|
+
parallel: { ...captured.parallel },
|
|
614
|
+
intercept: [...captured.intercept],
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function getParallelEntries(
|
|
619
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
620
|
+
): ParallelEntryData[] {
|
|
621
|
+
if (!parallels) return [];
|
|
622
|
+
if (Array.isArray(parallels)) {
|
|
623
|
+
return parallels.filter(
|
|
624
|
+
(entry): entry is ParallelEntryData => entry.type === "parallel",
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
return Object.values(parallels).filter(
|
|
628
|
+
(entry): entry is ParallelEntryData => !!entry,
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function getParallelSlotEntries(
|
|
633
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
634
|
+
): Array<{ slot: `@${string}`; entry: ParallelEntryData }> {
|
|
635
|
+
if (!parallels) return [];
|
|
636
|
+
|
|
637
|
+
if (Array.isArray(parallels)) {
|
|
638
|
+
return getParallelEntries(parallels).flatMap((entry) =>
|
|
639
|
+
(Object.keys(entry.handler) as `@${string}`[]).map((slot) => ({
|
|
640
|
+
slot,
|
|
641
|
+
entry,
|
|
642
|
+
})),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return Object.entries(parallels)
|
|
647
|
+
.filter(([, entry]) => !!entry)
|
|
648
|
+
.map(([slot, entry]) => ({
|
|
649
|
+
slot: slot as `@${string}`,
|
|
650
|
+
entry: entry!,
|
|
651
|
+
}));
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function getParallelSlotCount(
|
|
655
|
+
parallels: ParallelEntries | EntryData[] | undefined,
|
|
656
|
+
): number {
|
|
657
|
+
if (!parallels) return 0;
|
|
658
|
+
return Array.isArray(parallels)
|
|
659
|
+
? parallels.filter((entry) => entry?.type === "parallel").length
|
|
660
|
+
: Object.keys(parallels).length;
|
|
661
|
+
}
|
|
662
|
+
|
|
555
663
|
// ============================================================================
|
|
556
664
|
// Performance Metrics Helpers
|
|
557
665
|
// ============================================================================
|
|
@@ -567,7 +675,7 @@ export type { HelperContext };
|
|
|
567
675
|
* done(); // Records duration
|
|
568
676
|
* ```
|
|
569
677
|
*/
|
|
570
|
-
export function track(label: string): () => void {
|
|
678
|
+
export function track(label: string, depth?: number): () => void {
|
|
571
679
|
const store = RSCRouterContext.getStore();
|
|
572
680
|
|
|
573
681
|
// No-op if context unavailable or metrics not enabled
|
|
@@ -580,6 +688,53 @@ export function track(label: string): () => void {
|
|
|
580
688
|
return () => {
|
|
581
689
|
const duration =
|
|
582
690
|
performance.now() - store.metrics!.requestStart - startTime;
|
|
583
|
-
store.metrics!.metrics.push({
|
|
691
|
+
store.metrics!.metrics.push({
|
|
692
|
+
label,
|
|
693
|
+
duration,
|
|
694
|
+
startTime,
|
|
695
|
+
...(depth != null ? { depth } : {}),
|
|
696
|
+
});
|
|
584
697
|
};
|
|
585
698
|
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Separate ALS for tracking loader execution scope.
|
|
702
|
+
* Uses a dedicated ALS (not RSCRouterContext) to avoid issues with
|
|
703
|
+
* nested RSCRouterContext.run() calls in Vite's module runner.
|
|
704
|
+
*/
|
|
705
|
+
const LOADER_SCOPE_KEY = Symbol.for("rangojs-router:loader-scope");
|
|
706
|
+
const loaderScopeALS: AsyncLocalStorage<{ active: true }> = ((
|
|
707
|
+
globalThis as any
|
|
708
|
+
)[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage<{ active: true }>());
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Check if the current execution is inside a cache() DSL boundary.
|
|
712
|
+
* Returns false inside loader execution — loaders are always fresh
|
|
713
|
+
* (never cached), so non-cacheable reads are safe.
|
|
714
|
+
*/
|
|
715
|
+
export function isInsideCacheScope(): boolean {
|
|
716
|
+
if (RSCRouterContext.getStore()?.insideCacheScope !== true) return false;
|
|
717
|
+
// Loaders are always fresh — even inside a cache() boundary, the loader
|
|
718
|
+
// function re-executes on every request. Skip the guard when running
|
|
719
|
+
// inside a loader.
|
|
720
|
+
if (loaderScopeALS.getStore()?.active) return false;
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Check if the current execution is inside a DSL loader scope
|
|
726
|
+
* (wrapped by runInsideLoaderScope). Used by rendered() barrier
|
|
727
|
+
* to distinguish DSL loaders from handler-invoked loaders.
|
|
728
|
+
*/
|
|
729
|
+
export function isInsideLoaderScope(): boolean {
|
|
730
|
+
return loaderScopeALS.getStore()?.active === true;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Run `fn` inside a loader scope. While active, cache-scope guards
|
|
735
|
+
* are bypassed because loaders are always fresh (never cached) and
|
|
736
|
+
* their side effects (setCookie, header, etc.) are safe.
|
|
737
|
+
*/
|
|
738
|
+
export function runInsideLoaderScope<T>(fn: () => T): T {
|
|
739
|
+
return loaderScopeALS.run({ active: true }, fn);
|
|
740
|
+
}
|
|
@@ -13,6 +13,25 @@
|
|
|
13
13
|
*/
|
|
14
14
|
export type HandleData = Record<string, Record<string, unknown[]>>;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Build a HandleData snapshot from a HandleStore using segment ordering.
|
|
18
|
+
* Reads data directly from the store for each segment in order.
|
|
19
|
+
*/
|
|
20
|
+
export function buildHandleSnapshot(
|
|
21
|
+
handleStore: HandleStore,
|
|
22
|
+
segmentOrder: string[],
|
|
23
|
+
): HandleData {
|
|
24
|
+
const data: HandleData = {};
|
|
25
|
+
for (const segmentId of segmentOrder) {
|
|
26
|
+
const segData = handleStore.getDataForSegment(segmentId);
|
|
27
|
+
for (const handleName in segData) {
|
|
28
|
+
if (!data[handleName]) data[handleName] = {};
|
|
29
|
+
data[handleName][segmentId] = segData[handleName];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
function createLateHandlePushError(
|
|
17
36
|
handleName: string,
|
|
18
37
|
segmentId: string,
|
|
@@ -44,20 +44,21 @@ export function setLoaderImports(
|
|
|
44
44
|
export async function getLoaderLazy(
|
|
45
45
|
id: string,
|
|
46
46
|
): Promise<LoaderRegistryEntry | undefined> {
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return existing;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Check the fetchable loader registry (populated by createLoader)
|
|
47
|
+
// Always check fetchableLoaderRegistry first — it's the source of truth.
|
|
48
|
+
// createLoader() updates it during module re-evaluation (HMR), so checking
|
|
49
|
+
// here ensures we pick up the fresh function after a loader file change.
|
|
54
50
|
const fetchable = getFetchableLoader(id);
|
|
55
51
|
if (fetchable) {
|
|
56
|
-
// Cache in main registry for future requests
|
|
57
52
|
loaderRegistry.set(id, fetchable);
|
|
58
53
|
return fetchable;
|
|
59
54
|
}
|
|
60
55
|
|
|
56
|
+
// Fall back to local cache (populated by previous lazy imports in production)
|
|
57
|
+
const existing = loaderRegistry.get(id);
|
|
58
|
+
if (existing) {
|
|
59
|
+
return existing;
|
|
60
|
+
}
|
|
61
|
+
|
|
61
62
|
// Try to lazy load from the import map (production mode)
|
|
62
63
|
if (lazyLoaderImports && lazyLoaderImports.size > 0) {
|
|
63
64
|
const lazyImport = lazyLoaderImports.get(id);
|