@rangojs/router 0.21.0 → 0.22.1
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 +13 -1
- package/dist/types/browser/event-controller.d.ts +30 -13
- package/dist/types/browser/navigation-store.d.ts +9 -0
- package/dist/types/browser/partial-update.d.ts +2 -1
- package/dist/types/browser/prefetch/default-strategy.d.ts +16 -20
- package/dist/types/browser/react/NavigationProvider.d.ts +14 -1
- package/dist/types/browser/react/context.d.ts +14 -0
- package/dist/types/browser/rsc-router.d.ts +13 -2
- package/dist/types/browser/types.d.ts +10 -0
- package/dist/types/build/generate-manifest.d.ts +2 -0
- package/dist/types/cache/background-task.d.ts +22 -0
- package/dist/types/cache/cache-key-utils.d.ts +50 -5
- package/dist/types/cache/cache-policy.d.ts +17 -0
- package/dist/types/cache/cache-scope.d.ts +1 -1
- package/dist/types/cache/cf/cf-cache-store.d.ts +7 -1
- package/dist/types/cache/document-cache.d.ts +8 -5
- package/dist/types/cache/handle-snapshot.d.ts +23 -0
- package/dist/types/cache/index.d.ts +1 -1
- package/dist/types/cache/invalidation-order.d.ts +14 -0
- package/dist/types/cache/memory-segment-store.d.ts +33 -1
- package/dist/types/cache/shell-snapshot.d.ts +10 -1
- package/dist/types/cache/store-scope.d.ts +22 -0
- package/dist/types/cache/tag-invalidation.d.ts +7 -6
- package/dist/types/cache/types.d.ts +63 -14
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +7 -1
- package/dist/types/errors.d.ts +6 -0
- package/dist/types/handles/deferred-resolution.d.ts +7 -0
- package/dist/types/internal-suspense-audit.d.ts +1 -0
- package/dist/types/loader-store.d.ts +4 -8
- package/dist/types/outlet-provider.d.ts +2 -1
- package/dist/types/prerender/cloudflare.d.ts +49 -0
- package/dist/types/prerender/create-prerender-trigger.d.ts +121 -0
- package/dist/types/prerender/index.d.ts +12 -0
- package/dist/types/prerender/memory-prerender-store.d.ts +29 -0
- package/dist/types/prerender/on-demand.d.ts +272 -0
- package/dist/types/prerender/producer-guard.d.ts +33 -0
- package/dist/types/prerender/shell-manifest-key.d.ts +1 -18
- package/dist/types/prerender/warm-request.d.ts +65 -0
- package/dist/types/prerender/warm.d.ts +65 -0
- package/dist/types/prerender/writable-store.d.ts +170 -0
- package/dist/types/prerender.d.ts +27 -0
- package/dist/types/reverse.d.ts +1 -1
- package/dist/types/route-content-wrapper.d.ts +3 -1
- package/dist/types/router/error-handling.d.ts +2 -0
- package/dist/types/router/handler-context.d.ts +1 -1
- package/dist/types/router/match-middleware/cache-lookup.d.ts +14 -0
- package/dist/types/router/pattern-matching.d.ts +2 -0
- package/dist/types/router/prefetch-default.d.ts +4 -4
- package/dist/types/router/prerender-match.d.ts +23 -1
- package/dist/types/router/route-trie-builder.d.ts +8 -1
- package/dist/types/router/router-context.d.ts +1 -0
- package/dist/types/router/router-interfaces.d.ts +30 -0
- package/dist/types/router/router-options.d.ts +24 -0
- package/dist/types/router/segment-resolution/fresh.d.ts +7 -0
- package/dist/types/router/segment-resolution/loader-cache.d.ts +2 -1
- package/dist/types/router/segment-resolution/revalidation.d.ts +5 -1
- package/dist/types/router/segment-resolution/streamed-handler-boundary.d.ts +43 -0
- package/dist/types/router/trie-matching.d.ts +2 -0
- package/dist/types/rsc/shell-build-manifest.d.ts +6 -4
- package/dist/types/rsc/shell-capture-constants.d.ts +20 -5
- package/dist/types/rsc/shell-capture.d.ts +14 -1
- package/dist/types/rsc/shell-serve.d.ts +2 -2
- package/dist/types/rsc/ssr-module-loader.d.ts +15 -0
- package/dist/types/rsc/types.d.ts +8 -0
- package/dist/types/segment-boundary-content.d.ts +17 -0
- package/dist/types/segment-loader-promise.d.ts +2 -6
- package/dist/types/server/build-version-table.d.ts +9 -0
- package/dist/types/server/context.d.ts +12 -4
- package/dist/types/server/handle-store.d.ts +2 -0
- package/dist/types/server/request-context.d.ts +63 -2
- package/dist/types/suspense-audit-react.d.ts +26 -0
- package/dist/types/suspense-audit.d.ts +157 -0
- package/dist/types/testing/action-form.d.ts +34 -0
- package/dist/types/testing/flight.entry.d.ts +2 -0
- package/dist/types/testing/index.d.ts +4 -0
- package/dist/types/testing/internal/server-modules.d.ts +8 -0
- package/dist/types/testing/serve-shell-request.d.ts +34 -4
- package/dist/types/testing/shell-status.d.ts +14 -6
- package/dist/types/testing/vitest-stubs/plugin-rsc.d.ts +9 -6
- package/dist/types/thenable-status.d.ts +6 -0
- package/dist/types/types/handler-context.d.ts +7 -0
- package/dist/types/types/route-entry.d.ts +5 -0
- package/dist/types/urls/path-helper.d.ts +18 -0
- package/dist/types/vite/discovery/state.d.ts +11 -0
- package/dist/types/vite/inject-client-debug.d.ts +6 -25
- package/dist/types/vite/utils/bundle-analysis.d.ts +6 -2
- package/dist/vite/index.js +180 -497
- package/package.json +13 -3
- package/skills/cache-guide/SKILL.md +18 -17
- package/skills/caching/SKILL.md +40 -4
- package/skills/catalog.json +1 -1
- package/skills/cloudflare/SKILL.md +131 -0
- package/skills/comparison/references/framework-comparison.md +1 -1
- package/skills/deployment-caching/SKILL.md +33 -12
- package/skills/document-cache/SKILL.md +16 -14
- package/skills/hooks/SKILL.md +22 -0
- package/skills/hooks/handle-and-actions.md +3 -4
- package/skills/hooks/navigation.md +10 -0
- package/skills/hooks/state.md +5 -4
- package/skills/host-router/SKILL.md +18 -0
- package/skills/loader/SKILL.md +6 -4
- package/skills/middleware/SKILL.md +1 -1
- package/skills/migrate-nextjs/SKILL.md +82 -9
- package/skills/ppr/SKILL.md +31 -8
- package/skills/prerender/SKILL.md +645 -8
- package/skills/rango/SKILL.md +1 -0
- package/skills/response-routes/SKILL.md +5 -5
- package/skills/route/SKILL.md +29 -0
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/cache-prerender.md +18 -17
- package/skills/testing/serve-shell-request.md +104 -8
- package/skills/use-cache/SKILL.md +9 -5
- package/skills/vercel/SKILL.md +2 -0
- package/skills/view-transitions/SKILL.md +9 -4
- package/src/browser/event-controller.ts +69 -25
- package/src/browser/link-interceptor.ts +2 -2
- package/src/browser/navigation-bridge.ts +75 -35
- package/src/browser/navigation-store.ts +26 -0
- package/src/browser/navigation-transaction.ts +9 -2
- package/src/browser/network-error-handler.ts +2 -0
- package/src/browser/partial-update.ts +48 -12
- package/src/browser/prefetch/default-strategy.ts +22 -42
- package/src/browser/react/Link.tsx +37 -42
- package/src/browser/react/NavigationProvider.tsx +63 -3
- package/src/browser/react/context.ts +17 -0
- package/src/browser/react/location-state.ts +5 -19
- package/src/browser/react/use-action.ts +4 -1
- package/src/browser/react/use-handle.ts +4 -20
- package/src/browser/react/use-link-status.ts +4 -3
- package/src/browser/react/use-navigation.ts +4 -1
- package/src/browser/react/use-params.ts +4 -1
- package/src/browser/react/use-pathname.ts +4 -1
- package/src/browser/react/use-search-params.ts +5 -3
- package/src/browser/react/use-segments.ts +36 -54
- package/src/browser/rsc-router.tsx +79 -39
- package/src/browser/segment-reconciler.ts +3 -3
- package/src/browser/server-action-bridge.ts +26 -6
- package/src/browser/types.ts +11 -0
- package/src/build/generate-manifest.ts +17 -0
- package/src/build/merge-full-manifests.ts +5 -0
- package/src/build/route-trie.ts +1 -0
- package/src/cache/background-task.ts +61 -0
- package/src/cache/cache-key-utils.ts +91 -12
- package/src/cache/cache-policy.ts +11 -2
- package/src/cache/cache-runtime.ts +23 -23
- package/src/cache/cache-scope.ts +38 -36
- package/src/cache/cf/cf-cache-store.ts +16 -11
- package/src/cache/document-cache.ts +35 -14
- package/src/cache/handle-snapshot.ts +50 -0
- package/src/cache/index.ts +1 -0
- package/src/cache/invalidation-order.ts +53 -0
- package/src/cache/memory-segment-store.ts +52 -1
- package/src/cache/shell-snapshot.ts +17 -0
- package/src/cache/store-scope.ts +36 -0
- package/src/cache/tag-invalidation.ts +7 -5
- package/src/cache/types.ts +69 -14
- package/src/cache/vercel/vercel-cache-store.ts +20 -6
- package/src/client.tsx +40 -3
- package/src/errors.ts +13 -6
- package/src/handles/deferred-resolution.ts +7 -2
- package/src/internal-suspense-audit.ts +12 -0
- package/src/loader-store.ts +4 -9
- package/src/outlet-provider.tsx +25 -1
- package/src/prerender/cloudflare.ts +130 -0
- package/src/prerender/create-prerender-trigger.ts +780 -0
- package/src/prerender/index.ts +39 -0
- package/src/prerender/memory-prerender-store.ts +115 -0
- package/src/prerender/on-demand.ts +339 -0
- package/src/prerender/producer-guard.ts +66 -0
- package/src/prerender/shell-manifest-key.ts +12 -17
- package/src/prerender/warm-request.ts +154 -0
- package/src/prerender/warm.ts +193 -0
- package/src/prerender/writable-store.ts +278 -0
- package/src/prerender.ts +29 -0
- package/src/reverse.ts +1 -1
- package/src/route-content-wrapper.tsx +107 -13
- package/src/router/error-handling.ts +12 -12
- package/src/router/find-match.ts +1 -0
- package/src/router/handler-context.ts +2 -0
- package/src/router/intercept-resolution.ts +15 -8
- package/src/router/loader-resolution.ts +2 -10
- package/src/router/match-middleware/background-revalidation.ts +2 -0
- package/src/router/match-middleware/cache-lookup.ts +230 -38
- package/src/router/pattern-matching.ts +14 -0
- package/src/router/prefetch-default.ts +4 -4
- package/src/router/prerender-match.ts +460 -352
- package/src/router/route-trie-builder.ts +9 -0
- package/src/router/router-context.ts +1 -0
- package/src/router/router-interfaces.ts +33 -0
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +20 -19
- package/src/router/segment-resolution/loader-cache.ts +15 -17
- package/src/router/segment-resolution/revalidation.ts +34 -24
- package/src/router/segment-resolution/streamed-handler-boundary.ts +133 -0
- package/src/router/trie-matching.ts +3 -0
- package/src/router.ts +107 -1
- package/src/rsc/handler.ts +51 -4
- package/src/rsc/progressive-enhancement.ts +5 -1
- package/src/rsc/response-cache-serve.ts +10 -4
- package/src/rsc/rsc-rendering.ts +53 -11
- package/src/rsc/shell-build-manifest.ts +10 -8
- package/src/rsc/shell-capture-constants.ts +39 -7
- package/src/rsc/shell-capture.ts +108 -19
- package/src/rsc/shell-serve.ts +12 -6
- package/src/rsc/ssr-module-loader.ts +26 -0
- package/src/rsc/types.ts +8 -0
- package/src/segment-boundary-content.ts +25 -0
- package/src/segment-loader-promise.ts +28 -38
- package/src/segment-system.tsx +117 -57
- package/src/server/build-version-table.ts +15 -0
- package/src/server/context.ts +40 -17
- package/src/server/cookie-store.ts +5 -0
- package/src/server/handle-store.ts +13 -0
- package/src/server/loader-registry.ts +6 -1
- package/src/server/request-context.ts +87 -1
- package/src/ssr/ssr-root.tsx +20 -13
- package/src/suspense-audit-react.tsx +98 -0
- package/src/suspense-audit.ts +1030 -0
- package/src/testing/action-form.ts +66 -0
- package/src/testing/flight-runtime.d.ts +27 -0
- package/src/testing/flight.entry.ts +3 -0
- package/src/testing/index.ts +19 -0
- package/src/testing/internal/server-modules.ts +32 -0
- package/src/testing/render-route.tsx +25 -12
- package/src/testing/serve-shell-request.ts +190 -34
- package/src/testing/shell-status.ts +17 -6
- package/src/testing/vitest-stubs/plugin-rsc.ts +37 -11
- package/src/thenable-status.ts +10 -0
- package/src/types/handler-context.ts +7 -0
- package/src/types/route-entry.ts +6 -0
- package/src/urls/path-helper.ts +69 -9
- package/src/use-loader.tsx +57 -30
- package/src/vite/discovery/bundle-postprocess.ts +4 -0
- package/src/vite/discovery/discover-routers.ts +15 -0
- package/src/vite/discovery/shell-prerender-phase.ts +5 -5
- package/src/vite/discovery/state.ts +17 -1
- package/src/vite/inject-client-debug.ts +42 -20
- package/src/vite/plugins/virtual-entries.ts +8 -1
- package/src/vite/router-discovery.ts +14 -0
- package/src/vite/utils/bundle-analysis.ts +39 -7
- package/dist/types/browser/react/subscribe-to-nothing.d.ts +0 -7
- package/dist/types/segment-content-promise.d.ts +0 -13
- package/src/browser/react/subscribe-to-nothing.ts +0 -7
- package/src/segment-content-promise.ts +0 -67
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type {
|
|
9
|
+
CacheStoreScope,
|
|
9
10
|
SegmentCacheStore,
|
|
10
11
|
CachedEntryData,
|
|
11
12
|
CacheDefaults,
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
DEFAULT_FUNCTION_TTL,
|
|
24
25
|
} from "./cache-policy.js";
|
|
25
26
|
import { reportCacheError } from "./cache-error.js";
|
|
27
|
+
import { responseStartedAt } from "./invalidation-order.js";
|
|
26
28
|
|
|
27
29
|
const CACHE_REGISTRY_KEY = "__rsc_router_segment_cache_registry__";
|
|
28
30
|
const RESPONSE_CACHE_REGISTRY_KEY = "__rsc_router_response_cache_registry__";
|
|
@@ -146,6 +148,26 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
146
148
|
* ```
|
|
147
149
|
*/
|
|
148
150
|
maxEntries?: number;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Where an entry this store writes can be read; what `router.prerender()`
|
|
154
|
+
* checks before warming a route. Defaults to `"local"`: the maps live in one
|
|
155
|
+
* process, and the router cannot tell one long-running server from several
|
|
156
|
+
* replicas or serverless instances, so a warm into it is refused in
|
|
157
|
+
* production. Set `"global"` only when exactly one process serves the app
|
|
158
|
+
* (a single Node server); a warm then reports `warmed`. Under the Vite dev
|
|
159
|
+
* server a `"local"` instance already counts as shared.
|
|
160
|
+
*
|
|
161
|
+
* Like `defaults` and `keyGenerator`, the scope is per instance: two
|
|
162
|
+
* instances with the same `name` share their maps but each declares its own
|
|
163
|
+
* scope. Keep them identical.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* const store = new MemorySegmentCacheStore({ scope: "global" });
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
scope?: CacheStoreScope;
|
|
149
171
|
}
|
|
150
172
|
|
|
151
173
|
/** Default per-family entry cap for MemorySegmentCacheStore (FIFO eviction). */
|
|
@@ -188,6 +210,13 @@ const DEFAULT_MAX_ENTRIES = 1000;
|
|
|
188
210
|
export class MemorySegmentCacheStore<
|
|
189
211
|
TEnv = unknown,
|
|
190
212
|
> implements SegmentCacheStore<TEnv> {
|
|
213
|
+
/**
|
|
214
|
+
* SegmentCacheStore.scope: "local" unless `options.scope` says otherwise, so
|
|
215
|
+
* router.prerender() refuses to warm into these maps in production. Under
|
|
216
|
+
* the Vite dev server the gate counts a "local" instance as shared
|
|
217
|
+
* (store-scope.ts resolveWarmStoreScope).
|
|
218
|
+
*/
|
|
219
|
+
readonly scope: CacheStoreScope;
|
|
191
220
|
readonly supportsPassiveShellReads: true = true;
|
|
192
221
|
private cache: Map<string, CachedEntryData>;
|
|
193
222
|
private responseCache: Map<string, CachedResponseEntry>;
|
|
@@ -253,6 +282,7 @@ export class MemorySegmentCacheStore<
|
|
|
253
282
|
this.defaults = options?.defaults;
|
|
254
283
|
this.keyGenerator = options?.keyGenerator;
|
|
255
284
|
this.maxEntries = options?.maxEntries ?? DEFAULT_MAX_ENTRIES;
|
|
285
|
+
this.scope = options?.scope ?? "local";
|
|
256
286
|
}
|
|
257
287
|
|
|
258
288
|
/**
|
|
@@ -300,6 +330,7 @@ export class MemorySegmentCacheStore<
|
|
|
300
330
|
ttl: number,
|
|
301
331
|
_swr?: number,
|
|
302
332
|
): Promise<void> {
|
|
333
|
+
if (this.invalidatedAfter(data.tags, data.taggedAt)) return;
|
|
303
334
|
const entry: CachedEntryData = {
|
|
304
335
|
...data,
|
|
305
336
|
expiresAt: Date.now() + ttl * 1000,
|
|
@@ -358,6 +389,7 @@ export class MemorySegmentCacheStore<
|
|
|
358
389
|
tags?: string[],
|
|
359
390
|
): Promise<void> {
|
|
360
391
|
try {
|
|
392
|
+
const startedAt = responseStartedAt(response);
|
|
361
393
|
const body = await response.clone().arrayBuffer();
|
|
362
394
|
const headers: [string, string][] = [];
|
|
363
395
|
response.headers.forEach((value, name) => {
|
|
@@ -368,6 +400,8 @@ export class MemorySegmentCacheStore<
|
|
|
368
400
|
const swrWindow = resolveSwrWindow(swr, this.defaults);
|
|
369
401
|
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
370
402
|
|
|
403
|
+
// After the last await before the write.
|
|
404
|
+
if (this.invalidatedAfter(tags, startedAt)) return;
|
|
371
405
|
const prefixedKey = `res:${key}`;
|
|
372
406
|
this.unregisterTags(prefixedKey);
|
|
373
407
|
this.evictIfNeeded(this.responseCache, key, "res");
|
|
@@ -415,6 +449,7 @@ export class MemorySegmentCacheStore<
|
|
|
415
449
|
value: string,
|
|
416
450
|
options?: CacheItemOptions,
|
|
417
451
|
): Promise<void> {
|
|
452
|
+
if (this.invalidatedAfter(options?.tags, options?.startedAt)) return;
|
|
418
453
|
const ttl = resolveTtl(options?.ttl, this.defaults, DEFAULT_FUNCTION_TTL);
|
|
419
454
|
const swrWindow = resolveSwrWindow(options?.swr, this.defaults);
|
|
420
455
|
const { staleAt, expiresAt } = computeExpiration(ttl, swrWindow);
|
|
@@ -486,7 +521,23 @@ export class MemorySegmentCacheStore<
|
|
|
486
521
|
return this.tagsInvalidatedSince(tags, sinceMs);
|
|
487
522
|
}
|
|
488
523
|
|
|
489
|
-
|
|
524
|
+
/**
|
|
525
|
+
* A tag invalidated in a millisecond after the write's execution started
|
|
526
|
+
* (#1068). Strictly after: the start's own millisecond is ambiguous, and
|
|
527
|
+
* invalidation-order.ts orders those exactly.
|
|
528
|
+
*/
|
|
529
|
+
private invalidatedAfter(
|
|
530
|
+
tags: readonly string[] | undefined,
|
|
531
|
+
startedAt: number | undefined,
|
|
532
|
+
): boolean {
|
|
533
|
+
if (!tags || tags.length === 0 || startedAt === undefined) return false;
|
|
534
|
+
return this.tagsInvalidatedSince(tags, startedAt + 1);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private tagsInvalidatedSince(
|
|
538
|
+
tags: readonly string[],
|
|
539
|
+
sinceMs: number,
|
|
540
|
+
): boolean {
|
|
490
541
|
for (const tag of tags) {
|
|
491
542
|
const at = this.tagInvalidatedAt.get(tag);
|
|
492
543
|
// >= so a same-millisecond invalidation wins (freshness over staleness),
|
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
ShellCacheEntry,
|
|
33
33
|
ShellSnapshotRecord,
|
|
34
34
|
ShellSnapshotLoaderValue,
|
|
35
|
+
ShellSnapshotHandlesValue,
|
|
35
36
|
CacheReadError,
|
|
36
37
|
} from "./types.js";
|
|
37
38
|
import { settleGrowing } from "./background-task.js";
|
|
@@ -452,6 +453,22 @@ export async function buildShellLoaderSeed(
|
|
|
452
453
|
return seed.size > 0 ? seed : undefined;
|
|
453
454
|
}
|
|
454
455
|
|
|
456
|
+
/** The key of a snapshot's one `handles` record (ShellSnapshotHandlesValue). */
|
|
457
|
+
export const SHELL_HANDLES_RECORD_KEY = "handles";
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* The `handles` record of a prerender-served entry, which a HIT tail restores
|
|
461
|
+
* after the prerender store's handles (cache-lookup.ts yieldFromStore), or
|
|
462
|
+
* undefined: an entry with a doc record, or one written before the record
|
|
463
|
+
* existed (v0.21), which serves as it did.
|
|
464
|
+
*/
|
|
465
|
+
export function shellPrerenderHandles(
|
|
466
|
+
snapshot: readonly ShellSnapshotRecord[],
|
|
467
|
+
): ShellSnapshotHandlesValue | undefined {
|
|
468
|
+
const record = snapshot.find((r) => r?.family === "handles");
|
|
469
|
+
return record?.value as ShellSnapshotHandlesValue | undefined;
|
|
470
|
+
}
|
|
471
|
+
|
|
455
472
|
/**
|
|
456
473
|
* The implicit doc scope's store on a HIT tail and on a partial replay: it
|
|
457
474
|
* serves the snapshot's segment records, the doc record among them, and
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The store scope `router.prerender()` gates a warm on
|
|
3
|
+
* (prerender/create-prerender-trigger.ts). See
|
|
4
|
+
* docs/design/prerender-every-route.md, "The store declaration".
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { CacheStoreScope, SegmentCacheStore } from "./types.js";
|
|
8
|
+
import { MemorySegmentCacheStore } from "./memory-segment-store.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* True under the Vite dev server: only its routes-manifest virtual module sets
|
|
12
|
+
* the dev origin (vite/discovery/virtual-module-codegen.ts). A build, a preview
|
|
13
|
+
* server and a test runner answer false.
|
|
14
|
+
*/
|
|
15
|
+
export function isViteDevServer(): boolean {
|
|
16
|
+
return typeof globalThis.__PRERENDER_DEV_URL === "string";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The scope a warm treats `store` as having: its own declaration, with none
|
|
21
|
+
* or an unknown value being `"local"`.
|
|
22
|
+
*
|
|
23
|
+
* One exception: under the Vite dev server a MemorySegmentCacheStore answers
|
|
24
|
+
* `"global"`, because that one process serves every request. Production reads
|
|
25
|
+
* the class's own `scope` (`"local"`): changing that declaration is what
|
|
26
|
+
* would admit it there.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveWarmStoreScope(
|
|
29
|
+
store: SegmentCacheStore<any>,
|
|
30
|
+
devServer: boolean = isViteDevServer(),
|
|
31
|
+
): CacheStoreScope {
|
|
32
|
+
const declared = store.scope;
|
|
33
|
+
if (declared === "global" || declared === "regional") return declared;
|
|
34
|
+
if (devServer && store instanceof MemorySegmentCacheStore) return "global";
|
|
35
|
+
return "local";
|
|
36
|
+
}
|
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
export {
|
|
50
50
|
executionStart,
|
|
51
51
|
invalidatedSince,
|
|
52
|
+
markResponseStart,
|
|
52
53
|
type ExecutionStart,
|
|
53
54
|
} from "./invalidation-order.js";
|
|
54
55
|
|
|
@@ -147,11 +148,12 @@ function markFreshReads(stores: SegmentCacheStore[]): void {
|
|
|
147
148
|
* The write gate of every cache execution (#977): whether one of `tags` was
|
|
148
149
|
* invalidated after the execution that produced the value started.
|
|
149
150
|
*
|
|
150
|
-
*
|
|
151
|
-
* `taggedAt`, VercelCacheStore's `ta
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
151
|
+
* A store stamps an entry with the execution's start (CFCacheStore's
|
|
152
|
+
* `taggedAt`, VercelCacheStore's `ta`, #1068), but only CFCacheStore with KV
|
|
153
|
+
* markers rejects it on a later request's read, and MemorySegmentCacheStore
|
|
154
|
+
* refuses the write. The gate saves the write everywhere and is the only
|
|
155
|
+
* guard where a read compares nothing. Two answers, either one skips the
|
|
156
|
+
* write:
|
|
155
157
|
*
|
|
156
158
|
* - this isolate's order (invalidatedSince), which sees every request's
|
|
157
159
|
* updateTag()/revalidateTag() here, the calling one included;
|
package/src/cache/types.ts
CHANGED
|
@@ -42,6 +42,18 @@ export interface CacheGetResult {
|
|
|
42
42
|
shouldRevalidate: boolean;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Where an entry a store writes during one request can be read by a later
|
|
47
|
+
* request. `router.prerender()` warms a route only when the app store answers
|
|
48
|
+
* `"global"` or `"regional"`: a warm fills the store where it runs, which
|
|
49
|
+
* serves other traffic only when the store is shared beyond that place.
|
|
50
|
+
* - `"global"`: every location reads the same entries.
|
|
51
|
+
* - `"regional"`: each region has its own copy; a warm fills the region it
|
|
52
|
+
* runs in.
|
|
53
|
+
* - `"local"`: only the process, isolate or edge location that wrote it.
|
|
54
|
+
*/
|
|
55
|
+
export type CacheStoreScope = "global" | "regional" | "local";
|
|
56
|
+
|
|
45
57
|
/**
|
|
46
58
|
* Low-level segment cache store interface.
|
|
47
59
|
*
|
|
@@ -59,6 +71,12 @@ export interface CacheGetResult {
|
|
|
59
71
|
* @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
|
|
60
72
|
*/
|
|
61
73
|
export interface SegmentCacheStore<TEnv = unknown> {
|
|
74
|
+
/**
|
|
75
|
+
* Where an entry this store writes can be read ({@link CacheStoreScope}).
|
|
76
|
+
* Absent: unknown to the router, treated as `"local"`.
|
|
77
|
+
*/
|
|
78
|
+
readonly scope?: CacheStoreScope;
|
|
79
|
+
|
|
62
80
|
/**
|
|
63
81
|
* The store honors getShell(..., { claimRevalidation: false }) without
|
|
64
82
|
* claiming an SWR lock. Navigation replay requires this opt-in because it
|
|
@@ -168,6 +186,11 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
168
186
|
* @param ttl - Time-to-live in seconds
|
|
169
187
|
* @param swr - Optional stale-while-revalidate window in seconds
|
|
170
188
|
* @param tags - Optional cache tags for invalidation
|
|
189
|
+
*
|
|
190
|
+
* The built-in stores read the start of the render from the `Response`
|
|
191
|
+
* object itself (#1068). A custom store, or a wrapper that rebuilds the
|
|
192
|
+
* `Response` before delegating, stamps write time. The write gate
|
|
193
|
+
* (`predatesInvalidation`) runs before this call either way.
|
|
171
194
|
*/
|
|
172
195
|
putResponse?(
|
|
173
196
|
key: string,
|
|
@@ -451,9 +474,10 @@ export interface ShellCacheEntry {
|
|
|
451
474
|
* bake-lane loader pins (pruned to what a HIT reads, issue #941). Replaying
|
|
452
475
|
* them on a HIT keeps the freshly rendered hydration payload equal to the
|
|
453
476
|
* frozen prelude after the underlying cache entries have drifted; every
|
|
454
|
-
* other read on a HIT, a hole's included, reads the store.
|
|
455
|
-
*
|
|
456
|
-
* a tombstone
|
|
477
|
+
* other read on a HIT, a hole's included, reads the store. A
|
|
478
|
+
* prerender-served capture holds a `handles` record instead of a doc record
|
|
479
|
+
* (ShellSnapshotHandlesValue). Empty for a tombstone. A custom store
|
|
480
|
+
* returns it as putShell received it. See
|
|
457
481
|
* docs/design/ppr-shell-resume.md ("the capture data snapshot").
|
|
458
482
|
*/
|
|
459
483
|
snapshot: ShellSnapshotRecord[];
|
|
@@ -614,12 +638,26 @@ export interface ShellReadStats {
|
|
|
614
638
|
|
|
615
639
|
/**
|
|
616
640
|
* The families a shell snapshot pins: the doc segment record (recorded by
|
|
617
|
-
* RecordingShellStore)
|
|
618
|
-
* (lane rule: see resolveLoaderData, loader-cache.ts)
|
|
619
|
-
* pinned: a HIT's holes, and a bake-lane
|
|
620
|
-
* read the store.
|
|
641
|
+
* RecordingShellStore), the settled CONTAINER of each bake-lane loader
|
|
642
|
+
* (lane rule: see resolveLoaderData, loader-cache.ts), and a prerender-served
|
|
643
|
+
* entry's `handles` record. No cache read is pinned: a HIT's holes, and a bake-lane
|
|
644
|
+
* loader body that runs on the HIT, read the store.
|
|
621
645
|
*/
|
|
622
|
-
export type ShellSnapshotFamily = "segment" | "loader";
|
|
646
|
+
export type ShellSnapshotFamily = "segment" | "loader" | "handles";
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* The stored form of the `handles` family: the handle arrays of a
|
|
650
|
+
* prerender-served capture that hold a loader-owned push, in the doc record's
|
|
651
|
+
* format (CachedEntryData `handles` + `handleOwners`). A Prerender + ppr
|
|
652
|
+
* route's handler layer comes from the prerender store, whose build-time
|
|
653
|
+
* entry has no loader push, so the entry keeps these for the HIT to restore
|
|
654
|
+
* as a doc record's (issue #1057, yieldFromStore in cache-lookup.ts). An
|
|
655
|
+
* entry written before it (v0.21) has none and serves as it did.
|
|
656
|
+
*/
|
|
657
|
+
export type ShellSnapshotHandlesValue = Pick<
|
|
658
|
+
CachedEntryData,
|
|
659
|
+
"handles" | "handleOwners"
|
|
660
|
+
>;
|
|
623
661
|
|
|
624
662
|
/**
|
|
625
663
|
* The stored form of a loader-family snapshot value: the bake-lane loader's
|
|
@@ -654,17 +692,18 @@ export interface ShellSnapshotLoaderValue {
|
|
|
654
692
|
}
|
|
655
693
|
|
|
656
694
|
/**
|
|
657
|
-
* One snapshot record: the doc segment record
|
|
658
|
-
* `value` carries it in its
|
|
659
|
-
* through a JSON-serializing store
|
|
660
|
-
* ShellCacheEntry:
|
|
695
|
+
* One snapshot record: the doc segment record, a bake-lane loader pin, or a
|
|
696
|
+
* prerender-served entry's handle record. `value` carries it in its
|
|
697
|
+
* stored/serialized shape so it round-trips through a JSON-serializing store
|
|
698
|
+
* (KV, CF, Vercel) with the rest of the ShellCacheEntry:
|
|
661
699
|
* - `segment` -> {@link CachedEntryData} (already JSON-able)
|
|
662
700
|
* - `loader` -> {@link ShellSnapshotLoaderValue}
|
|
701
|
+
* - `handles` -> {@link ShellSnapshotHandlesValue}
|
|
663
702
|
*/
|
|
664
703
|
export interface ShellSnapshotRecord {
|
|
665
704
|
family: ShellSnapshotFamily;
|
|
666
705
|
key: string;
|
|
667
|
-
value: CachedEntryData | ShellSnapshotLoaderValue;
|
|
706
|
+
value: CachedEntryData | ShellSnapshotLoaderValue | ShellSnapshotHandlesValue;
|
|
668
707
|
}
|
|
669
708
|
|
|
670
709
|
/**
|
|
@@ -679,6 +718,17 @@ export interface CacheItemOptions {
|
|
|
679
718
|
swr?: number;
|
|
680
719
|
/** Cache tags for invalidation */
|
|
681
720
|
tags?: string[];
|
|
721
|
+
/**
|
|
722
|
+
* Ms since epoch when the execution that produced the value started (#1068).
|
|
723
|
+
* A store stamps a tagged entry with it instead of its write time. Later
|
|
724
|
+
* requests then reject an entry an invalidation landed under in
|
|
725
|
+
* CFCacheStore with KV markers; MemorySegmentCacheStore refuses the write.
|
|
726
|
+
* Not rejected: VercelCacheStore (a read compares the stamp with the
|
|
727
|
+
* invalidating request's mask only) and CFCacheStore L1 hits in purge mode
|
|
728
|
+
* or without KV. Absent or not positive: write time. A custom store may
|
|
729
|
+
* ignore it.
|
|
730
|
+
*/
|
|
731
|
+
startedAt?: number;
|
|
682
732
|
}
|
|
683
733
|
|
|
684
734
|
/**
|
|
@@ -726,7 +776,12 @@ export interface CachedEntryData {
|
|
|
726
776
|
expiresAt: number;
|
|
727
777
|
/** Cache tags for invalidation */
|
|
728
778
|
tags?: string[];
|
|
729
|
-
/**
|
|
779
|
+
/**
|
|
780
|
+
* Timestamp (ms since epoch) the entry's tags are valid as of, for
|
|
781
|
+
* distributed invalidation. The writer sets it to the start of the render
|
|
782
|
+
* that produced the record; a store stamps write time when it is absent
|
|
783
|
+
* (#1068). Which reads reject on it: see `CacheItemOptions.startedAt`.
|
|
784
|
+
*/
|
|
730
785
|
taggedAt?: number;
|
|
731
786
|
}
|
|
732
787
|
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
*/
|
|
36
36
|
|
|
37
37
|
import type {
|
|
38
|
+
CacheStoreScope,
|
|
38
39
|
SegmentCacheStore,
|
|
39
40
|
CachedEntryData,
|
|
40
41
|
CacheDefaults,
|
|
@@ -106,6 +107,7 @@ import {
|
|
|
106
107
|
type MarkerFetch,
|
|
107
108
|
type MarkerMemoOutcome,
|
|
108
109
|
} from "../isolate-tag-memo.js";
|
|
110
|
+
import { responseStartedAt, taggedStamp } from "../invalidation-order.js";
|
|
109
111
|
|
|
110
112
|
/**
|
|
111
113
|
* Minimal structural shape of the Vercel Runtime Cache returned by `getCache()`
|
|
@@ -454,9 +456,15 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
454
456
|
return typeof value === "object" && value !== null;
|
|
455
457
|
}
|
|
456
458
|
|
|
457
|
-
/**
|
|
458
|
-
|
|
459
|
-
|
|
459
|
+
/**
|
|
460
|
+
* The envelope's `ta` stamp, on tagged entries only: the producing execution's
|
|
461
|
+
* start when the writer passed it, else write time (#1068). Only the request
|
|
462
|
+
* mask reads it (expireTag keeps no queryable history), so it decides whether
|
|
463
|
+
* the invalidating request's own late write is masked.
|
|
464
|
+
*/
|
|
465
|
+
function tagStamp(tags: string[], startedAt?: number): { ta?: number } {
|
|
466
|
+
const ta = taggedStamp(tags, startedAt);
|
|
467
|
+
return ta === undefined ? {} : { ta };
|
|
460
468
|
}
|
|
461
469
|
|
|
462
470
|
/**
|
|
@@ -512,6 +520,12 @@ function tagStamp(tags: string[]): { ta?: number } {
|
|
|
512
520
|
export class VercelCacheStore<
|
|
513
521
|
TEnv = unknown,
|
|
514
522
|
> implements SegmentCacheStore<TEnv> {
|
|
523
|
+
/**
|
|
524
|
+
* SegmentCacheStore.scope: the Runtime Cache is per region. A
|
|
525
|
+
* router.prerender() warm fills the region it runs in, which is all traffic
|
|
526
|
+
* only when the project's functions run in one region.
|
|
527
|
+
*/
|
|
528
|
+
readonly scope: CacheStoreScope = "regional";
|
|
515
529
|
readonly supportsPassiveShellReads: true = true;
|
|
516
530
|
readonly defaults?: CacheDefaults;
|
|
517
531
|
readonly keyGenerator?: (
|
|
@@ -649,7 +663,7 @@ export class VercelCacheStore<
|
|
|
649
663
|
d,
|
|
650
664
|
s: staleAt,
|
|
651
665
|
e: expiresAt,
|
|
652
|
-
...tagStamp(safeTags),
|
|
666
|
+
...tagStamp(safeTags, data.taggedAt),
|
|
653
667
|
};
|
|
654
668
|
await this.write(
|
|
655
669
|
this.toStoreKey(key, "s"),
|
|
@@ -797,7 +811,7 @@ export class VercelCacheStore<
|
|
|
797
811
|
s: staleAt,
|
|
798
812
|
e: expiresAt,
|
|
799
813
|
t: safeTags.length > 0 ? safeTags : undefined,
|
|
800
|
-
...tagStamp(safeTags),
|
|
814
|
+
...tagStamp(safeTags, responseStartedAt(response)),
|
|
801
815
|
};
|
|
802
816
|
await this.write(
|
|
803
817
|
this.toStoreKey(key, "r"),
|
|
@@ -903,7 +917,7 @@ export class VercelCacheStore<
|
|
|
903
917
|
s: staleAt,
|
|
904
918
|
e: expiresAt,
|
|
905
919
|
t: safeTags.length > 0 ? safeTags : undefined,
|
|
906
|
-
...tagStamp(safeTags),
|
|
920
|
+
...tagStamp(safeTags, options?.startedAt),
|
|
907
921
|
};
|
|
908
922
|
await this.write(
|
|
909
923
|
this.toStoreKey(key, "i"),
|
package/src/client.tsx
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
import {
|
|
4
4
|
Component,
|
|
5
5
|
createElement,
|
|
6
|
+
isValidElement,
|
|
6
7
|
useContext,
|
|
7
8
|
useMemo,
|
|
8
9
|
Suspense,
|
|
10
|
+
type ComponentProps,
|
|
9
11
|
type ReactNode,
|
|
10
12
|
} from "react";
|
|
11
13
|
// Not `export * as Html`: plugin-rsc's "use client" proxy transform only
|
|
@@ -19,12 +21,15 @@ import {
|
|
|
19
21
|
type ResolvedSegment,
|
|
20
22
|
} from "./types";
|
|
21
23
|
import {
|
|
24
|
+
AuditedLoaderBoundary,
|
|
25
|
+
AuditedRouteContent,
|
|
22
26
|
RouteContentWrapper,
|
|
23
27
|
LoaderBoundary,
|
|
24
28
|
} from "./route-content-wrapper.js";
|
|
25
|
-
import { OutletProvider } from "./outlet-provider.js";
|
|
29
|
+
import { AuditedOutletProvider, OutletProvider } from "./outlet-provider.js";
|
|
30
|
+
import { INTERNAL_RANGO_SUSPENSE_AUDIT } from "./internal-suspense-audit.js";
|
|
26
31
|
import { MountContextProvider } from "./browser/react/mount-context.js";
|
|
27
|
-
import {
|
|
32
|
+
import { getBoundaryContent } from "./segment-boundary-content.js";
|
|
28
33
|
|
|
29
34
|
/**
|
|
30
35
|
* Render the content for a named parallel/intercept slot segment.
|
|
@@ -39,7 +44,7 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
39
44
|
const content: ReactNode =
|
|
40
45
|
segment.loading || segment.component instanceof Promise ? (
|
|
41
46
|
<RouteContentWrapper
|
|
42
|
-
content={
|
|
47
|
+
content={getBoundaryContent(segment.component)}
|
|
43
48
|
fallback={segment.loading}
|
|
44
49
|
segmentId={segment.id}
|
|
45
50
|
/>
|
|
@@ -83,6 +88,10 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
83
88
|
result = content;
|
|
84
89
|
}
|
|
85
90
|
|
|
91
|
+
if (process.env.NODE_ENV !== "production" && INTERNAL_RANGO_SUSPENSE_AUDIT) {
|
|
92
|
+
result = auditedSlot(result);
|
|
93
|
+
}
|
|
94
|
+
|
|
86
95
|
if (segment.mountPath) {
|
|
87
96
|
return (
|
|
88
97
|
<MountContextProvider value={segment.mountPath}>
|
|
@@ -94,6 +103,34 @@ function renderSlotContent(segment: ResolvedSegment | null): ReactNode {
|
|
|
94
103
|
return result;
|
|
95
104
|
}
|
|
96
105
|
|
|
106
|
+
// Dev only (suspense-audit.ts): the boundaries renderSlotContent built, as
|
|
107
|
+
// their audited components with the same props. After the fact, not a choice
|
|
108
|
+
// per JSX element: a build must fold to the product elements alone (see
|
|
109
|
+
// AuditedRouteContent, route-content-wrapper.tsx).
|
|
110
|
+
function auditedSlot(node: ReactNode): ReactNode {
|
|
111
|
+
if (!isValidElement(node)) return node;
|
|
112
|
+
const props = node.props as Record<string, any>;
|
|
113
|
+
if (node.type === OutletProvider) {
|
|
114
|
+
return createElement(AuditedOutletProvider, {
|
|
115
|
+
...(props as ComponentProps<typeof OutletProvider>),
|
|
116
|
+
content: auditedSlot(props.content),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (node.type === LoaderBoundary) {
|
|
120
|
+
return createElement(AuditedLoaderBoundary, {
|
|
121
|
+
...(props as ComponentProps<typeof LoaderBoundary>),
|
|
122
|
+
children: auditedSlot(props.children),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (node.type === RouteContentWrapper) {
|
|
126
|
+
return createElement(
|
|
127
|
+
AuditedRouteContent,
|
|
128
|
+
props as ComponentProps<typeof RouteContentWrapper>,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return node;
|
|
132
|
+
}
|
|
133
|
+
|
|
97
134
|
function useSlotSegment(
|
|
98
135
|
context: OutletContextValue | null,
|
|
99
136
|
name: `@${string}` | undefined,
|
package/src/errors.ts
CHANGED
|
@@ -356,6 +356,18 @@ export function invariant(condition: any, message: string): asserts condition {
|
|
|
356
356
|
* @param error - Error to sanitize
|
|
357
357
|
* @returns Response suitable for sending to client
|
|
358
358
|
*/
|
|
359
|
+
/**
|
|
360
|
+
* Vite replaces import.meta.env.DEV at compile time. The fallback covers
|
|
361
|
+
* non-Vite environments (plain Node, test runners without Vite transforms).
|
|
362
|
+
* SECURITY: fail closed — default to production when the environment is ambiguous.
|
|
363
|
+
*/
|
|
364
|
+
export function isDevEnvironment(): boolean {
|
|
365
|
+
return (
|
|
366
|
+
(import.meta as any).env?.DEV ??
|
|
367
|
+
globalThis.process?.env?.NODE_ENV === "development"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
359
371
|
export function sanitizeError(error: unknown): Response {
|
|
360
372
|
// CRITICAL: Consume stack trace to prevent memory leaks
|
|
361
373
|
if (error && typeof error === "object" && "stack" in error) {
|
|
@@ -367,12 +379,7 @@ export function sanitizeError(error: unknown): Response {
|
|
|
367
379
|
return error;
|
|
368
380
|
}
|
|
369
381
|
|
|
370
|
-
|
|
371
|
-
// non-Vite environments (plain Node, test runners without Vite transforms).
|
|
372
|
-
// SECURITY: fail closed — default to production when the environment is ambiguous.
|
|
373
|
-
const isDev =
|
|
374
|
-
(import.meta as any).env?.DEV ??
|
|
375
|
-
globalThis.process?.env?.NODE_ENV === "development";
|
|
382
|
+
const isDev = isDevEnvironment();
|
|
376
383
|
|
|
377
384
|
if (isDev) {
|
|
378
385
|
// Development: Send full error details for debugging
|
|
@@ -124,8 +124,13 @@ export async function* resolvedHandleStream(
|
|
|
124
124
|
yield await resolveDeferredHandleValues(snapshot);
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
/**
|
|
128
|
-
|
|
127
|
+
/**
|
|
128
|
+
* `snapshot` without the pushes a record leaves out. A shell capture renders
|
|
129
|
+
* (resolvedHandleStream) and waits (settleCaptureRecord) on this cut only: a
|
|
130
|
+
* promise the record drops has no slot in the prelude, and waiting for it
|
|
131
|
+
* could refuse a capture for a value the shell does not hold.
|
|
132
|
+
*/
|
|
133
|
+
export function recordedHandleData(
|
|
129
134
|
handleStore: HandleStore,
|
|
130
135
|
snapshot: HandleData,
|
|
131
136
|
): HandleData {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Turns on the suspense audit (suspense-audit.ts) in a dev build. Only this
|
|
2
|
+
// repo sets INTERNAL_RANGO_SUSPENSE_AUDIT: the e2e webServers and fixtures of
|
|
3
|
+
// the router test-app and cloudflare-basic, and the router's vitest config.
|
|
4
|
+
// A consumer's dev server and tests never print the audit's errors.
|
|
5
|
+
//
|
|
6
|
+
// Runtime fallback for non-Vite contexts (vitest, Node). In the Vite pipeline
|
|
7
|
+
// the discovery plugin replaces this module with the build-time-resolved flag
|
|
8
|
+
// (vite/inject-client-debug.ts), as it does for internal-debug.ts. Keep this
|
|
9
|
+
// module to the single export: the transform replaces the whole file.
|
|
10
|
+
export const INTERNAL_RANGO_SUSPENSE_AUDIT: boolean =
|
|
11
|
+
typeof process !== "undefined" &&
|
|
12
|
+
Boolean((process as any).env?.INTERNAL_RANGO_SUSPENSE_AUDIT);
|
package/src/loader-store.ts
CHANGED
|
@@ -29,9 +29,10 @@
|
|
|
29
29
|
* reclaim a bucket that is about to be reused, and is held until any
|
|
30
30
|
* in-flight load settles.
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* Each router's NavigationStore owns one (navigation-store.ts), and hooks reach
|
|
33
|
+
* it through NavigationStoreContext: the router store is the single source of
|
|
34
|
+
* truth. Server renders have no store — the hook falls back to
|
|
35
|
+
* `OutletContext.loaderData`.
|
|
35
36
|
*/
|
|
36
37
|
|
|
37
38
|
import { unwrapsSynchronously } from "./thenable-status.js";
|
|
@@ -597,10 +598,4 @@ export class LoaderStore {
|
|
|
597
598
|
}
|
|
598
599
|
}
|
|
599
600
|
|
|
600
|
-
/**
|
|
601
|
-
* Module-level singleton. Each browser tab gets its own; SSR never mutates it.
|
|
602
|
-
* The hook falls through to `OutletContext.loaderData` during the server render.
|
|
603
|
-
*/
|
|
604
|
-
export const loaderStore: LoaderStore = new LoaderStore();
|
|
605
|
-
|
|
606
601
|
export const EMPTY_LOADER_SNAPSHOT: LoaderEntry = EMPTY_SNAPSHOT;
|
package/src/outlet-provider.tsx
CHANGED
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
useContext,
|
|
5
|
+
useMemo,
|
|
6
|
+
type ComponentProps,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
4
9
|
import { OutletContext, type OutletContextValue } from "./outlet-context.js";
|
|
5
10
|
import type { ResolvedSegment } from "./types.js";
|
|
11
|
+
import { AuditedOutlet } from "./suspense-audit-react.js";
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
14
|
* Outlet content provider — stores parent context for useLoader chain walking.
|
|
@@ -57,3 +63,21 @@ export function OutletProvider({
|
|
|
57
63
|
<OutletContext.Provider value={value}>{children}</OutletContext.Provider>
|
|
58
64
|
);
|
|
59
65
|
}
|
|
66
|
+
|
|
67
|
+
// Dev only (suspense-audit.ts): OutletProvider with the segment's mount
|
|
68
|
+
// tracked under it. Chosen where the element is created, inside a NODE_ENV
|
|
69
|
+
// test: see AuditedRouteContent (route-content-wrapper.tsx).
|
|
70
|
+
export function AuditedOutletProvider({
|
|
71
|
+
children,
|
|
72
|
+
...props
|
|
73
|
+
}: ComponentProps<typeof OutletProvider>): ReactNode {
|
|
74
|
+
return (
|
|
75
|
+
<OutletProvider {...props}>
|
|
76
|
+
{props.segment ? (
|
|
77
|
+
<AuditedOutlet segmentId={props.segment.id}>{children}</AuditedOutlet>
|
|
78
|
+
) : (
|
|
79
|
+
children
|
|
80
|
+
)}
|
|
81
|
+
</OutletProvider>
|
|
82
|
+
);
|
|
83
|
+
}
|