@rangojs/router 0.1.0 → 0.1.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 +11 -1
- package/dist/types/browser/dev-discovery.d.ts +11 -0
- package/dist/types/browser/navigation-store.d.ts +3 -13
- package/dist/types/browser/prefetch/loader.d.ts +10 -0
- package/dist/types/browser/prefetch/runtime.d.ts +3 -0
- package/dist/types/browser/types.d.ts +4 -16
- package/dist/types/build/generate-manifest.d.ts +6 -0
- package/dist/types/cache/cache-scope.d.ts +7 -5
- package/dist/types/cache/cf/cf-cache-store.d.ts +10 -9
- package/dist/types/cache/memory-segment-store.d.ts +6 -6
- package/dist/types/cache/shell-snapshot.d.ts +21 -8
- package/dist/types/cache/types.d.ts +24 -7
- package/dist/types/cache/vercel/vercel-cache-store.d.ts +9 -8
- package/dist/types/dev-discovery-protocol.d.ts +5 -0
- package/dist/types/prerender/store.d.ts +1 -0
- package/dist/types/route-map-builder.d.ts +4 -29
- package/dist/types/router/prerender-match.d.ts +4 -1
- package/dist/types/router/router-interfaces.d.ts +3 -1
- package/dist/types/rsc/handler-context.d.ts +1 -0
- package/dist/types/rsc/render-pipeline.d.ts +3 -2
- package/dist/types/rsc/rsc-rendering.d.ts +5 -10
- package/dist/types/rsc/shell-capture.d.ts +4 -8
- package/dist/types/rsc/types.d.ts +2 -0
- package/dist/types/server/context.d.ts +16 -0
- package/dist/types/server/request-context.d.ts +18 -8
- package/dist/types/server.d.ts +1 -1
- package/dist/types/types/route-entry.d.ts +3 -0
- package/dist/types/vite/discovery/state.d.ts +3 -2
- package/dist/types/vite/utils/manifest-utils.d.ts +1 -1
- package/dist/vite/index.js +194 -107
- package/package.json +7 -2
- package/skills/caching/SKILL.md +1 -1
- package/skills/ppr/SKILL.md +48 -4
- package/src/browser/dev-discovery.ts +66 -0
- package/src/browser/navigation-client.ts +6 -0
- package/src/browser/navigation-store.ts +4 -271
- package/src/browser/partial-update.ts +7 -14
- package/src/browser/prefetch/cache.ts +1 -1
- package/src/browser/prefetch/loader.ts +110 -0
- package/src/browser/prefetch/runtime.ts +7 -0
- package/src/browser/react/Link.tsx +7 -10
- package/src/browser/react/NavigationProvider.tsx +1 -1
- package/src/browser/react/use-router.ts +1 -1
- package/src/browser/rsc-router.tsx +4 -2
- package/src/browser/types.ts +4 -27
- package/src/build/generate-manifest.ts +11 -0
- package/src/cache/cache-scope.ts +17 -6
- package/src/cache/cf/cf-cache-store.ts +63 -39
- package/src/cache/memory-segment-store.ts +17 -6
- package/src/cache/shell-snapshot.ts +45 -15
- package/src/cache/types.ts +23 -6
- package/src/cache/vercel/vercel-cache-store.ts +52 -22
- package/src/dev-discovery-protocol.ts +11 -0
- package/src/prerender/build-shell-capture.ts +7 -1
- package/src/prerender/store.ts +5 -0
- package/src/route-definition/dsl-helpers.ts +7 -2
- package/src/route-map-builder.ts +56 -49
- package/src/router/handler-context.ts +13 -5
- package/src/router/lazy-includes.ts +1 -0
- package/src/router/match-api.ts +8 -4
- package/src/router/prerender-match.ts +19 -2
- package/src/router/router-interfaces.ts +4 -0
- package/src/router/segment-resolution/static-store.ts +36 -10
- package/src/router.ts +34 -3
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +3 -1
- package/src/rsc/loader-fetch.ts +2 -2
- package/src/rsc/manifest-init.ts +4 -11
- package/src/rsc/render-pipeline.ts +10 -2
- package/src/rsc/rsc-rendering.ts +207 -146
- package/src/rsc/shell-capture.ts +34 -11
- package/src/rsc/types.ts +2 -0
- package/src/server/context.ts +28 -0
- package/src/server/request-context.ts +32 -10
- package/src/server.ts +0 -2
- package/src/testing/dispatch.ts +1 -0
- package/src/types/route-entry.ts +3 -0
- package/src/urls/include-helper.ts +1 -0
- package/src/urls/path-helper.ts +8 -4
- package/src/vite/discovery/bundle-postprocess.ts +10 -7
- package/src/vite/discovery/discover-routers.ts +7 -66
- package/src/vite/discovery/prerender-collection.ts +13 -2
- package/src/vite/discovery/state.ts +4 -4
- package/src/vite/discovery/virtual-module-codegen.ts +75 -42
- package/src/vite/plugins/version-injector.ts +0 -1
- package/src/vite/plugins/virtual-entries.ts +11 -1
- package/src/vite/router-discovery.ts +132 -22
- package/src/vite/utils/manifest-utils.ts +1 -4
- package/src/vite/utils/shared-utils.ts +7 -0
package/dist/bin/rango.js
CHANGED
|
@@ -1424,7 +1424,17 @@ async function initializeApp() {
|
|
|
1424
1424
|
// context, including strictMode (default true) from createRouter. StrictMode
|
|
1425
1425
|
// is the default; createRouter({ strictMode: false }) ships the opt-out in the
|
|
1426
1426
|
// payload metadata. StrictMode emits no DOM, so toggling never changes markup.
|
|
1427
|
-
const { strictMode } = await initBrowserApp({ rscStream, deps });
|
|
1427
|
+
const { strictMode, initialPayload } = await initBrowserApp({ rscStream, deps });
|
|
1428
|
+
|
|
1429
|
+
if (import.meta.hot) {
|
|
1430
|
+
const { startDevDiscoveryHandshake } = await import(
|
|
1431
|
+
"@rangojs/router/internal/browser/dev-discovery"
|
|
1432
|
+
);
|
|
1433
|
+
startDevDiscoveryHandshake(
|
|
1434
|
+
initialPayload.metadata?.devDiscoveryEpoch,
|
|
1435
|
+
import.meta.hot
|
|
1436
|
+
);
|
|
1437
|
+
}
|
|
1428
1438
|
|
|
1429
1439
|
const app = createElement(Rango);
|
|
1430
1440
|
hydrateRoot(
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DEV_DISCOVERY_QUERY_EVENT, DEV_DISCOVERY_READY_EVENT } from "../dev-discovery-protocol.js";
|
|
2
|
+
export interface DevDiscoveryHot {
|
|
3
|
+
on(event: typeof DEV_DISCOVERY_READY_EVENT | "vite:ws:connect", listener: (payload: unknown) => void): void;
|
|
4
|
+
send(event: typeof DEV_DISCOVERY_QUERY_EVENT, payload: {
|
|
5
|
+
epoch: number;
|
|
6
|
+
}): void;
|
|
7
|
+
}
|
|
8
|
+
export interface DevDiscoveryHandshakeOptions {
|
|
9
|
+
reload?: () => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function startDevDiscoveryHandshake(documentEpoch: unknown, hot: DevDiscoveryHot, options?: DevDiscoveryHandshakeOptions): void;
|
|
@@ -61,11 +61,10 @@ export interface NavigationStoreConfig {
|
|
|
61
61
|
onCrossTabRefresh?: () => void;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
* Create a navigation store for
|
|
64
|
+
* Create a navigation store for browser-side segment and history state.
|
|
65
65
|
*
|
|
66
|
-
* The
|
|
67
|
-
*
|
|
68
|
-
* - SegmentState: Internal segment management for partial RSC updates
|
|
66
|
+
* The public navigation lifecycle lives in EventController; this store owns
|
|
67
|
+
* segment reconciliation, history snapshots, and cross-tab cache invalidation.
|
|
69
68
|
*
|
|
70
69
|
* @param config - Initial configuration
|
|
71
70
|
* @returns NavigationStore instance
|
|
@@ -77,15 +76,6 @@ export interface NavigationStoreConfig {
|
|
|
77
76
|
* initialSegmentIds: [],
|
|
78
77
|
* });
|
|
79
78
|
*
|
|
80
|
-
* // Subscribe to state changes (for useNavigation hook)
|
|
81
|
-
* const unsubscribe = store.subscribe(() => {
|
|
82
|
-
* const state = store.getState();
|
|
83
|
-
* console.log('Navigation state:', state);
|
|
84
|
-
* });
|
|
85
|
-
*
|
|
86
|
-
* // Update state
|
|
87
|
-
* store.setState({ state: 'loading' });
|
|
88
|
-
*
|
|
89
79
|
* // Subscribe to UI updates (for re-rendering)
|
|
90
80
|
* store.onUpdate((update) => {
|
|
91
81
|
* console.log('New root:', update.root);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RscPayload } from "../types.js";
|
|
2
|
+
type PrefetchDecoder = (response: Promise<Response>) => Promise<RscPayload>;
|
|
3
|
+
export declare function setPrefetchDecoder(fn: PrefetchDecoder): void;
|
|
4
|
+
export declare function setPrefetchConcurrency(value: number): void;
|
|
5
|
+
export declare function prefetchDirect(url: string, segmentIds: string[], version?: string, routerId?: string, prefetchKey?: ":source"): void;
|
|
6
|
+
export declare function prefetchQueued(url: string, segmentIds: string[], version?: string, routerId?: string, prefetchKey?: ":source"): void;
|
|
7
|
+
export declare function observeForPrefetch(element: Element, callback: () => void): () => void;
|
|
8
|
+
export declare function cancelAllPrefetches(keepUrl?: string | null): void;
|
|
9
|
+
export declare function abortAllPrefetches(): void;
|
|
10
|
+
export {};
|
|
@@ -60,6 +60,8 @@ export interface RscMetadata {
|
|
|
60
60
|
* Used to detect version mismatches after HMR/deployment.
|
|
61
61
|
*/
|
|
62
62
|
version?: string;
|
|
63
|
+
/** Cloudflare dev worker generation used for stale-document convergence. */
|
|
64
|
+
devDiscoveryEpoch?: number;
|
|
63
65
|
/**
|
|
64
66
|
* TTL in milliseconds for the client-side in-memory prefetch cache.
|
|
65
67
|
* Sent on initial render so the browser can configure its cache duration.
|
|
@@ -370,18 +372,10 @@ export type StateListener = () => void;
|
|
|
370
372
|
/**
|
|
371
373
|
* Navigation store interface
|
|
372
374
|
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* - SegmentState: Internal segment management for partial updates
|
|
375
|
+
* Owns segment state, history snapshots, and partial-update notifications.
|
|
376
|
+
* EventController owns the public navigation lifecycle exposed by hooks.
|
|
376
377
|
*/
|
|
377
378
|
export interface NavigationStore {
|
|
378
|
-
getState(): NavigationState;
|
|
379
|
-
setState(partial: Partial<NavigationState>): void;
|
|
380
|
-
subscribe(listener: StateListener): () => void;
|
|
381
|
-
addInflightAction(action: InflightAction): void;
|
|
382
|
-
removeInflightAction(id: string): void;
|
|
383
|
-
isActionInProgress(): boolean;
|
|
384
|
-
setActionInProgress(value: boolean): void;
|
|
385
379
|
getSegmentState(): SegmentState;
|
|
386
380
|
setPath(path: string): void;
|
|
387
381
|
setCurrentUrl(url: string): void;
|
|
@@ -390,8 +384,6 @@ export interface NavigationStore {
|
|
|
390
384
|
setHistoryKey(key: string): void;
|
|
391
385
|
/** Monotonic token of the most recently committed navigation. */
|
|
392
386
|
getNavInstance(): number;
|
|
393
|
-
/** Nav-instance token recorded on a cache entry (undefined if absent). */
|
|
394
|
-
getCacheEntryInstance(historyKey: string): number | undefined;
|
|
395
387
|
cacheSegmentsForHistory(historyKey: string, segments: ResolvedSegment[], handleData?: HandleData): void;
|
|
396
388
|
getCachedSegments(historyKey: string): {
|
|
397
389
|
segments: ResolvedSegment[];
|
|
@@ -420,7 +412,6 @@ export interface NavigationStore {
|
|
|
420
412
|
* probe and write into one historyCache scan for the per-yield streaming path.
|
|
421
413
|
*/
|
|
422
414
|
updateCacheHandleDataIfOwned(historyKey: string, handleData: HandleData, ownerInstance: number, stale?: boolean, handlesPending?: boolean): void;
|
|
423
|
-
markCacheAsStale(): void;
|
|
424
415
|
markHistoryCacheStale(): void;
|
|
425
416
|
markCacheAsStaleAndBroadcast(): void;
|
|
426
417
|
clearHistoryCache(): void;
|
|
@@ -431,9 +422,6 @@ export interface NavigationStore {
|
|
|
431
422
|
setRouterId?(id: string): void;
|
|
432
423
|
onUpdate(callback: UpdateSubscriber): () => void;
|
|
433
424
|
emitUpdate(update: NavigationUpdate): void;
|
|
434
|
-
getActionState(actionId: string): TrackedActionState;
|
|
435
|
-
setActionState(actionId: string, state: Partial<TrackedActionState>): void;
|
|
436
|
-
subscribeToAction(actionId: string, listener: ActionStateListener): () => void;
|
|
437
425
|
}
|
|
438
426
|
/**
|
|
439
427
|
* Options for partial navigation fetch
|
|
@@ -79,6 +79,12 @@ export declare function generateManifest<TEnv>(urlpatterns: UrlPatterns<TEnv, an
|
|
|
79
79
|
*/
|
|
80
80
|
export declare function generateManifestFull<TEnv>(urlpatterns: UrlPatterns<TEnv, any>, mountIndex?: number, options?: {
|
|
81
81
|
urlPrefix?: string;
|
|
82
|
+
/**
|
|
83
|
+
* Owning router id. Threaded into the evaluation store so path() scopes
|
|
84
|
+
* its search-schema/root-scope registrations per router — same-named
|
|
85
|
+
* routes in different routers must not clobber each other.
|
|
86
|
+
*/
|
|
87
|
+
routerId?: string;
|
|
82
88
|
/**
|
|
83
89
|
* Called once per `"use client"` component registered as an
|
|
84
90
|
* errorBoundary/notFoundBoundary fallback, with its client-reference key
|
|
@@ -30,11 +30,12 @@ export declare function resolveCacheTags(config: PartialCacheOptions | false, ct
|
|
|
30
30
|
* 4. Hardcoded fallback (60 seconds)
|
|
31
31
|
*/
|
|
32
32
|
export declare class CacheScope {
|
|
33
|
+
private readonly defaultKeyPrefix?;
|
|
33
34
|
readonly config: PartialCacheOptions | false;
|
|
34
35
|
readonly parent: CacheScope | null;
|
|
35
36
|
/** Explicit store from cache() options, if specified */
|
|
36
37
|
private readonly explicitStore;
|
|
37
|
-
constructor(config: PartialCacheOptions | false, parent?: CacheScope | null);
|
|
38
|
+
constructor(config: PartialCacheOptions | false, parent?: CacheScope | null, defaultKeyPrefix?: "doc" | undefined);
|
|
38
39
|
/**
|
|
39
40
|
* Whether caching is enabled for this scope
|
|
40
41
|
*/
|
|
@@ -121,10 +122,11 @@ export declare function createCacheScope(config: {
|
|
|
121
122
|
/**
|
|
122
123
|
* Shell fast path: when the route tree derived NO cache scope and the current
|
|
123
124
|
* request context carries the `_shellImplicitCache` marker (a shell capture,
|
|
124
|
-
*
|
|
125
|
-
* scope so withCacheLookup/withCacheStore treat the WHOLE
|
|
126
|
-
* cache() boundary — the shell entry IS a cache() of the
|
|
127
|
-
* loaders as the live carve-outs
|
|
125
|
+
* an eligible HIT tail, or a normal partial navigation replay), substitute an
|
|
126
|
+
* implicit doc-level scope so withCacheLookup/withCacheStore treat the WHOLE
|
|
127
|
+
* matched route as a cache() boundary — the shell entry IS a cache() of the
|
|
128
|
+
* handler layer, with loaders as the live carve-outs
|
|
129
|
+
* (resolveFreshLoadersAndYield).
|
|
128
130
|
*
|
|
129
131
|
* An existing scope — including an explicit cache(false) opt-out — always
|
|
130
132
|
* wins: the consumer's cache() semantics (their ttl/swr/store/condition) are
|
|
@@ -29,6 +29,7 @@ export { CACHE_STALE_AT_HEADER, CACHE_STATUS_HEADER, CACHE_TAGS_HEADER, CACHE_TA
|
|
|
29
29
|
import type { KVNamespace, CFCacheReadDebugEvent, CFCacheDebug, CFCacheStoreOptions } from "./cf-cache-types.js";
|
|
30
30
|
export type { KVNamespace, CFCacheReadDebugEvent, CFCacheDebug, CFCacheStoreOptions, };
|
|
31
31
|
export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
32
|
+
readonly supportsPassiveShellReads: true;
|
|
32
33
|
readonly defaults?: CacheDefaults;
|
|
33
34
|
readonly keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
|
|
34
35
|
private readonly namespace?;
|
|
@@ -265,7 +266,7 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
|
|
|
265
266
|
* Get a cached PPR shell entry by key from KV (no L1). Applies the KV read
|
|
266
267
|
* budget, corrupt-entry eviction, hard-expiry, and tag invalidation exactly
|
|
267
268
|
* like kvGetItem, minus the L1 promote. SWR is a plain staleness flag — KV has
|
|
268
|
-
* no REVALIDATING herd guard, so the
|
|
269
|
+
* no REVALIDATING herd guard, so the capture scheduler's module-level
|
|
269
270
|
* in-flight set is the recapture stampede guard.
|
|
270
271
|
*/
|
|
271
272
|
getShell(key: string): Promise<{
|
|
@@ -273,11 +274,12 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
|
|
|
273
274
|
shouldRevalidate?: boolean;
|
|
274
275
|
} | null>;
|
|
275
276
|
/**
|
|
276
|
-
* Store a PPR shell entry in KV with TTL and optional SWR window.
|
|
277
|
-
*
|
|
277
|
+
* Store a PPR shell entry in KV with TTL and optional SWR window. The write is
|
|
278
|
+
* registered with waitUntil and awaited so invalidation rejection can be
|
|
279
|
+
* acknowledged to the capture scheduler. The tags/taggedAt ride in the envelope
|
|
278
280
|
* so isGloballyInvalidated() can invalidate the shell via the shared KV markers.
|
|
279
281
|
*/
|
|
280
|
-
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<void>;
|
|
282
|
+
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
|
|
281
283
|
/**
|
|
282
284
|
* Convert string key to Request object for CF Cache API.
|
|
283
285
|
* Includes version in URL if specified (for cache invalidation on code changes).
|
|
@@ -537,11 +539,10 @@ export declare class CFCacheStore<TEnv = unknown> implements SegmentCacheStore<T
|
|
|
537
539
|
* eager purge still fires for the whole batch first (it is additive).
|
|
538
540
|
*/
|
|
539
541
|
/**
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
544
|
-
* envelope check (identical semantics: marker >= since, fail open).
|
|
542
|
+
* Shell tag-generation gate (SegmentCacheStore.isTagsInvalidatedSince): the
|
|
543
|
+
* SAME KV markers used by runtime envelopes also evict immutable build shells
|
|
544
|
+
* and captures whose write races updateTag(). Thin public wrapper over the
|
|
545
|
+
* private envelope check (marker >= since, fail open).
|
|
545
546
|
*/
|
|
546
547
|
isTagsInvalidatedSince(tags: string[], sinceMs: number): Promise<boolean>;
|
|
547
548
|
invalidateTags(tags: string[]): Promise<void>;
|
|
@@ -106,6 +106,7 @@ export interface MemorySegmentCacheStoreOptions<TEnv = unknown> {
|
|
|
106
106
|
* ```
|
|
107
107
|
*/
|
|
108
108
|
export declare class MemorySegmentCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
109
|
+
readonly supportsPassiveShellReads: true;
|
|
109
110
|
private cache;
|
|
110
111
|
private responseCache;
|
|
111
112
|
private itemCache;
|
|
@@ -115,11 +116,9 @@ export declare class MemorySegmentCacheStore<TEnv = unknown> implements SegmentC
|
|
|
115
116
|
/** prefixed cache key -> set of tags (reverse index for O(tags) unregister) */
|
|
116
117
|
private keyTags;
|
|
117
118
|
/**
|
|
118
|
-
* tag -> epoch ms of its latest invalidateTags() call.
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* these markers against the entry's build-time createdAt. Per-isolate,
|
|
122
|
-
* like every other map here — matching this store's tag semantics.
|
|
119
|
+
* tag -> epoch ms of its latest invalidateTags() call. Runtime capture races
|
|
120
|
+
* and immutable build shells are gated by comparing these markers with the
|
|
121
|
+
* shell generation start. Per-isolate, like every other map here.
|
|
123
122
|
*/
|
|
124
123
|
private tagInvalidatedAt;
|
|
125
124
|
readonly defaults?: CacheDefaults;
|
|
@@ -150,8 +149,9 @@ export declare class MemorySegmentCacheStore<TEnv = unknown> implements SegmentC
|
|
|
150
149
|
entry: ShellCacheEntry;
|
|
151
150
|
shouldRevalidate?: boolean;
|
|
152
151
|
} | null>;
|
|
153
|
-
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<
|
|
152
|
+
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated">;
|
|
154
153
|
isTagsInvalidatedSince(tags: string[], sinceMs: number): Promise<boolean>;
|
|
154
|
+
private tagsInvalidatedSince;
|
|
155
155
|
invalidateTags(tags: string[]): Promise<void>;
|
|
156
156
|
private registerTags;
|
|
157
157
|
private unregisterTags;
|
|
@@ -47,6 +47,7 @@ export declare class RecordingShellStore<TEnv = unknown> implements SegmentCache
|
|
|
47
47
|
constructor(inner: SegmentCacheStore<TEnv>);
|
|
48
48
|
get defaults(): SegmentCacheStore<TEnv>["defaults"];
|
|
49
49
|
get keyGenerator(): SegmentCacheStore<TEnv>["keyGenerator"];
|
|
50
|
+
get supportsPassiveShellReads(): true | undefined;
|
|
50
51
|
private record;
|
|
51
52
|
/** Track a deferred cache-write promise so the capture can await it pre-drain. */
|
|
52
53
|
trackWrite(p: Promise<unknown>): void;
|
|
@@ -83,11 +84,13 @@ export declare class RecordingShellStore<TEnv = unknown> implements SegmentCache
|
|
|
83
84
|
putResponse(key: string, response: Response, ttl: number, swr?: number, tags?: string[]): Promise<void>;
|
|
84
85
|
getItem(key: string): Promise<CacheItemResult | null>;
|
|
85
86
|
setItem(key: string, value: string, options?: CacheItemOptions): Promise<void>;
|
|
86
|
-
getShell(key: string
|
|
87
|
+
getShell(key: string, options?: {
|
|
88
|
+
claimRevalidation?: boolean;
|
|
89
|
+
}): Promise<{
|
|
87
90
|
entry: ShellCacheEntry;
|
|
88
91
|
shouldRevalidate?: boolean;
|
|
89
92
|
} | null>;
|
|
90
|
-
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<void>;
|
|
93
|
+
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
|
|
91
94
|
invalidateTags(tags: string[]): Promise<void>;
|
|
92
95
|
}
|
|
93
96
|
/** True iff `store` is a RecordingShellStore (duck-typed across module copies). */
|
|
@@ -141,18 +144,26 @@ export declare function buildShellLoaderSeed(snapshot: ShellSnapshotRecord[]): P
|
|
|
141
144
|
* the snapshot it serves the recorded value AS FRESH (shouldRevalidate: false —
|
|
142
145
|
* a pinned key must NOT kick SWR background revalidation) so the tail's payload
|
|
143
146
|
* matches the frozen prelude. Every other read falls through to the real store
|
|
144
|
-
* (the holes — masked loaders were never recorded — stay live).
|
|
145
|
-
* through
|
|
146
|
-
* family
|
|
147
|
+
* (the holes — masked loaders were never recorded — stay live). Writes pass
|
|
148
|
+
* through so a live hole may legitimately write, except that `segmentsOnly`
|
|
149
|
+
* fully isolates the segment family: misses do not fall through, and writes or
|
|
150
|
+
* deletes stay local to the navigation overlay. The shell family always passes
|
|
151
|
+
* through. With `segmentsOnly`, item and response reads also pass through so a
|
|
152
|
+
* partial navigation cannot freeze captured data or write a partial result into
|
|
153
|
+
* the canonical document namespace.
|
|
147
154
|
*/
|
|
148
155
|
export declare class SeededShellStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
149
156
|
private readonly inner;
|
|
150
157
|
private readonly items;
|
|
151
158
|
private readonly segments;
|
|
152
159
|
private readonly responses;
|
|
153
|
-
|
|
160
|
+
private readonly segmentsOnly;
|
|
161
|
+
constructor(inner: SegmentCacheStore<TEnv>, snapshot: ShellSnapshotRecord[], options?: {
|
|
162
|
+
segmentsOnly?: boolean;
|
|
163
|
+
});
|
|
154
164
|
get defaults(): SegmentCacheStore<TEnv>["defaults"];
|
|
155
165
|
get keyGenerator(): SegmentCacheStore<TEnv>["keyGenerator"];
|
|
166
|
+
get supportsPassiveShellReads(): true | undefined;
|
|
156
167
|
get(key: string): Promise<CacheGetResult | null>;
|
|
157
168
|
set(key: string, data: CachedEntryData, ttl: number, swr?: number): Promise<void>;
|
|
158
169
|
delete(key: string): Promise<boolean>;
|
|
@@ -164,10 +175,12 @@ export declare class SeededShellStore<TEnv = unknown> implements SegmentCacheSto
|
|
|
164
175
|
putResponse(key: string, response: Response, ttl: number, swr?: number, tags?: string[]): Promise<void>;
|
|
165
176
|
getItem(key: string): Promise<CacheItemResult | null>;
|
|
166
177
|
setItem(key: string, value: string, options?: CacheItemOptions): Promise<void>;
|
|
167
|
-
getShell(key: string
|
|
178
|
+
getShell(key: string, options?: {
|
|
179
|
+
claimRevalidation?: boolean;
|
|
180
|
+
}): Promise<{
|
|
168
181
|
entry: ShellCacheEntry;
|
|
169
182
|
shouldRevalidate?: boolean;
|
|
170
183
|
} | null>;
|
|
171
|
-
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<void>;
|
|
184
|
+
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
|
|
172
185
|
invalidateTags(tags: string[]): Promise<void>;
|
|
173
186
|
}
|
|
@@ -33,6 +33,12 @@ export interface CacheGetResult {
|
|
|
33
33
|
* @typeParam TEnv - Platform bindings type (e.g., Cloudflare env)
|
|
34
34
|
*/
|
|
35
35
|
export interface SegmentCacheStore<TEnv = unknown> {
|
|
36
|
+
/**
|
|
37
|
+
* The store honors getShell(..., { claimRevalidation: false }) without
|
|
38
|
+
* claiming an SWR lock. Navigation replay requires this opt-in because it
|
|
39
|
+
* cannot recapture an HTML shell after observing a stale entry.
|
|
40
|
+
*/
|
|
41
|
+
readonly supportsPassiveShellReads?: true;
|
|
36
42
|
/**
|
|
37
43
|
* Default cache options for this store.
|
|
38
44
|
* Used by cache() boundaries when ttl/swr are not explicitly specified.
|
|
@@ -120,8 +126,12 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
120
126
|
*
|
|
121
127
|
* Optional: a store that does not implement the shell family disables the
|
|
122
128
|
* shell-cache middleware (it fails open to the normal HTML render path).
|
|
129
|
+
* A passive read reports a stale entry as `shouldRevalidate: true` without
|
|
130
|
+
* claiming store-specific revalidation ownership.
|
|
123
131
|
*/
|
|
124
|
-
getShell?(key: string
|
|
132
|
+
getShell?(key: string, options?: {
|
|
133
|
+
claimRevalidation?: boolean;
|
|
134
|
+
}): Promise<{
|
|
125
135
|
entry: ShellCacheEntry;
|
|
126
136
|
shouldRevalidate?: boolean;
|
|
127
137
|
} | null>;
|
|
@@ -136,8 +146,10 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
136
146
|
* @param swrSeconds - Optional stale-while-revalidate window in seconds
|
|
137
147
|
* @param tags - Optional cache tags for invalidation (participates in
|
|
138
148
|
* invalidateTags via the same tag machinery as the item family)
|
|
149
|
+
* @returns `invalidated` when a generation marker rejected the write,
|
|
150
|
+
* `stored` when acknowledged, or void for stores without acknowledgements.
|
|
139
151
|
*/
|
|
140
|
-
putShell?(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<void>;
|
|
152
|
+
putShell?(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
|
|
141
153
|
/**
|
|
142
154
|
* Get a cached function result by key.
|
|
143
155
|
* Returns the serialized value, optional handle data, and staleness flag.
|
|
@@ -162,10 +174,9 @@ export interface SegmentCacheStore<TEnv = unknown> {
|
|
|
162
174
|
/**
|
|
163
175
|
* True when ANY of `tags` was invalidated (invalidateTags/updateTag) at or
|
|
164
176
|
* after `sinceMs` (>= so a same-millisecond invalidation wins, favouring
|
|
165
|
-
* freshness). Consulted by
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
* markers against the entry's createdAt rather than by deleting anything.
|
|
177
|
+
* freshness). Consulted by build-shell read-through and runtime shell stores:
|
|
178
|
+
* "was it evicted" is answered by tag markers against the entry's createdAt,
|
|
179
|
+
* including when invalidation races a capture that has not been written yet.
|
|
169
180
|
* Optional: without it, TAGGED build entries are not served (untagged ones
|
|
170
181
|
* are unaffected — they are evictable only by deploy/buildVersion anyway).
|
|
171
182
|
* Fail open to `false` on marker-read errors: a transient store fault must
|
|
@@ -267,7 +278,13 @@ export interface ShellCacheEntry {
|
|
|
267
278
|
* their holes always fill.
|
|
268
279
|
*/
|
|
269
280
|
handlerLiveHoles?: boolean;
|
|
270
|
-
/**
|
|
281
|
+
/**
|
|
282
|
+
* The capture encountered transition({ when }). Its effective hold policy is
|
|
283
|
+
* request-dependent, so handler-free document and navigation replay must
|
|
284
|
+
* re-run resolution to collect and evaluate the predicate.
|
|
285
|
+
*/
|
|
286
|
+
transitionWhen?: true;
|
|
287
|
+
/** Capture-generation start time; tag invalidations at or after it win. */
|
|
271
288
|
createdAt: number;
|
|
272
289
|
}
|
|
273
290
|
/**
|
|
@@ -184,6 +184,7 @@ export interface VercelCacheStoreOptions<TEnv = unknown> {
|
|
|
184
184
|
* ```
|
|
185
185
|
*/
|
|
186
186
|
export declare class VercelCacheStore<TEnv = unknown> implements SegmentCacheStore<TEnv> {
|
|
187
|
+
readonly supportsPassiveShellReads: true;
|
|
187
188
|
readonly defaults?: CacheDefaults;
|
|
188
189
|
readonly keyGenerator?: (ctx: RequestContext<TEnv>, defaultKey: string) => string | Promise<string>;
|
|
189
190
|
private readonly cache;
|
|
@@ -203,18 +204,18 @@ export declare class VercelCacheStore<TEnv = unknown> implements SegmentCacheSto
|
|
|
203
204
|
putResponse(key: string, response: Response, ttl: number, swr?: number, tags?: string[]): Promise<void>;
|
|
204
205
|
getItem(key: string): Promise<CacheItemResult | null>;
|
|
205
206
|
setItem(key: string, value: string, options?: CacheItemOptions): Promise<void>;
|
|
206
|
-
getShell(key: string
|
|
207
|
+
getShell(key: string, options?: {
|
|
208
|
+
claimRevalidation?: boolean;
|
|
209
|
+
}): Promise<{
|
|
207
210
|
entry: ShellCacheEntry;
|
|
208
211
|
shouldRevalidate?: boolean;
|
|
209
212
|
} | null>;
|
|
210
|
-
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<void>;
|
|
213
|
+
putShell(key: string, entry: ShellCacheEntry, ttlSeconds?: number, swrSeconds?: number, tags?: string[]): Promise<"stored" | "invalidated" | void>;
|
|
211
214
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
* (>= so a same-millisecond invalidation wins). Fails open to `false` on
|
|
217
|
-
* read errors — the same posture as the CF marker check.
|
|
215
|
+
* Shell tag-generation gate (SegmentCacheStore.isTagsInvalidatedSince).
|
|
216
|
+
* expireTag() keeps no queryable history, so invalidateTags() writes "tm"
|
|
217
|
+
* markers for runtime capture races and immutable build shells. Marker >=
|
|
218
|
+
* generation start wins; read errors fail open like the CF marker check.
|
|
218
219
|
*/
|
|
219
220
|
isTagsInvalidatedSince(tags: string[], sinceMs: number): Promise<boolean>;
|
|
220
221
|
invalidateTags(tags: string[]): Promise<void>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const DEV_DISCOVERY_READY_EVENT = "rango:dev-discovery-ready";
|
|
2
|
+
export declare const DEV_DISCOVERY_QUERY_EVENT = "rango:dev-discovery-query";
|
|
3
|
+
export declare const DEV_DISCOVERY_PROBE_HEADER = "x-rango-dev-discovery-probe";
|
|
4
|
+
export declare const DEV_DISCOVERY_EPOCH_HEADER = "x-rango-dev-discovery-epoch";
|
|
5
|
+
export declare function isValidDevDiscoveryEpoch(value: unknown): value is number;
|
|
@@ -7,10 +7,6 @@
|
|
|
7
7
|
*
|
|
8
8
|
* See docs/manifests.md for the full data flow.
|
|
9
9
|
*/
|
|
10
|
-
declare let cachedPrecomputedEntries: Array<{
|
|
11
|
-
staticPrefix: string;
|
|
12
|
-
routes: Record<string, string>;
|
|
13
|
-
}> | null;
|
|
14
10
|
/**
|
|
15
11
|
* Register routes into the global route map.
|
|
16
12
|
* Routes are merged with any existing registered routes.
|
|
@@ -63,26 +59,6 @@ export declare function hasCachedManifest(): boolean;
|
|
|
63
59
|
* Clear the cached manifest (for testing)
|
|
64
60
|
*/
|
|
65
61
|
export declare function clearCachedManifest(): void;
|
|
66
|
-
/**
|
|
67
|
-
* Set pre-computed route entries from build-time data.
|
|
68
|
-
*
|
|
69
|
-
* Each entry corresponds to a leaf node in the prefix tree (no nested includes).
|
|
70
|
-
* evaluateLazyEntry() checks these before running the handler, avoiding the
|
|
71
|
-
* 5-50ms cost of handler evaluation for route matching on the first request.
|
|
72
|
-
*
|
|
73
|
-
* @param entries - Array of { staticPrefix, routes } from build-time prefix tree leaves
|
|
74
|
-
*/
|
|
75
|
-
export declare function setPrecomputedEntries(entries: Array<{
|
|
76
|
-
staticPrefix: string;
|
|
77
|
-
routes: Record<string, string>;
|
|
78
|
-
}> | null): void;
|
|
79
|
-
/**
|
|
80
|
-
* Get pre-computed route entries (if available)
|
|
81
|
-
*/
|
|
82
|
-
export declare function getPrecomputedEntries(): typeof cachedPrecomputedEntries;
|
|
83
|
-
declare let cachedRouteTrie: import("./build/route-trie.js").TrieNode | null;
|
|
84
|
-
export declare function setRouteTrie(trie: typeof cachedRouteTrie): void;
|
|
85
|
-
export declare function getRouteTrie(): typeof cachedRouteTrie;
|
|
86
62
|
/**
|
|
87
63
|
* Clear all cached route data (global and per-router).
|
|
88
64
|
* Called during HMR when route definitions change so the handler rebuilds
|
|
@@ -115,13 +91,12 @@ export declare function waitForManifestReady(): Promise<void> | null;
|
|
|
115
91
|
* Register whether a route is at root scope.
|
|
116
92
|
* Called by path() during route evaluation.
|
|
117
93
|
*/
|
|
118
|
-
export declare function registerRouteRootScope(routeName: string, rootScoped: boolean): void;
|
|
94
|
+
export declare function registerRouteRootScope(routeName: string, rootScoped: boolean, routerId?: string): void;
|
|
119
95
|
/**
|
|
120
96
|
* Check if a route is at root scope.
|
|
121
97
|
* Returns undefined if the route has not been registered (e.g. in unit tests).
|
|
122
98
|
*/
|
|
123
|
-
export declare function isRouteRootScoped(routeName: string): boolean | undefined;
|
|
99
|
+
export declare function isRouteRootScoped(routeName: string, routerId?: string): boolean | undefined;
|
|
124
100
|
import type { SearchSchema } from "./search-params.js";
|
|
125
|
-
export declare function registerSearchSchema(routeName: string, schema: SearchSchema): void;
|
|
126
|
-
export declare function getSearchSchema(routeName: string): SearchSchema | undefined;
|
|
127
|
-
export {};
|
|
101
|
+
export declare function registerSearchSchema(routeName: string, schema: SearchSchema, routerId?: string): void;
|
|
102
|
+
export declare function getSearchSchema(routeName: string, routerId?: string): SearchSchema | undefined;
|
|
@@ -6,6 +6,9 @@ import type { HandlerContext, ResolvedSegment } from "../types";
|
|
|
6
6
|
import type { SerializedSegmentData } from "../cache/types.js";
|
|
7
7
|
import type { RouteMatchResult } from "./pattern-matching.js";
|
|
8
8
|
export interface PrerenderMatchDeps<TEnv = any> {
|
|
9
|
+
/** Owning router id; scopes bake-time root-scope/search-schema lookups the
|
|
10
|
+
* same way reqCtx._routerId does at runtime (issue #762). */
|
|
11
|
+
routerId?: string;
|
|
9
12
|
findMatch: (pathname: string) => RouteMatchResult<TEnv> | null | Promise<RouteMatchResult<TEnv> | null>;
|
|
10
13
|
buildRouterContext: () => RouterContext<TEnv>;
|
|
11
14
|
mergedRouteMap: Record<string, string>;
|
|
@@ -44,7 +47,7 @@ devMode?: boolean): Promise<{
|
|
|
44
47
|
* the component. Returns the encoded Flight string (or null on failure).
|
|
45
48
|
* Used by the Vite plugin to collect static segment data at build time.
|
|
46
49
|
*/
|
|
47
|
-
export declare function renderStaticSegment<TEnv = any>(handler: Function, handlerId: string, mergedRouteMap: Record<string, string>, routeName?: string, buildEnv?: TEnv, devMode?: boolean): Promise<{
|
|
50
|
+
export declare function renderStaticSegment<TEnv = any>(handler: Function, handlerId: string, mergedRouteMap: Record<string, string>, routeName?: string, buildEnv?: TEnv, devMode?: boolean, routerId?: string, rootScoped?: boolean): Promise<{
|
|
48
51
|
encoded: string;
|
|
49
52
|
handles: string;
|
|
50
53
|
} | null>;
|
|
@@ -333,6 +333,8 @@ export interface RangoInternal<TEnv = any, TRoutes extends Record<string, unknow
|
|
|
333
333
|
readonly __sourceFile?: string;
|
|
334
334
|
/** @internal basename for runtime manifest generation */
|
|
335
335
|
readonly __basename?: string;
|
|
336
|
+
/** @internal Cloudflare dev worker generation captured at construction. */
|
|
337
|
+
readonly __devDiscoveryEpoch?: number;
|
|
336
338
|
/**
|
|
337
339
|
* @internal Router-level error/notFound fallbacks (`createRouter` options),
|
|
338
340
|
* exposed for the build-time clientChunks discovery so a `"use client"`
|
|
@@ -364,7 +366,7 @@ export interface RangoInternal<TEnv = any, TRoutes extends Record<string, unknow
|
|
|
364
366
|
* Render a single Static handler at build time.
|
|
365
367
|
* Returns the RSC-serialized component string and handle data, or null on failure.
|
|
366
368
|
*/
|
|
367
|
-
renderStaticSegment(handler: Function, handlerId: string, routeName?: string, buildEnv?: any, devMode?: boolean): Promise<{
|
|
369
|
+
renderStaticSegment(handler: Function, handlerId: string, routeName?: string, buildEnv?: any, devMode?: boolean, rootScoped?: boolean): Promise<{
|
|
368
370
|
encoded: string;
|
|
369
371
|
handles: string;
|
|
370
372
|
} | null>;
|
|
@@ -13,6 +13,7 @@ import type { SSRStreamMode } from "../router/router-options.js";
|
|
|
13
13
|
export interface HandlerContext<TEnv = unknown> {
|
|
14
14
|
router: RangoInternal<TEnv, any>;
|
|
15
15
|
version: string;
|
|
16
|
+
devDiscoveryEpoch?: number;
|
|
16
17
|
renderToReadableStream: RSCDependencies["renderToReadableStream"];
|
|
17
18
|
decodeReply: RSCDependencies["decodeReply"];
|
|
18
19
|
createTemporaryReferenceSet: RSCDependencies["createTemporaryReferenceSet"];
|
|
@@ -39,8 +39,9 @@ export interface RscRenderStageTracking {
|
|
|
39
39
|
/** Whole-render span receiving low-cardinality current/error phase attrs. */
|
|
40
40
|
span?: TraceSpan;
|
|
41
41
|
}
|
|
42
|
+
type RscRenderContext<TEnv> = Pick<HandlerContext<TEnv>, "renderToReadableStream" | "callOnError" | "devDiscoveryEpoch">;
|
|
42
43
|
export interface RscRenderInput<TEnv> {
|
|
43
|
-
ctx:
|
|
44
|
+
ctx: RscRenderContext<TEnv>;
|
|
44
45
|
request: Request;
|
|
45
46
|
url: URL;
|
|
46
47
|
env: TEnv;
|
|
@@ -51,7 +52,7 @@ export interface RscRenderInput<TEnv> {
|
|
|
51
52
|
tracking?: RscRenderStageTracking;
|
|
52
53
|
}
|
|
53
54
|
export interface RscFlightStageInput<TEnv> {
|
|
54
|
-
ctx:
|
|
55
|
+
ctx: RscRenderContext<TEnv>;
|
|
55
56
|
request: Request;
|
|
56
57
|
url: URL;
|
|
57
58
|
env: TEnv;
|
|
@@ -9,15 +9,10 @@ import { getRequestContext } from "../server/request-context.js";
|
|
|
9
9
|
import type { HandlerContext } from "./handler-context.js";
|
|
10
10
|
export declare function handleRscRendering<TEnv>(ctx: HandlerContext<TEnv>, request: Request, env: TEnv, url: URL, isPartial: boolean, handleStore: ReturnType<typeof getRequestContext>["_handleStore"], nonce: string | undefined): Promise<Response>;
|
|
11
11
|
/**
|
|
12
|
-
* Neutralize the shell-HIT degradation redirect target.
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* + Location responses, never a committed 200 body). So it reuses the ONE
|
|
18
|
-
* same-origin resolver directly: a cross-origin/unparseable/unsafe target
|
|
19
|
-
* neutralizes to the same safe same-origin landing as redirect-guard.ts
|
|
20
|
-
* (basename root, or "/" when unset) rather than navigating the user off-host.
|
|
21
|
-
* A safe same-origin/relative target passes through as its normalized href.
|
|
12
|
+
* Neutralize the shell-HIT degradation redirect target. The inline
|
|
13
|
+
* `location.replace` in a committed 200 body bypasses the 3xx chokepoint
|
|
14
|
+
* (guardOutgoingRedirect only sees 3xx + Location), so this reuses the same
|
|
15
|
+
* same-origin resolver directly: unsafe targets neutralize to the
|
|
16
|
+
* redirect-guard.ts landing instead of navigating the user off-host.
|
|
22
17
|
*/
|
|
23
18
|
export declare function resolveShellHitRedirectTarget(rawTarget: string, requestOrigin: string, basename: string | undefined): string;
|