@rangojs/router 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +2 -1
  2. package/dist/bin/rango.js +11 -1
  3. package/dist/types/browser/dev-discovery.d.ts +11 -0
  4. package/dist/types/browser/navigation-store.d.ts +3 -13
  5. package/dist/types/browser/prefetch/loader.d.ts +10 -0
  6. package/dist/types/browser/prefetch/runtime.d.ts +3 -0
  7. package/dist/types/browser/types.d.ts +4 -16
  8. package/dist/types/build/generate-manifest.d.ts +6 -0
  9. package/dist/types/cache/cache-scope.d.ts +7 -5
  10. package/dist/types/cache/cf/cf-cache-store.d.ts +10 -9
  11. package/dist/types/cache/document-cache.d.ts +3 -1
  12. package/dist/types/cache/memory-segment-store.d.ts +6 -6
  13. package/dist/types/cache/shell-snapshot.d.ts +21 -8
  14. package/dist/types/cache/types.d.ts +25 -7
  15. package/dist/types/cache/vercel/vercel-cache-store.d.ts +9 -8
  16. package/dist/types/dev-discovery-protocol.d.ts +5 -0
  17. package/dist/types/prerender/store.d.ts +1 -0
  18. package/dist/types/route-definition/helpers-types.d.ts +6 -6
  19. package/dist/types/route-map-builder.d.ts +4 -29
  20. package/dist/types/router/match-handlers.d.ts +2 -3
  21. package/dist/types/router/prerender-match.d.ts +4 -1
  22. package/dist/types/router/router-interfaces.d.ts +3 -1
  23. package/dist/types/router/segment-resolution/view-transition-default.d.ts +3 -4
  24. package/dist/types/router/transition-when.d.ts +13 -0
  25. package/dist/types/rsc/handler-context.d.ts +1 -0
  26. package/dist/types/rsc/render-pipeline.d.ts +3 -2
  27. package/dist/types/rsc/rsc-rendering.d.ts +5 -10
  28. package/dist/types/rsc/shell-capture.d.ts +4 -8
  29. package/dist/types/rsc/shell-serve.d.ts +2 -0
  30. package/dist/types/rsc/transition-gate.d.ts +10 -14
  31. package/dist/types/rsc/types.d.ts +2 -0
  32. package/dist/types/server/context.d.ts +16 -0
  33. package/dist/types/server/request-context.d.ts +22 -8
  34. package/dist/types/server.d.ts +1 -1
  35. package/dist/types/testing/e2e/index.d.ts +1 -1
  36. package/dist/types/testing/index.d.ts +2 -2
  37. package/dist/types/testing/run-transition-when.d.ts +6 -5
  38. package/dist/types/testing/shell-status.d.ts +23 -3
  39. package/dist/types/types/route-entry.d.ts +3 -0
  40. package/dist/types/types/segments.d.ts +27 -22
  41. package/dist/types/urls/path-helper-types.d.ts +4 -3
  42. package/dist/types/vite/discovery/state.d.ts +3 -2
  43. package/dist/types/vite/utils/manifest-utils.d.ts +1 -1
  44. package/dist/vite/index.js +194 -107
  45. package/package.json +24 -20
  46. package/skills/cache-guide/SKILL.md +6 -3
  47. package/skills/caching/SKILL.md +1 -1
  48. package/skills/catalog.json +7 -1
  49. package/skills/deployment-caching/SKILL.md +176 -0
  50. package/skills/document-cache/SKILL.md +30 -3
  51. package/skills/ppr/SKILL.md +90 -14
  52. package/skills/prerender/SKILL.md +15 -8
  53. package/skills/rango/SKILL.md +20 -17
  54. package/skills/testing/SKILL.md +1 -1
  55. package/skills/testing/cache-prerender.md +5 -1
  56. package/skills/vercel/SKILL.md +22 -1
  57. package/skills/view-transitions/SKILL.md +12 -8
  58. package/src/browser/dev-discovery.ts +66 -0
  59. package/src/browser/navigation-client.ts +6 -0
  60. package/src/browser/navigation-store.ts +4 -271
  61. package/src/browser/partial-update.ts +7 -14
  62. package/src/browser/prefetch/cache.ts +1 -1
  63. package/src/browser/prefetch/loader.ts +110 -0
  64. package/src/browser/prefetch/runtime.ts +7 -0
  65. package/src/browser/react/Link.tsx +7 -10
  66. package/src/browser/react/NavigationProvider.tsx +1 -1
  67. package/src/browser/react/use-router.ts +1 -1
  68. package/src/browser/rsc-router.tsx +4 -2
  69. package/src/browser/types.ts +4 -27
  70. package/src/build/generate-manifest.ts +11 -0
  71. package/src/cache/cache-scope.ts +23 -7
  72. package/src/cache/cf/cf-cache-store.ts +63 -39
  73. package/src/cache/document-cache.ts +4 -2
  74. package/src/cache/memory-segment-store.ts +17 -6
  75. package/src/cache/shell-snapshot.ts +45 -15
  76. package/src/cache/types.ts +24 -6
  77. package/src/cache/vercel/vercel-cache-store.ts +52 -22
  78. package/src/dev-discovery-protocol.ts +11 -0
  79. package/src/prerender/build-shell-capture.ts +7 -1
  80. package/src/prerender/store.ts +5 -0
  81. package/src/route-definition/dsl-helpers.ts +7 -2
  82. package/src/route-definition/helpers-types.ts +6 -6
  83. package/src/route-map-builder.ts +56 -49
  84. package/src/router/handler-context.ts +13 -5
  85. package/src/router/lazy-includes.ts +1 -0
  86. package/src/router/match-api.ts +8 -4
  87. package/src/router/match-handlers.ts +44 -6
  88. package/src/router/match-middleware/cache-lookup.ts +10 -3
  89. package/src/router/prerender-match.ts +19 -2
  90. package/src/router/router-interfaces.ts +4 -0
  91. package/src/router/segment-resolution/static-store.ts +36 -10
  92. package/src/router/segment-resolution/view-transition-default.ts +9 -5
  93. package/src/router/transition-when.ts +76 -0
  94. package/src/router.ts +34 -3
  95. package/src/rsc/handler-context.ts +1 -0
  96. package/src/rsc/handler.ts +3 -1
  97. package/src/rsc/loader-fetch.ts +2 -2
  98. package/src/rsc/manifest-init.ts +4 -11
  99. package/src/rsc/progressive-enhancement.ts +9 -14
  100. package/src/rsc/render-pipeline.ts +10 -2
  101. package/src/rsc/rsc-rendering.ts +319 -147
  102. package/src/rsc/shell-capture.ts +34 -11
  103. package/src/rsc/shell-serve.ts +3 -0
  104. package/src/rsc/transition-gate.ts +37 -40
  105. package/src/rsc/types.ts +2 -0
  106. package/src/server/context.ts +28 -0
  107. package/src/server/request-context.ts +38 -10
  108. package/src/server.ts +0 -2
  109. package/src/testing/dispatch.ts +1 -0
  110. package/src/testing/e2e/index.ts +5 -0
  111. package/src/testing/index.ts +9 -1
  112. package/src/testing/run-transition-when.ts +42 -9
  113. package/src/testing/shell-status.ts +92 -3
  114. package/src/types/route-entry.ts +3 -0
  115. package/src/types/segments.ts +27 -22
  116. package/src/urls/include-helper.ts +1 -0
  117. package/src/urls/path-helper-types.ts +4 -3
  118. package/src/urls/path-helper.ts +8 -4
  119. package/src/vite/discovery/bundle-postprocess.ts +10 -7
  120. package/src/vite/discovery/discover-routers.ts +7 -66
  121. package/src/vite/discovery/prerender-collection.ts +13 -2
  122. package/src/vite/discovery/state.ts +4 -4
  123. package/src/vite/discovery/virtual-module-codegen.ts +75 -42
  124. package/src/vite/plugins/version-injector.ts +0 -1
  125. package/src/vite/plugins/virtual-entries.ts +11 -1
  126. package/src/vite/router-discovery.ts +132 -22
  127. package/src/vite/utils/manifest-utils.ts +1 -4
  128. package/src/vite/utils/shared-utils.ts +7 -0
@@ -20,9 +20,8 @@ import type { EntryData } from "../../server/context";
20
20
  * gate, which also avoids needless object allocation and payload growth.
21
21
  *
22
22
  * `when`: a server-only predicate. It is STRIPPED from the returned config (a
23
- * function cannot cross Flight or the segment cache) and recorded on the request
24
- * context keyed by `segmentId`, so rsc-rendering can evaluate it post-handler
25
- * outside any cache scope and drop this segment's transition when it returns
26
- * false. Used by both the fresh and revalidation resolution paths.
23
+ * function cannot cross Flight or the segment cache). PPR routes evaluate it
24
+ * from the manifest before the pipeline; other routes record it here for the
25
+ * existing post-handler gate. Used by both fresh and revalidation resolution.
27
26
  */
28
27
  export declare function applyViewTransitionDefault(transition: EntryData["transition"], viewTransitionDefault: "auto" | false | undefined, segmentId?: string): EntryData["transition"];
@@ -0,0 +1,13 @@
1
+ import { type EntryData } from "../server/context.js";
2
+ import type { RequestContext } from "../server/request-context.js";
3
+ import type { TransitionWhenContext } from "../types/segments.js";
4
+ export type TransitionWhenErrorReporter = (error: unknown, segmentId: string) => void;
5
+ export declare function createTransitionWhenContext<TEnv>(ctx: RequestContext<TEnv>, target?: {
6
+ params: Record<string, string>;
7
+ routeName?: string;
8
+ }): TransitionWhenContext<Record<string, string>, TEnv>;
9
+ /** Evaluate server-only transition gates before handlers on a PPR route. */
10
+ export declare function evaluatePprTransitionWhen<TEnv>(entries: EntryData[], ctx: RequestContext<TEnv>, target: {
11
+ params: Record<string, string>;
12
+ routeName?: string;
13
+ }, reportError: TransitionWhenErrorReporter): void;
@@ -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: Pick<HandlerContext<TEnv>, "renderToReadableStream" | "callOnError">;
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: Pick<HandlerContext<TEnv>, "renderToReadableStream" | "callOnError">;
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
- * The inline `location.replace` emitted by serveShellHit when a shell HIT lands
15
- * on a URL whose route became redirecting mid-TTL is a document-native redirect
16
- * exit that BYPASSES the 3xx chokepoint (guardOutgoingRedirect acts only on 3xx
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;
@@ -148,6 +148,8 @@ export interface ShellCaptureDebugEvent {
148
148
  snapshotBytes?: number;
149
149
  /** True when the snapshot exceeded maxSnapshotBytes and was dropped. */
150
150
  snapshotSkipped?: boolean;
151
+ /** Outcome reported by a store that supports shell-write acknowledgements. */
152
+ storeWrite?: "stored" | "invalidated";
151
153
  /** Consecutive failure count in the key's backoff entry, when one exists. */
152
154
  backoffFailures?: number;
153
155
  /** Ms remaining in the key's backoff window, when one exists. */
@@ -308,13 +310,7 @@ type CaptureAttemptOutcome = "stored" | "redirect" | "no-shell" | "refused";
308
310
  * bag, not a return value: captureAndStoreShell's outcome type stays a string
309
311
  * union its existing callers (producer B, tests) consume unchanged.
310
312
  */
311
- interface CaptureAttemptStats {
312
- barrierWaitMs?: number;
313
- writeSettleMs?: number;
314
- preludeBytes?: number;
315
- snapshotBytes?: number;
316
- snapshotSkipped?: boolean;
317
- }
313
+ type CaptureAttemptStats = Pick<ShellCaptureDebugEvent, "barrierWaitMs" | "writeSettleMs" | "preludeBytes" | "snapshotBytes" | "snapshotSkipped" | "storeWrite">;
318
314
  /**
319
315
  * Run the shell capture with a single in-place retry, then store the result.
320
316
  *
@@ -369,6 +365,6 @@ export declare function deriveShellCaptureContext(reqCtx: RequestContext<any>, d
369
365
  * retryable outcome; a genuine (non-abort) captureShellHTML error propagates so it
370
366
  * reaches reportCacheError and is NOT retried.
371
367
  */
372
- declare function captureAndStoreShell(ssrModule: SSRModule, rscStream: ReadableStream<Uint8Array>, handleStore: HandleStore, reqCtx: RequestContext<any>, capture: ShellCaptureDescriptor, stats?: CaptureAttemptStats): Promise<Exclude<CaptureAttemptOutcome, "redirect">>;
368
+ declare function captureAndStoreShell(ssrModule: SSRModule, rscStream: ReadableStream<Uint8Array>, handleStore: HandleStore, reqCtx: RequestContext<any>, capture: ShellCaptureDescriptor, stats?: CaptureAttemptStats, captureStartedAt?: number): Promise<Exclude<CaptureAttemptOutcome, "redirect">>;
373
369
  export { runShellCapture, captureAndStoreShell, delay, SHELL_CAPTURE_RETRY_DELAY_MS, };
374
370
  export { isCaptureBackedOff, markCaptureBackoff, clearCaptureBackoff, REFUSED_CAPTURE_BASE_MS, REFUSED_CAPTURE_MAX_MS, REFUSED_CAPTURE_DEV_MAX_MS, };
@@ -19,6 +19,8 @@ import { type EntryData } from "../server/context.js";
19
19
  import type { ShellCacheEntry, SegmentCacheStore } from "../cache/types.js";
20
20
  /** Debug/status header the browser (and e2e assertions) can read: HIT | MISS. */
21
21
  export declare const SHELL_STATUS_HEADER = "x-rango-shell";
22
+ /** Partial-navigation status header set only after captured PPR segments are consumed. */
23
+ export declare const PPR_REPLAY_STATUS_HEADER = "x-rango-ppr-replay";
22
24
  /**
23
25
  * Default shell ttl (seconds) for `ppr: true` and for a PartialPrerenderProps
24
26
  * that omits `ttl`.
@@ -4,24 +4,20 @@ import type { OnErrorCallback } from "../types/error-types.js";
4
4
  /**
5
5
  * Apply transition({ when }) gates to a payload's segments.
6
6
  *
7
- * The predicates were collected during resolution (keyed by segment id) and
8
- * stripped from the serialized config; here after handlers ran and outside any
9
- * cache scope we evaluate each and drop the segment's transition when the
10
- * predicate does not hold, so the navigation streams its loading fallback
11
- * instead of holding the previous content. A predicate that throws is reported
12
- * to the router's onError (phase "rendering") and then treated as "do not hold"
13
- * (conservative), so a buggy predicate degrades to no transition rather than
14
- * failing the response.
7
+ * PPR predicates were evaluated from the manifest before cache lookup and route
8
+ * handlers; their request-specific decisions are projected onto a copy so they
9
+ * never mutate reusable cache/prerender/shell records. Ordinary-route predicates
10
+ * were collected during fresh resolution and are evaluated here after handlers.
11
+ * Both paths drop the segment transition when the predicate does not hold.
15
12
  *
16
13
  * Mutating the segments here is safe: the segment cache stores a serialized copy
17
14
  * (segment-codec), written during match() BEFORE this gate runs, so dropping a
18
- * transition never corrupts a cache entry. The flip side is that a cache hit
15
+ * transition never corrupts a cache entry. On an ordinary route, a cache hit
19
16
  * skips resolution, collects no predicate, and replays the cached transition
20
- * as-is (it was serialized before the gate) combining transition({ when })
21
- * with cache() on the same segment freezes the gate to its cached state, so
22
- * avoid caching a route whose transition decision is request-dependent.
17
+ * as-is (it was serialized before the gate). PPR routes are the exception: their
18
+ * manifest predicates were evaluated before lookup and apply to replayed data.
23
19
  *
24
- * Returns the same array (mutated) for inline use at the payload's `segments`
25
- * field.
20
+ * Returns the same array for the ordinary post-handler path. A PPR drop returns a
21
+ * copy containing only the request-specific transition changes.
26
22
  */
27
23
  export declare function gateTransitions(segments: MatchResult["segments"], ctx: ReturnType<typeof getRequestContext>, onError?: OnErrorCallback): MatchResult["segments"];
@@ -41,6 +41,8 @@ export interface RscPayload {
41
41
  handles?: AsyncGenerator<HandleData, void, unknown>;
42
42
  /** RSC version string for cache invalidation */
43
43
  version?: string;
44
+ /** Cloudflare dev worker generation used for stale-document convergence. */
45
+ devDiscoveryEpoch?: number;
44
46
  /** TTL in milliseconds for the client-side in-memory prefetch cache */
45
47
  prefetchCacheTTL?: number;
46
48
  /** Max entries in the client-side in-memory prefetch cache (FIFO eviction) */
@@ -248,6 +248,10 @@ interface HelperContext {
248
248
  counters: Record<string, number>;
249
249
  forRoute?: string;
250
250
  mountIndex?: number;
251
+ /** Owning router id, when known (threaded by generateManifestFull). Scopes
252
+ * the search-schema/root-scope registries so same-named routes in
253
+ * different routers don't clobber each other (route-map-builder.ts). */
254
+ routerId?: string;
251
255
  metrics?: MetricsStore;
252
256
  /** True when rendering for SSR (document requests) */
253
257
  isSSR?: boolean;
@@ -337,6 +341,18 @@ export declare function getNamePrefix(): string | undefined;
337
341
  * Returns true at root or inside { name: "" } includes, false inside named includes.
338
342
  */
339
343
  export declare function getRootScoped(): boolean;
344
+ /**
345
+ * Stamp build-time scope identity onto a Static() definition at mount time.
346
+ * The bake collector (prerender-collection.ts) renders defs OUTSIDE any
347
+ * evaluation scope and used to iterate the registry first-non-null, so these
348
+ * are the ONLY reliable carriers of the def's owning router, root-scope, and
349
+ * full route name — a name-keyed registry lookup at bake time reproduces the
350
+ * cross-router collision #757/#762 fixed (and the collector's historical
351
+ * `$$routePrefix` argument is a name PREFIX, which the root-scope registry
352
+ * never contains, silently degrading to the dot-heuristic).
353
+ * A definition mounted more than once is stamped each time; the last mount wins.
354
+ */
355
+ export declare function stampStaticDefScope(handler: unknown, routeName?: string): void;
340
356
  export type { HelperContext };
341
357
  /**
342
358
  * Return an isolated copy of a lazy include's captured parent entry.
@@ -141,6 +141,8 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
141
141
  id: string;
142
142
  when: TransitionWhenFn;
143
143
  }>;
144
+ /** @internal PPR transition decisions evaluated before cache lookup/handlers. */
145
+ _pprTransitionDecisions?: Map<string, boolean>;
144
146
  /** @internal Cache store for segment caching (optional, used by CacheScope) */
145
147
  _cacheStore?: SegmentCacheStore;
146
148
  /**
@@ -180,21 +182,30 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
180
182
  */
181
183
  _shellLoaderSeed?: Map<string, import("../cache/shell-snapshot.js").ShellLoaderSeedEntry>;
182
184
  /**
183
- * @internal Shell fast-path marker: makes the NEXT full match treat the whole
184
- * matched route as an implicit doc-level cache() boundary (see
185
+ * @internal Shell fast-path marker: makes the next eligible match treat the
186
+ * whole matched route as an implicit doc-level cache() boundary (see
185
187
  * resolveShellImplicitCacheScope in cache/cache-scope.ts). Set ONLY on
186
188
  * (a) the capture's derived context — with a record-only store so the
187
189
  * capture's cacheRoute write lands in the snapshot, never the real store —
188
- * and (b) a HIT tail's seeded context when the entry is eligible
189
- * (!handlerLiveHoles), where the SeededShellStore serves the recorded doc
190
- * entry and the match skips handler execution entirely. Routes with their
191
- * own cache() config (including cache(false)) are never overridden: the
192
- * marker only applies when the route tree derived NO cache scope.
190
+ * (b) a HIT tail's seeded context, and (c) an eligible normal-route partial
191
+ * replay, where `store` is a request-local segment overlay. Handler-live holes
192
+ * and conditional transitions decline replay.
193
+ * Routes with their own cache() config (including cache(false)) are never
194
+ * overridden: the marker only applies when the route tree derived NO cache
195
+ * scope.
193
196
  */
194
197
  _shellImplicitCache?: {
195
198
  ttl?: number;
196
199
  swr?: number;
197
200
  store?: SegmentCacheStore;
201
+ /**
202
+ * @internal Override the implicit scope's default cache namespace. Shell
203
+ * captures and navigation replay both consume the canonical document
204
+ * segment record even when the triggering request is partial.
205
+ */
206
+ keyPrefix?: "doc";
207
+ /** @internal Called only after the implicit cache hit decodes successfully. */
208
+ onHit?: () => void;
198
209
  };
199
210
  /**
200
211
  * @internal Shell-HIT tail marker: cache/prerender-store hits during THIS
@@ -484,6 +495,9 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
484
495
  _tracing?: ResolvedTracing;
485
496
  /** @internal Router basename for this request (used by redirect()) */
486
497
  _basename?: string;
498
+ /** @internal Owning router id; scopes the search-schema/root-scope registry
499
+ * lookups per router (route-map-builder.ts) */
500
+ _routerId?: string;
487
501
  /**
488
502
  * @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
489
503
  * to avoid a second resolveRoute call. Cleared on HMR invalidation.
@@ -504,7 +518,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
504
518
  * This is the type exported to library consumers. Internal code should
505
519
  * use the full RequestContext interface directly.
506
520
  */
507
- export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhen" | "_cacheStore" | "_shellCaptureRun" | "_shellFragmentPayload" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_themeConfig" | "_locationState" | "_routeName" | "_prevRouteKey" | "_gateCurrentUrl" | "_gateCurrentParams" | "_gateActionId" | "_gateActionUrl" | "_gateActionResult" | "_gateFormData" | "_inActionRevalidation" | "_reportedErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_basename" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_variables" | "_classifiedRoute" | "_cacheSignal" | "_dynamic" | "res">;
521
+ export type PublicRequestContext<TEnv = DefaultEnv, TParams = Record<string, string>> = Omit<RequestContext<TEnv, TParams>, "cookie" | "cookies" | "setCookie" | "deleteCookie" | "_handleStore" | "_transitionWhen" | "_pprTransitionDecisions" | "_cacheStore" | "_shellCaptureRun" | "_shellImplicitCache" | "_shellLoaderSeed" | "_shellCaptureLoaderRecords" | "_shellCaptureHandleLiveness" | "_shellCaptureLoaderHandleValues" | "_shellFragmentPayload" | "_shellCaptureGuardTripped" | "_shellCaptureGuardTrippedLoaderId" | "_explicitTaggedStores" | "_requestTags" | "_cacheProfiles" | "_onResponseCallbacks" | "_pendingBackgroundTasks" | "_themeConfig" | "_locationState" | "_routeName" | "_prevRouteKey" | "_gateCurrentUrl" | "_gateCurrentParams" | "_gateActionId" | "_gateActionUrl" | "_gateActionResult" | "_gateFormData" | "_inActionRevalidation" | "_reportedErrors" | "_renderBarrier" | "_resolveRenderBarrier" | "_renderBarrierSegmentOrder" | "_treeHasStreaming" | "_renderBarrierWaiters" | "_handlerLoaderDeps" | "_renderBarrierHandleSnapshot" | "_renderBarrierGuardClosed" | "_reportBackgroundError" | "_debugPerformance" | "_metricsStore" | "_renderForeground" | "_renderDiagnosticsEnabled" | "_handlerStart" | "_tracing" | "_basename" | "_routerId" | "_setStatus" | "_rotateStateCookie" | "_setKeepCacheDirective" | "_variables" | "_classifiedRoute" | "_cacheSignal" | "_dynamic" | "res">;
508
522
  /**
509
523
  * Marker for a waitUntil-scheduled fn whose task promise must NOT enter
510
524
  * _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
@@ -9,7 +9,7 @@
9
9
  */
10
10
  export { RSC_ROUTER_BRAND, RouterRegistry } from "./router.js";
11
11
  export { HostRouterRegistry, type HostRouterRegistryEntry, } from "./host/router.js";
12
- export { registerRouteMap, setCachedManifest, clearCachedManifest, clearAllRouterData, getGlobalRouteMap, getRouterManifest, setPrecomputedEntries, setRouteTrie, setManifestReadyPromise, setRouterManifest, setRouterTrie, setRouterPrecomputedEntries, registerRouterManifestLoader, ensureRouterManifest, } from "./route-map-builder.js";
12
+ export { registerRouteMap, setCachedManifest, clearCachedManifest, clearAllRouterData, getGlobalRouteMap, getRouterManifest, setManifestReadyPromise, setRouterManifest, setRouterTrie, setRouterPrecomputedEntries, registerRouterManifestLoader, ensureRouterManifest, } from "./route-map-builder.js";
13
13
  export { registerLoaderById, setLoaderImports, } from "./server/loader-registry.js";
14
14
  export { createRequestContext, type CreateRequestContextOptions, } from "./server/request-context.js";
15
15
  export { isClientComponent, assertClientComponent } from "./component-utils.js";
@@ -4,7 +4,7 @@ import { createPageHelpers, createStopwatch, getHistoryState, getNumericContent,
4
4
  import { createParity, type ExpectParityOptions, type Parity, type ParityDescribeOptions, type ParityIntent } from "./parity.js";
5
5
  import { createRangoMatchers, type RangoMatchers } from "./matchers.js";
6
6
  export { assertCacheStatus, assertCacheDecision, parseCacheHeader, createCacheSink, filterCacheDecisions, type CacheSink, type ExpectedCacheStatus, type CacheStatusTarget, } from "../cache-status.js";
7
- export { assertShellStatus, parseShellStatus, shellCacheKey, SHELL_STATUS_HEADER, type ShellStatus, type ShellStatusTarget, } from "../shell-status.js";
7
+ export { assertPprReplayStatus, assertShellStatus, parsePprReplayStatus, parseShellStatus, PPR_REPLAY_STATUS_HEADER, shellCacheKey, SHELL_STATUS_HEADER, type PprReplayBypassReason, type PprReplayStatus, type ShellStatus, type ShellStatusTarget, } from "../shell-status.js";
8
8
  export { testId, waitForHydration, waitForNavigation, goBack, goForward, getHistoryState, waitForElement, isVisibleInViewport, parseNumber, getNumericContent, createStopwatch, measureTime, createPageHelpers, createUseFixture, createParity, createRangoMatchers, };
9
9
  export type { Fixture, FixtureOptions, PageHelpers, Stopwatch, Parity, ParityIntent, ParityDescribeOptions, ExpectParityOptions, RangoMatchers, };
10
10
  export interface RangoE2E extends PageHelpers, Parity {
@@ -44,8 +44,8 @@ export type { DispatchOptions } from "./dispatch.js";
44
44
  export { assertCacheStatus, assertCacheDecision, parseCacheHeader, createCacheSink, filterCacheDecisions, } from "./cache-status.js";
45
45
  export type { ExpectedCacheStatus, CacheStatusTarget, CacheSink, } from "./cache-status.js";
46
46
  export type { TelemetryEvent, TelemetrySink, CacheDecisionEvent, CacheSegmentSignal, CacheSegmentStatus, } from "../router/telemetry.js";
47
- export { assertShellStatus, parseShellStatus, shellCacheKey, SHELL_STATUS_HEADER, } from "./shell-status.js";
48
- export type { ShellStatus, ShellStatusTarget } from "./shell-status.js";
47
+ export { assertPprReplayStatus, assertShellStatus, parsePprReplayStatus, parseShellStatus, PPR_REPLAY_STATUS_HEADER, shellCacheKey, SHELL_STATUS_HEADER, } from "./shell-status.js";
48
+ export type { PprReplayBypassReason, PprReplayStatus, ShellStatus, ShellStatusTarget, } from "./shell-status.js";
49
49
  export { collectHandle } from "./collect-handle.js";
50
50
  export { diffGeneratedRoutes, assertGeneratedRoutesMatch, } from "./generated-routes.js";
51
51
  export type { GeneratedRoutesDiff, GeneratedRouteMismatch, } from "./generated-routes.js";
@@ -1,10 +1,9 @@
1
1
  /**
2
2
  * runTransitionWhen — unit-test a transition({ when }) predicate in isolation.
3
3
  *
4
- * Runs the SAME two server functions the router uses — applyViewTransitionDefault
5
- * (strips the `when` function from the serialized config and records the
6
- * predicate on the request context) and gateTransitions (assembles the
7
- * TransitionWhenContext and evaluates the predicate post-handler). So the
4
+ * Runs the SAME server functions the router uses — the PPR pre-handler evaluator
5
+ * when `ppr: true`, applyViewTransitionDefault (which strips the server-only
6
+ * function), and gateTransitions. So the
8
7
  * predicate sees exactly the navigation/action metadata it would at runtime
9
8
  * (currentUrl/currentParams/fromRouteName, nextUrl/nextParams/toRouteName,
10
9
  * actionId/actionUrl/actionResult/formData/method, get/env), and `kept` reflects
@@ -39,7 +38,7 @@ export interface RunTransitionWhenOptions<TEnv = any> {
39
38
  toRouteName?: string;
40
39
  /** Environment bindings surfaced as `env` (and `ctx.env`). */
41
40
  env?: TEnv;
42
- /** Variables a handler/middleware would have set this request, readable via the predicate's `get()`. */
41
+ /** Variables readable via the predicate's `get()`. With `ppr`, these model pre-handler middleware/input state. */
43
42
  vars?: VarsInit;
44
43
  /** Navigation SOURCE url (`currentUrl`): a URL or path string. */
45
44
  currentUrl?: string | URL;
@@ -57,6 +56,8 @@ export interface RunTransitionWhenOptions<TEnv = any> {
57
56
  formData?: FormData;
58
57
  /** Receives an error thrown by the predicate (the gate reports to `router.onError`, phase `"rendering"`). */
59
58
  onError?: OnErrorCallback;
59
+ /** Model a route with `ppr`, where the predicate runs before route handlers and cache lookup. */
60
+ ppr?: boolean;
60
61
  }
61
62
  /**
62
63
  * Result of runTransitionWhen.
@@ -2,7 +2,8 @@
2
2
  * PPR shell-status testing primitives for @rangojs/router consumers.
3
3
  *
4
4
  * Companion to `cache-status.ts` (segment/document cache): this module covers
5
- * the **shell axis** (`ppr` path option → `x-rango-shell: HIT | MISS`).
5
+ * the **shell axis** (`ppr` path option → `x-rango-shell: HIT | MISS`) and
6
+ * partial-navigation segment replay (`x-rango-ppr-replay: HIT`).
6
7
  *
7
8
  * ## Spike conclusions (plan 009)
8
9
  *
@@ -11,8 +12,9 @@
11
12
  * + `ppr: true` on a response route is a no-op for shell serve/capture. The
12
13
  * production path lives in `rsc/rsc-rendering.ts` + `rsc/shell-capture.ts`.
13
14
  *
14
- * 2. **Smallest HIT signal:** response header `x-rango-shell` (`HIT` | `MISS`),
15
- * always on document GETs to a ppr route that the serve path considered.
15
+ * 2. **Smallest status signals:** `x-rango-shell` (`HIT` | `MISS`) on document
16
+ * GETs, and `x-rango-ppr-replay` on partial requests to ppr routes. A replay
17
+ * HIT is reported only when matching consumes the captured segment record.
16
18
  * Secondary unit signal: `store.getShell(shellCacheKey(url))` after a real
17
19
  * capture flush (background `putShell`). There is no Flight flag for shell HIT.
18
20
  *
@@ -27,8 +29,21 @@
27
29
  */
28
30
  /** Production header name (`rsc/shell-serve.ts` `SHELL_STATUS_HEADER`). */
29
31
  export declare const SHELL_STATUS_HEADER: string;
32
+ /** Partial-navigation replay decision header from `rsc/rsc-rendering.ts`. */
33
+ export declare const PPR_REPLAY_STATUS_HEADER: string;
30
34
  /** Values the serve path writes on `x-rango-shell`. */
31
35
  export type ShellStatus = "HIT" | "MISS";
36
+ declare const PPR_REPLAY_BYPASS_REASONS: readonly ["method", "dynamic", "nonce", "store-unavailable", "passive-read-unsupported", "read-error", "no-entry", "invalid-version", "corrupt-entry", "handler-live-holes", "transition-when", "no-segment-snapshot", "snapshot-miss", "stale-build-entry"];
37
+ /** Bounded reasons a PPR partial request can fall open to ordinary matching. */
38
+ export type PprReplayBypassReason = (typeof PPR_REPLAY_BYPASS_REASONS)[number];
39
+ /** Parsed `x-rango-ppr-replay` value. */
40
+ export type PprReplayStatus = {
41
+ outcome: "HIT";
42
+ freshness: "fresh" | "stale";
43
+ } | {
44
+ outcome: "BYPASS";
45
+ reason: PprReplayBypassReason;
46
+ };
32
47
  /** A target carrying response headers (a Response or a `{ headers }` object). */
33
48
  export type ShellStatusTarget = Response | {
34
49
  headers: Headers;
@@ -56,3 +71,8 @@ export declare function parseShellStatus(target: ShellStatusTarget): ShellStatus
56
71
  * invent a HIT Response in unit tests — that fakes the serve path.
57
72
  */
58
73
  export declare function assertShellStatus(target: ShellStatusTarget, expected: ShellStatus): void;
74
+ /** Parse the dedicated PPR partial-navigation replay decision header. */
75
+ export declare function parsePprReplayStatus(target: ShellStatusTarget): PprReplayStatus | null;
76
+ /** Assert a partial response's PPR replay outcome and freshness/reason. */
77
+ export declare function assertPprReplayStatus(target: ShellStatusTarget, expected: PprReplayStatus): void;
78
+ export {};
@@ -9,6 +9,9 @@ export interface LazyIncludeContext {
9
9
  cacheProfiles?: Record<string, import("../cache/profile-registry.js").CacheProfile>;
10
10
  /** Root scope flag for dot-local reverse resolution */
11
11
  rootScoped?: boolean;
12
+ /** Owning router id; scopes search-schema/root-scope registration during
13
+ * lazy include evaluation (route-map-builder.ts registries) */
14
+ routerId?: string;
12
15
  /**
13
16
  * Positional include scope token composed from the parent scope plus this
14
17
  * include's sibling index (`${parentScope}I${idx}`). Applied to direct-
@@ -12,11 +12,12 @@ export type ViewTransitionClass = Record<string, string> | string;
12
12
  *
13
13
  * It mirrors the {@link ShouldRevalidateFn} args a `revalidate()` predicate
14
14
  * gets — the same navigation/action metadata — so the two read the same shape,
15
- * plus `get`/`env` for post-handler reads. There is no full `HandlerContext`
15
+ * plus `get`/`env` for request-context reads. There is no full `HandlerContext`
16
16
  * here: the gate runs at the RSC-payload layer with the request context, not a
17
17
  * handler context, so handler-only sugar (`search`/`build`/`dev`/`headers`) is
18
- * absent by design. `get` is the way to read what the handler/middleware set
19
- * via `ctx.set(...)` this request.
18
+ * absent by design. On ordinary routes, `get` can read what handlers or
19
+ * middleware set via `ctx.set(...)`. On `ppr` routes the gate runs before route
20
+ * handlers, so only middleware-established values are available.
20
21
  *
21
22
  * Field availability (all source fields are optional — never fabricated):
22
23
  * - `currentUrl` / `currentParams` / `fromRouteName` (the navigation SOURCE) are
@@ -41,27 +42,30 @@ export type ViewTransitionClass = Record<string, string> | string;
41
42
  * time, so `currentUrl`/`currentParams`/`fromRouteName` reflect the page the
42
43
  * prefetch fired from, NOT necessarily the page the user actually navigates from
43
44
  * — the decision is baked into the stored Flight payload and replayed verbatim.
44
- * A `cache()`/prerender hit replays the stored transition with the predicate NOT
45
- * re-run at all. So a source-sensitive predicate can be frozen to prefetch-time
46
- * or store-time state. This is accepted (~99% of navigations match), but if your
47
- * gate must reflect the exact click-time source, source-scope the prefetch
48
- * (`<Link prefetchKey=":source">`) and do not `cache()` that segment.
45
+ * A non-PPR `cache()`/prerender hit replays the stored transition with the
46
+ * predicate NOT re-run. A `ppr` route instead evaluates the predicate before
47
+ * handlers on every match, including cache, prerender, and PPR replay. Prefetch
48
+ * still freezes the server decision into its Flight payload. If the gate must
49
+ * reflect the exact click-time source, source-scope the prefetch
50
+ * (`<Link prefetchKey=":source">`).
49
51
  */
50
52
  export type TransitionWhenContext<TParams = Record<string, string>, TEnv = unknown> = Partial<Pick<RevalidateParams<TParams, TEnv>, "currentUrl" | "currentParams" | "fromRouteName">> & Pick<RevalidateParams<TParams, TEnv>, "nextUrl" | "nextParams" | "toRouteName" | "actionId" | "actionUrl" | "actionResult" | "formData" | "method"> & Pick<HandlerContext<any, TEnv>, "get" | "env">;
51
53
  /**
52
54
  * Predicate that gates whether a transition() applies for the current request.
53
55
  *
54
- * Evaluated server-side AFTER the route's handler runs (so `get(...)` can read
55
- * handler/middleware-set state) and outside any cache scope. Return false to
56
- * drop this segment's transition for the request; return true to apply it. The
56
+ * Evaluated server-side outside any cache scope. On ordinary routes it runs
57
+ * AFTER the route's handler, so `get(...)` can read handler/middleware state. A
58
+ * route with `ppr` automatically hoists it before route handlers and reevaluates
59
+ * it on every cache/prerender/PPR replay; there `get(...)` can read middleware
60
+ * state, but not values set by route handlers. Return false to drop this
61
+ * segment's transition for the request; return true to apply it. The
57
62
  * context ({@link TransitionWhenContext}) carries the same navigation/action
58
63
  * metadata a `revalidate()` predicate sees plus `get`/`env`. If it throws, the
59
64
  * error is reported to the router's onError (phase "rendering") and the
60
65
  * transition is dropped (the navigation does not hold).
61
66
  *
62
67
  * Distinct from intercept()'s `when` config selector, which runs at MATCH time
63
- * over `{ from, to, params, segments, … }`; a transition `when` runs
64
- * post-handler over the resolved payload.
68
+ * over `{ from, to, params, segments, … }`.
65
69
  *
66
70
  * Scope: dropping a transition removes only THIS segment's contribution to the
67
71
  * navigation's hold. The startTransition hold is navigation-wide — it engages if
@@ -69,10 +73,10 @@ export type TransitionWhenContext<TParams = Record<string, string>, TEnv = unkno
69
73
  * navigation stream its loading fallback only when no other matched segment
70
74
  * keeps a transition (the common case: a single transition on the route).
71
75
  *
72
- * Evaluated on every fresh (cache-miss) resolution; it is NOT re-run when a
73
- * segment is replayed from the runtime cache or a build-time prerender, and a
74
- * prefetched navigation freezes it to prefetch-time state see the caveat on
75
- * {@link TransitionWhenContext}.
76
+ * On non-PPR routes it runs only during fresh resolution. On PPR routes it runs
77
+ * before handlers for every match, including runtime cache, build-time
78
+ * prerender, and PPR segment replay. A prefetched navigation still freezes the
79
+ * result to prefetch time; see {@link TransitionWhenContext}.
76
80
  */
77
81
  export type TransitionWhenFn = (ctx: TransitionWhenContext) => boolean;
78
82
  /**
@@ -105,11 +109,12 @@ export interface TransitionConfig {
105
109
  viewTransition?: "auto" | false;
106
110
  /**
107
111
  * Optional server-side predicate that gates this transition per request. When
108
- * present and it returns false (evaluated post-handler), the router drops this
109
- * segment's transition for the request, so the navigation streams its loading
110
- * fallback instead of holding. The predicate is server-only and never
111
- * serialized to the client; only its resolved effect (transition kept or
112
- * dropped) crosses. See {@link TransitionWhenFn}.
112
+ * present and it returns false, the router drops this segment's transition for
113
+ * the request, so the navigation streams its loading fallback instead of
114
+ * holding. PPR routes evaluate it before route handlers and on every replay;
115
+ * other routes evaluate it after handlers on fresh resolution. The predicate
116
+ * is server-only and never serialized to the client; only its resolved effect
117
+ * crosses. See {@link TransitionWhenFn}.
113
118
  */
114
119
  when?: TransitionWhenFn;
115
120
  }
@@ -183,9 +183,10 @@ export type PathHelpers<TEnv> = {
183
183
  * transition cannot fire without a startTransition. See
184
184
  * skills/view-transitions for the startTransition x ViewTransition matrix.
185
185
  *
186
- * Pass `when: (ctx) => boolean` to gate the transition per request: it runs
187
- * server-side after the route handler (can read `ctx.get(...)`), and returning
188
- * false drops the transition so the navigation streams its loading() skeleton.
186
+ * Pass `when: (ctx) => boolean` to gate the transition per request. It normally
187
+ * runs after the route handler; `ppr` routes automatically run it before route
188
+ * handlers and on every replay, where `ctx.get(...)` sees middleware state but
189
+ * not handler-set values.
189
190
  */
190
191
  transition: {
191
192
  (): TransitionItem;
@@ -60,6 +60,7 @@ export interface ChunkInfo {
60
60
  export interface PerRouterManifestEntry {
61
61
  id: string;
62
62
  routeManifest: Record<string, string>;
63
+ routeTrailingSlash?: Record<string, string>;
63
64
  routeSearchSchemas?: Record<string, Record<string, string>>;
64
65
  sourceFile?: string;
65
66
  factoryOnlyPrefixes?: Set<string>;
@@ -103,8 +104,6 @@ export interface DiscoveryState {
103
104
  opts: PluginOptions | undefined;
104
105
  mergedRouteManifest: Record<string, string> | null;
105
106
  perRouterManifests: PerRouterManifestEntry[];
106
- mergedPrecomputedEntries: PrecomputedEntry[] | null;
107
- mergedRouteTrie: any;
108
107
  perRouterTrieMap: Map<string, any>;
109
108
  perRouterPrecomputedMap: Map<string, PrecomputedEntry[]>;
110
109
  perRouterManifestDataMap: Map<string, Record<string, string>>;
@@ -137,6 +136,8 @@ export interface DiscoveryState {
137
136
  resolvedPrerenderModules: Map<string, string[]> | undefined;
138
137
  resolvedStaticModules: Map<string, string[]> | undefined;
139
138
  discoveryDone: Promise<void> | null;
139
+ /** Monotonic Cloudflare-dev generation installed by the routes manifest. */
140
+ devDiscoveryEpoch?: number;
140
141
  devServerOrigin: string | null;
141
142
  devServer: any;
142
143
  selfWrittenGenFiles: Map<string, {
@@ -1,4 +1,4 @@
1
- export { flattenLeafEntries, buildRouteToStaticPrefix, } from "../../build/prefix-tree-utils.js";
1
+ export { flattenLeafEntries } from "../../build/prefix-tree-utils.js";
2
2
  /**
3
3
  * Wrap a value as `JSON.parse('...')` instead of a JS object literal.
4
4
  * V8's JSON parser is significantly faster than its full JS parser for large