@rangojs/router 0.34.0 → 0.35.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 (103) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/{chunk-4HHFQSDC.js → chunk-3CQXDX3A.js} +5 -5
  3. package/dist/bin/{client-urls-projection-4Q5EQDAL.js → client-urls-projection-2GBDM5YK.js} +1 -1
  4. package/dist/bin/rango.js +1 -1
  5. package/dist/bin/{runtime-discovery-2ZTYRZ3V.js → runtime-discovery-PA3V3IFQ.js} +2 -2
  6. package/dist/bin/{temp-rsc-server-EUNKRFJD.js → temp-rsc-server-WDXEHDY7.js} +2 -2
  7. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  8. package/dist/types/cache/types.d.ts +1 -1
  9. package/dist/types/client-urls/server-projection.d.ts +1 -1
  10. package/dist/types/client-urls/types.d.ts +6 -6
  11. package/dist/types/decode-loader-results.d.ts +1 -1
  12. package/dist/types/errors.d.ts +3 -3
  13. package/dist/types/handles/meta.d.ts +2 -2
  14. package/dist/types/loader-store.d.ts +20 -8
  15. package/dist/types/outlet-context.d.ts +1 -1
  16. package/dist/types/route-definition/helpers-types.d.ts +5 -5
  17. package/dist/types/router/loader-resolution.d.ts +1 -1
  18. package/dist/types/router/match-context.d.ts +12 -0
  19. package/dist/types/router/navigation-snapshot.d.ts +17 -0
  20. package/dist/types/router/segment-resolution/loader-cache.d.ts +7 -7
  21. package/dist/types/router/types.d.ts +1 -1
  22. package/dist/types/rsc/shell-capture-constants.d.ts +2 -2
  23. package/dist/types/server/context.d.ts +3 -3
  24. package/dist/types/server/request-context.d.ts +1 -1
  25. package/dist/types/ssr/index.d.ts +1 -1
  26. package/dist/types/ssr/ssr-root.d.ts +1 -1
  27. package/dist/types/ssr-suspension-warning.d.ts +1 -1
  28. package/dist/types/types/loader-types.d.ts +19 -19
  29. package/dist/types/types/segments.d.ts +2 -2
  30. package/dist/types/urls/path-helper-types.d.ts +4 -4
  31. package/dist/types/urls/pattern-types.d.ts +1 -1
  32. package/dist/types/vite/plugin-types.d.ts +1 -1
  33. package/dist/vite/index.js +7 -7
  34. package/package.json +1 -1
  35. package/skills/app-structure/e-commerce.md +1 -1
  36. package/skills/breadcrumbs/SKILL.md +5 -5
  37. package/skills/cache-guide/SKILL.md +4 -4
  38. package/skills/caching/SKILL.md +1 -1
  39. package/skills/catalog.json +2 -2
  40. package/skills/client-urls/SKILL.md +20 -20
  41. package/skills/comparison/references/framework-comparison.md +6 -6
  42. package/skills/defer-hydration/SKILL.md +2 -2
  43. package/skills/handler-use/SKILL.md +5 -5
  44. package/skills/hooks/data.md +5 -2
  45. package/skills/hooks/handle-and-actions.md +9 -9
  46. package/skills/loader/SKILL.md +52 -47
  47. package/skills/middleware/SKILL.md +2 -2
  48. package/skills/migrate-nextjs/SKILL.md +10 -10
  49. package/skills/migrate-react-router/SKILL.md +1 -1
  50. package/skills/migrate-react-router/component-migration.md +2 -2
  51. package/skills/migrate-react-router/data-and-actions.md +2 -2
  52. package/skills/migrate-react-router/route-mapping.md +2 -2
  53. package/skills/parallel/SKILL.md +2 -2
  54. package/skills/ppr/SKILL.md +91 -92
  55. package/skills/prerender/SKILL.md +5 -5
  56. package/skills/rango/SKILL.md +7 -7
  57. package/skills/route/SKILL.md +1 -1
  58. package/skills/router-setup/SKILL.md +2 -2
  59. package/skills/scripts/SKILL.md +3 -3
  60. package/skills/shell-manifest/SKILL.md +3 -3
  61. package/skills/testing/serve-shell-request.md +1 -1
  62. package/skills/theme/SKILL.md +2 -2
  63. package/skills/typesafety/env-and-bindings.md +2 -2
  64. package/skills/use-cache/SKILL.md +1 -1
  65. package/skills/ux-architecture/SKILL.md +7 -7
  66. package/src/cache/shell-snapshot.ts +1 -1
  67. package/src/cache/types.ts +1 -1
  68. package/src/client-urls/client-urls.ts +16 -9
  69. package/src/client-urls/server-projection.ts +4 -4
  70. package/src/client-urls/types.ts +6 -6
  71. package/src/decode-loader-results.ts +1 -1
  72. package/src/errors.ts +9 -9
  73. package/src/handles/meta.ts +2 -2
  74. package/src/loader-store.ts +29 -13
  75. package/src/outlet-context.ts +1 -1
  76. package/src/route-definition/dsl-helpers.ts +4 -8
  77. package/src/route-definition/helpers-types.ts +5 -5
  78. package/src/router/loader-resolution.ts +4 -4
  79. package/src/router/match-api.ts +2 -0
  80. package/src/router/match-context.ts +22 -1
  81. package/src/router/match-middleware/cache-lookup.ts +17 -8
  82. package/src/router/match-middleware/intercept-resolution.ts +3 -3
  83. package/src/router/navigation-snapshot.ts +39 -8
  84. package/src/router/segment-resolution/loader-cache.ts +8 -8
  85. package/src/router/segment-resolution/prefetch-deferral.ts +1 -1
  86. package/src/router/segment-resolution/revalidation.ts +3 -3
  87. package/src/router/types.ts +1 -1
  88. package/src/rsc/shell-capture-constants.ts +3 -3
  89. package/src/rsc/shell-capture.ts +12 -12
  90. package/src/segment-system.tsx +3 -3
  91. package/src/server/context.ts +5 -5
  92. package/src/server/cookie-store.ts +1 -1
  93. package/src/server/request-context.ts +1 -1
  94. package/src/ssr/index.tsx +6 -6
  95. package/src/ssr/ssr-root.tsx +1 -1
  96. package/src/ssr-suspension-warning.ts +4 -4
  97. package/src/types/loader-types.ts +19 -19
  98. package/src/types/segments.ts +2 -2
  99. package/src/urls/path-helper-types.ts +4 -4
  100. package/src/urls/pattern-types.ts +1 -1
  101. package/src/use-loader.tsx +5 -3
  102. package/src/vite/discovery/shell-prerender-phase.ts +1 -1
  103. package/src/vite/plugin-types.ts +1 -1
package/README.md CHANGED
@@ -304,7 +304,7 @@ Loaders run in parallel with the handler and stream their results.
304
304
  into it. Without `loading()`, the segment has no skeleton, and the HTML
305
305
  normally arrives with the data already in place. To guarantee that a
306
306
  loader's data, head tags, or a real 404 are in the first HTML flush, register
307
- it with `loader(StockLoader, { ssr: false })`.
307
+ it with `loader(StockLoader, { ssr: "await" })`.
308
308
 
309
309
  When a loader's data changes, every component that reads it updates too.
310
310
  Calling `load()` from `useLoader()` refetches it on the client, and
@@ -23,7 +23,7 @@ import * as React4 from "react";
23
23
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
24
24
 
25
25
  // src/rsc/shell-capture-constants.ts
26
- var PPR_LANE_HINT = 'Lane rule: only loader(Def, { ssr: false }) executes at capture and bakes its non-promise data; every other loader is live (masked at capture, fresh per request) and needs loading() or an inline <Suspense> above its reader. See the /ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
26
+ var PPR_LANE_HINT = 'Lane rule: only loader(Def, { ssr: "await" }) executes at capture and bakes its non-promise data; every other loader is live (masked at capture, fresh per request) and needs loading() or an inline <Suspense> above its reader. See the /ppr skill, "The loader lane rule" (node_modules/@rangojs/router/skills/ppr/SKILL.md).';
27
27
 
28
28
  // src/cache/cache-exec-scope.ts
29
29
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -52,8 +52,8 @@ var LOADER_BODY_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-bo
52
52
  var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage2();
53
53
  function requestReadCaptureFix(what) {
54
54
  return {
55
- capture: `The captured shell is served to every user of this URL, so request-scoped data read here would leak one user's ${what} to others. Read it inside a loader without ssr: false and consume it with useLoader, e.g. createLoader(async () => getUser(cookies().get("session")?.value)). ${PPR_LANE_HINT}`,
56
- warning: "Read it in a loader without ssr: false and consume it with useLoader under loading() or an inline <Suspense> (a live hole). A promise the handler passes or pushes does not help: the capture waits for it."
55
+ capture: `The captured shell is served to every user of this URL, so request-scoped data read here would leak one user's ${what} to others. Read it inside a loader without ssr: "await" and consume it with useLoader, e.g. createLoader(async () => getUser(cookies().get("session")?.value)). ${PPR_LANE_HINT}`,
56
+ warning: 'Read it in a loader without ssr: "await" and consume it with useLoader under loading() or an inline <Suspense> (a live hole). A promise the handler passes or pushes does not help: the capture waits for it.'
57
57
  };
58
58
  }
59
59
  var NON_CACHEABLE_READ = {
@@ -1149,7 +1149,7 @@ var RAW_COOKIES_READ = {
1149
1149
  verb: "called",
1150
1150
  fix: COOKIES_READ.fix
1151
1151
  };
1152
- var THEME_READ_FIX = "On ppr and cache() routes, read the theme with useTheme() in a client component, or in a live loader (no ssr: false) with cookies().get(<storageKey>). The <html> theme class needs neither: the theme script sets it before paint. See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).";
1152
+ var THEME_READ_FIX = 'On ppr and cache() routes, read the theme with useTheme() in a client component, or in a live loader (no ssr: "await") with cookies().get(<storageKey>). The <html> theme class needs neither: the theme script sets it before paint. See the /theme skill (node_modules/@rangojs/router/skills/theme/SKILL.md).';
1153
1153
  var THEME_READ = {
1154
1154
  verb: "read",
1155
1155
  fix: {
@@ -1348,7 +1348,7 @@ function serializeRoute(route2, options, ordinals) {
1348
1348
  });
1349
1349
  const awaitedLoaderIndices = indicesWhere(
1350
1350
  route2.loaders,
1351
- ({ ssr }) => ssr === false
1351
+ ({ ssr }) => ssr === "await"
1352
1352
  );
1353
1353
  const transition3 = serializeTransition(route2);
1354
1354
  return Object.freeze({
@@ -10,7 +10,7 @@ import {
10
10
  refreshRecordedClientUrlProjections,
11
11
  resolveClientUrlsSource,
12
12
  scanForClientUrlModules
13
- } from "./chunk-4HHFQSDC.js";
13
+ } from "./chunk-3CQXDX3A.js";
14
14
  import "./chunk-MWOHBDPM.js";
15
15
  import "./chunk-LD7UBHT4.js";
16
16
  export {
package/dist/bin/rango.js CHANGED
@@ -229,7 +229,7 @@ ${formatNestedRouterConflictError(nestedRouterConflict, "[rango]")}
229
229
  }
230
230
  let discoverAndWriteRouteTypes;
231
231
  try {
232
- const mod = await import("./runtime-discovery-2ZTYRZ3V.js");
232
+ const mod = await import("./runtime-discovery-PA3V3IFQ.js");
233
233
  discoverAndWriteRouteTypes = mod.discoverAndWriteRouteTypes;
234
234
  } catch (err) {
235
235
  if (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") {
@@ -163,9 +163,9 @@ async function discoverAndWriteRouteTypes(opts) {
163
163
  "Runtime discovery requires '@vitejs/plugin-rsc'. Install it with: pnpm add -D @vitejs/plugin-rsc"
164
164
  );
165
165
  }
166
- const { createTempRscServer } = await import("./temp-rsc-server-EUNKRFJD.js");
166
+ const { createTempRscServer } = await import("./temp-rsc-server-WDXEHDY7.js");
167
167
  const { createDiscoveryState } = await import("./state-ACD2MGKJ.js");
168
- const { discoverClientUrlProjections } = await import("./client-urls-projection-4Q5EQDAL.js");
168
+ const { discoverClientUrlProjections } = await import("./client-urls-projection-2GBDM5YK.js");
169
169
  const {
170
170
  isFrameworkPlugin,
171
171
  pickForwardedRunnerConfig,
@@ -27,7 +27,7 @@ import {
27
27
  propertyKeyIs,
28
28
  recordClientUrlsModule,
29
29
  unwrapExpression
30
- } from "./chunk-4HHFQSDC.js";
30
+ } from "./chunk-3CQXDX3A.js";
31
31
  import "./chunk-MWOHBDPM.js";
32
32
  import {
33
33
  codeMatchIndices,
@@ -47,7 +47,7 @@ var DEFAULT_ROUTER_VERSIONS_KEY = "*";
47
47
  // package.json
48
48
  var package_default = {
49
49
  name: "@rangojs/router",
50
- version: "0.34.0",
50
+ version: "0.35.0",
51
51
  description: "Django-inspired RSC router with composable URL patterns",
52
52
  keywords: [
53
53
  "react",
@@ -5,7 +5,7 @@
5
5
  * that must agree with them byte for byte. The handler layer's part of that
6
6
  * payload is the capture's own doc segment record: the capture writes it
7
7
  * first, renders its prelude from it, and every HIT replays it (no handler
8
- * runs on a HIT). The loader layer's part is the bake-lane (`ssr: false`)
8
+ * runs on a HIT). The loader layer's part is the bake-lane (`ssr: "await"`)
9
9
  * loader pins: a promise-free bake-lane loader is served from its pin and
10
10
  * does not run; one whose return carries promises (`holes`), or whose pushes
11
11
  * the record could not keep (`runs`), runs on the HIT. See
@@ -770,7 +770,7 @@ export type SegmentHandleData = Record<string, unknown[]>;
770
770
  /**
771
771
  * segmentId -> handleName -> the owning loader id of each recorded value, by
772
772
  * index (null: not owned). Only a PPR shell capture writes owners: the
773
- * settled pushes of the loader bodies it ran (an `ssr: false` loader's own,
773
+ * settled pushes of the loader bodies it ran (an `ssr: "await"` loader's own,
774
774
  * the loaders it awaits, and its own cache() replays), which the record keeps
775
775
  * because the prelude rendered them. A restore of the record
776
776
  * (handle-snapshot.ts restoreHandles) keeps an owner's value where the
@@ -28,7 +28,7 @@ export interface ClientUrlProjectionRoute {
28
28
  readonly options: ClientUrlProjectionOptions;
29
29
  readonly loaderIds: readonly string[];
30
30
  readonly hasLoading: boolean;
31
- /** Indices into loaderIds of loaders declared loader(Def, { ssr: false });
31
+ /** Indices into loaderIds of loaders declared loader(Def, { ssr: "await" });
32
32
  * materialization passes the option through to the server loader() so
33
33
  * document renders await them before first flush. Absent (= none) in
34
34
  * projections serialized before the option existed. */
@@ -42,12 +42,12 @@ export interface ClientUrlLoaderRecord {
42
42
  */
43
43
  readonly revalidate: readonly (ShouldRevalidateFn<any, DefaultEnv> | HoistedRevalidateRef | boolean)[];
44
44
  /**
45
- * loader(Def, { ssr: false }): document renders await this loader before
45
+ * loader(Def, { ssr: "await" }): document renders await this loader before
46
46
  * first flush (see {@link LoaderOptions}). Projected into the server tree,
47
47
  * where the per-isSSR entry stamping applies — client navigations stream
48
48
  * regardless.
49
49
  */
50
- readonly ssr?: false;
50
+ readonly ssr?: "await";
51
51
  /**
52
52
  * loader(Def, { prefetch: false | "intent" }): a prefetch of the route, or
53
53
  * of an intercept's target from its source, defers this loader and the
@@ -109,10 +109,10 @@ export interface ClientUrlHelpers {
109
109
  * Attach a projected loader. The optional use callback may contain
110
110
  * revalidate() only: a per-loader predicate the server runs, as in urls().
111
111
  *
112
- * Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
113
- * false }) — to await this loader before first flush on DOCUMENT requests
114
- * (see {@link LoaderOptions}): the opt-in for loaders whose data, handle
115
- * pushes, or thrown notFound()/redirect() must be in the SSR'd HTML.
112
+ * Pass `{ ssr: "await" }` to await this loader before first flush on
113
+ * DOCUMENT requests (see {@link LoaderOptions}): the opt-in for loaders
114
+ * whose data, handle pushes, or thrown notFound()/redirect() must be in the
115
+ * SSR'd HTML.
116
116
  * Per-loader: a streaming sibling keeps streaming. Under a `ppr` group
117
117
  * route the flag BAKES: the loader executes at shell capture and its
118
118
  * settled return freezes into the shell (nested promises stay live
@@ -18,7 +18,7 @@ import { type ErrorInfo, type ResolvedSegment } from "./types.js";
18
18
  * decodeLoaderResults runs during the tree build on settled results, where a
19
19
  * throw would collapse the whole payload — it routes signals through the
20
20
  * errorFallback slot instead (same visual: the slot replaces the children
21
- * under OutletProvider). The document lane's `{ ssr: false }` results take
21
+ * under OutletProvider). The document lane's `{ ssr: "await" }` results take
22
22
  * this route (segment-system buildLoaderStreams): they are settled before the
23
23
  * tree is built, and the read-site throw would land in the Fizz shell with no
24
24
  * boundary to absorb it.
@@ -243,10 +243,10 @@ export declare class InvalidHandlerError extends Error {
243
243
  */
244
244
  export declare function invariant(condition: any, message: string): asserts condition;
245
245
  /**
246
- * Rejects a boolean-only DSL option that holds anything else (undefined
247
- * passes), as an invariant.
246
+ * Rejects a loader `ssr` option other than `"stream"` or `"await"`
247
+ * (undefined passes). clientUrls() inlines the same checks.
248
248
  */
249
- export declare function assertBooleanOption(value: unknown, option: string, effect: string): void;
249
+ export declare function assertLoaderSsrOption(value: unknown, helper: string): void;
250
250
  /**
251
251
  * Rejects a `prefetch` DSL option that is neither a boolean nor `"intent"`
252
252
  * (undefined passes), as an invariant.
@@ -40,9 +40,9 @@ import type { MetaDescriptor } from "../router/types.js";
40
40
  * title-templating identically to synchronous descriptors. A descriptor that
41
41
  * resolves to `null`/`undefined` (or rejects) is dropped.
42
42
  *
43
- * On a ppr route, a descriptor a handler or an `ssr: false` loader pushes,
43
+ * On a ppr route, a descriptor a handler or an `ssr: "await"` loader pushes,
44
44
  * promise or not, is in the shell's `<head>` on every HIT (#1182). One a
45
- * loader without `ssr: false` pushes is live: on a HIT it arrives after
45
+ * loader without `ssr: "await"` pushes is live: on a HIT it arrives after
46
46
  * hydration.
47
47
  */
48
48
  export declare const Meta: Handle<MetaDescriptor, MetaDescriptor[]>;
@@ -28,6 +28,10 @@
28
28
  * cancelled on resubscribe so a StrictMode / transition remount does not
29
29
  * reclaim a bucket that is about to be reused, and is held until any
30
30
  * in-flight load settles.
31
+ * - Owned buckets (one reader's private refresh-group bucket,
32
+ * `useLoader(L, { refreshGroup })` without a key) are sticky while that
33
+ * reader is mounted and dropped like an ephemeral bucket once it leaves:
34
+ * no other reader names the bucket, and a remount takes a new one.
31
35
  *
32
36
  * Each router's NavigationStore owns one (navigation-store.ts), and hooks reach
33
37
  * it through NavigationStoreContext: the router store is the single source of
@@ -79,6 +83,13 @@ export interface SubscribeOptions {
79
83
  * subscriber attaches, and from then on resets via `clearFamily`.
80
84
  */
81
85
  ephemeral?: boolean;
86
+ /**
87
+ * When true, the bucket belongs to this subscriber alone (a private
88
+ * refresh-group bucket, which no other reader names): it is reset by
89
+ * `clearFamily` while subscribed, and dropped after the subscriber leaves
90
+ * even when sticky. Read at unsubscribe, so pass it on every subscribe.
91
+ */
92
+ owned?: boolean;
82
93
  /**
83
94
  * Cross-loader refresh group name(s). Tags this bucket so `refreshGroups(name)`
84
95
  * can refresh it alongside buckets of other loaders. A bucket may be tagged
@@ -130,8 +141,8 @@ export declare class LoaderStore {
130
141
  * Subscribe to entry changes for `bucketKey`.
131
142
  * Returns an unsubscribe function. A sticky bucket's entry is kept around even
132
143
  * after the last subscriber leaves so that an in-flight `load()` can still
133
- * commit if the consumer remounts. An ephemeral bucket is dropped once its
134
- * last subscriber leaves (deferred, see maybeScheduleRefcountClear).
144
+ * commit if the consumer remounts. An ephemeral or owned bucket is dropped
145
+ * once its last subscriber leaves (deferred, see maybeScheduleRefcountClear).
135
146
  */
136
147
  subscribe(bucketKey: string, cb: () => void, options?: SubscribeOptions): () => void;
137
148
  private addToGroup;
@@ -149,14 +160,14 @@ export declare class LoaderStore {
149
160
  */
150
161
  refreshGroups(groups: string | string[]): Promise<void>;
151
162
  /**
152
- * Schedule a deferred drop for an ad-hoc (never-sticky) bucket whose last
153
- * subscriber just left. Deferred to a microtask so a StrictMode / transition
154
- * remount that resubscribes in the same tick cancels it.
163
+ * Schedule a deferred drop for an ad-hoc (never-sticky) or owned bucket
164
+ * whose last subscriber just left. Deferred to a microtask so a StrictMode /
165
+ * transition remount that resubscribes in the same tick cancels it.
155
166
  */
156
167
  private maybeScheduleRefcountClear;
157
168
  /** Remove a bucket entry entirely and prune it from its family/group indexes. */
158
169
  private dropBucket;
159
- /** Drop an ephemeral bucket that settled with no subscribers left. */
170
+ /** Drop an ephemeral or owned bucket that settled with no subscribers left. */
160
171
  private dropIfSettled;
161
172
  /**
162
173
  * Returns the current snapshot for `bucketKey`. Stable reference between
@@ -205,7 +216,8 @@ export declare class LoaderStore {
205
216
  * Reset a single bucket entry. Bumps `latestRequestId` so any in-flight
206
217
  * `load()` whose promise is still pending will fail its gate when it resolves
207
218
  * and be dropped — prevents pre-navigation loads from clobbering the new
208
- * route's context. The entry itself is kept (sticky-bucket semantics).
219
+ * route's context. The entry itself is kept (sticky-bucket semantics),
220
+ * unless it was only waiting for that request to be dropped.
209
221
  */
210
222
  clear(bucketKey: string): void;
211
223
  /**
@@ -221,7 +233,7 @@ export declare class LoaderStore {
221
233
  /**
222
234
  * Register a still-streaming per-loader result for `loaderId`. No-op for a
223
235
  * settled Flight chunk (unwrapsSynchronously / "rejected"): the forceAwait
224
- * and action lanes commit settled chunks, and a { ssr: false } loader is
236
+ * and action lanes commit settled chunks, and a { ssr: "await" } loader is
225
237
  * awaited before flush. Released as the stream settles, either way.
226
238
  */
227
239
  trackPendingStream(loaderId: string, stream: Promise<unknown>): void;
@@ -19,7 +19,7 @@ export interface OutletContextValue {
19
19
  loaderStreams?: Record<string, unknown>;
20
20
  /**
21
21
  * Loader $$ids on this segment that the render awaited before first flush
22
- * (loader(Def, { ssr: false })). Dev-diagnostic input only: useLoader uses
22
+ * (loader(Def, { ssr: "await" })). Dev-diagnostic input only: useLoader uses
23
23
  * it to warn when a read still suspends during SSR on a render that awaited
24
24
  * flagged loaders (ssr-suspension-warning.ts). Present only where a
25
25
  * segment carries flagged loaders (document and shell-capture renders, and
@@ -221,14 +221,14 @@ export type RouteHelpers<T extends RouteDefinition, TEnv> = {
221
221
  * return <div>{data.name}</div>;
222
222
  * }
223
223
  * ```
224
- * Pass `{ ssr: false }` — the same knob as loading(fallback, { ssr:
225
- * false }) — to await this loader before first flush on DOCUMENT requests
226
- * (see {@link LoaderOptions}): the opt-in for loaders whose data, handle
227
- * pushes, or thrown notFound()/redirect() must be in the SSR'd HTML.
224
+ * Pass `{ ssr: "await" }` to await this loader before first flush on
225
+ * DOCUMENT requests (see {@link LoaderOptions}): the opt-in for loaders
226
+ * whose data, handle pushes, or thrown notFound()/redirect() must be in the
227
+ * SSR'd HTML.
228
228
  * Per-loader: a streaming sibling in the same segment keeps streaming.
229
229
  *
230
230
  * ```typescript
231
- * loader(ProductLoader, { ssr: false }, () => [cache()]),
231
+ * loader(ProductLoader, { ssr: "await" }, () => [cache()]),
232
232
  * loader(RecommendationsLoader), // still streams behind loading()
233
233
  * ```
234
234
  *
@@ -44,7 +44,7 @@ export type LoaderErrorCallback = (error: unknown, context: {
44
44
  * This has a simplified signature for internal use - the caller (typically
45
45
  * wrapLoaderPromise in router.ts) is responsible for bridging to the full
46
46
  * OnErrorCallback with complete request context (request, url, env, etc.).
47
- * @param useDefaultErrorBoundary - An `ssr: false` loader on a document or
47
+ * @param useDefaultErrorBoundary - An `ssr: "await"` loader on a document or
48
48
  * capture render: an error with no declared boundary gets
49
49
  * `DefaultErrorFallback` (it would otherwise reject the tree), and every
50
50
  * error answers 500.
@@ -112,6 +112,7 @@ import type { CacheScope } from "../cache/cache-scope.js";
112
112
  import type { EntryData, InterceptSelectorContext, MetricsStore } from "../server/context.js";
113
113
  import type { HandlerContext, ResolvedSegment } from "../types.js";
114
114
  import type { RouteMatchResult } from "./pattern-matching.js";
115
+ import type { PrevPage } from "./navigation-snapshot.js";
115
116
  import type { InterceptResult } from "./router-context.js";
116
117
  import type { ResolutionPassInput } from "./segment-resolution/resolution-pass.js";
117
118
  import type { ActionContext } from "./types.js";
@@ -130,7 +131,10 @@ export interface MatchContext<TEnv = any> {
130
131
  heldBoundary?: string;
131
132
  prevUrl: URL;
132
133
  prevParams: Record<string, string>;
134
+ /** The match of the page left: the intercept's URL while one is open. */
133
135
  prevMatch: RouteMatchResult | null;
136
+ /** The page an open intercept's slot was rendered for. */
137
+ interceptPrev: PrevPage;
134
138
  matched: RouteMatchResult;
135
139
  manifestEntry: EntryData;
136
140
  entries: EntryData[];
@@ -169,6 +173,14 @@ export interface MatchContext<TEnv = any> {
169
173
  export declare function rendersLive(ctx: Pick<MatchContext, "isAction" | "stale">): boolean;
170
174
  /** This match as the input of a revalidating resolution pass (resolution-pass.ts). */
171
175
  export declare function resolutionPassInput<TEnv>(ctx: MatchContext<TEnv>): ResolutionPassInput<TEnv>;
176
+ /**
177
+ * This match as the input of the pass that resolves its intercept's slot: its
178
+ * held segments compare with the intercept's own URL (`interceptPrev`), not
179
+ * with the source page under it. From /item/2 over /list/1, a link to /item/1
180
+ * compared with the source's params would read as param-equal and keep item
181
+ * 2's slot.
182
+ */
183
+ export declare function interceptPassInput<TEnv>(ctx: MatchContext<TEnv>): ResolutionPassInput<TEnv>;
172
184
  /**
173
185
  * Mutable state that flows through the pipeline
174
186
  */
@@ -1,10 +1,27 @@
1
1
  import type { RouteMatchResult } from "./pattern-matching.js";
2
+ /** A page held segments were rendered for: what their revalidation compares with. */
3
+ export interface PrevPage {
4
+ url: URL;
5
+ params: Record<string, string>;
6
+ }
2
7
  export interface NavigationSnapshot {
8
+ /**
9
+ * `prevUrl` and `prevParams`: the page the held page segments were rendered
10
+ * for. That is the page left, or the source page while an intercept is open
11
+ * (`hasInterceptSource`).
12
+ */
3
13
  prevUrl: URL;
4
14
  /** The visitor's URL of the page left, when `prevUrl` is its rewrite target. */
5
15
  prevVisitorUrl: URL | null;
6
16
  prevParams: Record<string, string>;
17
+ /** The match of the page left: the intercept's URL while one is open. */
7
18
  prevMatch: RouteMatchResult | null;
19
+ /**
20
+ * The page an open intercept's slot was rendered for: the intercept's own
21
+ * URL (match-context.ts interceptPassInput). Equals `prevUrl`/`prevParams`
22
+ * when the request names no intercept source.
23
+ */
24
+ interceptPrev: PrevPage;
8
25
  interceptContextUrl: URL;
9
26
  interceptContextMatch: RouteMatchResult | null;
10
27
  clientSegmentIds: string[];
@@ -56,14 +56,14 @@ import { type RequestContext } from "../../server/request-context.js";
56
56
  export declare function bindsLoaderCache(entries: readonly EntryData[]): boolean;
57
57
  /**
58
58
  * The lane of each loader the matched chain `entries` registers: "live" (no
59
- * `ssr: false`) or "bake". A loader registered on both is "live". A PPR
59
+ * `ssr: "await"`) or "bake". A loader registered on both is "live". A PPR
60
60
  * capture credits a push to the first registered loader around it when that
61
61
  * one is live (shell-capture.ts deriveShellCaptureContext).
62
62
  */
63
63
  export declare function routeLoaderLanes(entries: readonly EntryData[]): ReadonlyMap<string, "live" | "bake">;
64
64
  /**
65
65
  * This request serves `loaderEntry` from a pin (servedPins), so its body does
66
- * not decide the value: resolveLoaderData serves a pin to an `ssr: false`
66
+ * not decide the value: resolveLoaderData serves a pin to an `ssr: "await"`
67
67
  * registration only. A prefetch does not defer such a loader
68
68
  * (prefetch-deferral.ts defersLoader): nothing would run, and the pin is the
69
69
  * value the document shows.
@@ -77,14 +77,14 @@ export declare function servesLoaderPin(loaderEntry: LoaderEntry, reqCtx?: Reque
77
77
  * only and the store asks while loader bodies run.
78
78
  *
79
79
  * Each side applies its own lane to the pin. The value is per registration
80
- * (resolveLoaderData: an `ssr: false` registration is served its pin). The
81
- * pushes are per loader: one the route also registers without `ssr: false`
80
+ * (resolveLoaderData: an `ssr: "await"` registration is served its pin). The
81
+ * pushes are per loader: one the route also registers without `ssr: "await"`
82
82
  * is a hole whatever its other registration's pin says, because the capture
83
83
  * credited its pushes to the live registration's run (routeLoaderLanes).
84
84
  *
85
85
  * A dependency the route does not register has no pin of its own, and the
86
86
  * record does not name the loader that ran it at capture: its copies stand
87
- * only while every `ssr: false` loader is pinned (exact when the pins are
87
+ * only while every `ssr: "await"` loader is pinned (exact when the pins are
88
88
  * all there or all gone).
89
89
  */
90
90
  export declare function loaderPins(entries: readonly EntryData[], reqCtx: RequestContext<any> | undefined): OwnedPushDelivery;
@@ -108,7 +108,7 @@ export declare function loaderPins(entries: readonly EntryData[], reqCtx: Reques
108
108
  * never run under a shell capture), so the lane is decided here, per LOADER,
109
109
  * never by the entry's loading():
110
110
  *
111
- * - BAKE lane: `loader(Def, { ssr: false })` (LoaderEntry `bake`: the
111
+ * - BAKE lane: `loader(Def, { ssr: "await" })` (LoaderEntry `bake`: the
112
112
  * caller passes a `bakeSegmentKey` for it on document and navigation
113
113
  * evaluations alike; a capture, a document evaluation, also has
114
114
  * awaitBeforeFlush). The loader EXECUTES at capture (the
@@ -130,7 +130,7 @@ export declare function loaderPins(entries: readonly EntryData[], reqCtx: Reques
130
130
  * (loader-mask.ts) and postpones at the reader's boundary — loading() or an
131
131
  * inline Suspense — then streams fresh per request. A masked read with NO
132
132
  * boundary above it root-postpones and the <body> sanity gate refuses the
133
- * shell (eternal-MISS warning). A route whose loaders are ALL `ssr: false`
133
+ * shell (eternal-MISS warning). A route whose loaders are ALL `ssr: "await"`
134
134
  * needs no loading(): nothing masks.
135
135
  */
136
136
  export declare function resolveLoaderData<TEnv>(loaderEntry: LoaderEntry, ctx: HandlerContext<any, TEnv>, pathname: string, bakeSegmentKey?: string | null): Promise<any>;
@@ -35,7 +35,7 @@ export interface SegmentResolutionDeps<TEnv = any> {
35
35
  env?: TEnv;
36
36
  isPartial?: boolean;
37
37
  },
38
- /** An `ssr: false` loader on a document or capture render. */
38
+ /** An `ssr: "await"` loader on a document or capture render. */
39
39
  useDefaultErrorBoundary?: boolean) => Promise<LoaderDataResult<T>>;
40
40
  trackHandler: <T>(promise: Promise<T>, errorContext?: {
41
41
  segmentId?: string;
@@ -148,12 +148,12 @@ export declare const SHELL_CAPTURE_TASK_HARD_CAP_MS = 25000;
148
148
  * (ShellCaptureDebugEvent.refusal, `caches.refusal` on a `router.prerender()`
149
149
  * result): `identity` (a request-scoped read: cookies(), headers(), the
150
150
  * theme, a `{ cache: false }` variable), `dynamic` (`ctx.dynamic()`),
151
- * `loader` (a `{ ssr: false }` loader rejected, settled with
151
+ * `loader` (a `{ ssr: "await" }` loader rejected, settled with
152
152
  * redirect()/notFound(), or a promise it pushed rejected),
153
153
  * `no-record` (no doc record: the route's cache() refused the write, or the
154
154
  * handler output failed), `handles` (a pushed
155
155
  * handle value did not finish encoding), `size` (over the store's value
156
- * limit, or `ssr: false` loader pins over `ppr.maxSnapshotBytes`: a
156
+ * limit, or `ssr: "await"` loader pins over `ppr.maxSnapshotBytes`: a
157
157
  * navigation-only entry was stored in place of the document shell),
158
158
  * `record-expired` (the route cache() record
159
159
  * the capture wrote ran out first), `invalidated` (a tag of the shell was
@@ -119,7 +119,7 @@ export type LoaderEntry = {
119
119
  cache?: EntryCacheConfig;
120
120
  /**
121
121
  * Document renders await this loader before segment resolution returns
122
- * (loader(Def, { ssr: false })), so its data, handle pushes, and
122
+ * (loader(Def, { ssr: "await" })), so its data, handle pushes, and
123
123
  * thrown notFound()/redirect() deterministically precede first flush.
124
124
  * Resolved at DSL-evaluation time from ctx.isSSR — entries are cached
125
125
  * per-isSSR (router/manifest.ts cache key), so the flag is already
@@ -129,7 +129,7 @@ export type LoaderEntry = {
129
129
  */
130
130
  awaitBeforeFlush?: true;
131
131
  /**
132
- * loader(Def, { ssr: false }) on every DSL evaluation, document and
132
+ * loader(Def, { ssr: "await" }) on every DSL evaluation, document and
133
133
  * navigation: the loader's PPR bake lane (loader-cache.ts
134
134
  * resolveLoaderData). The bake/seed key rides on it, so a client
135
135
  * navigation's shell replay pins exactly the loaders a document HIT pins,
@@ -491,7 +491,7 @@ export type { HelperContext };
491
491
  * parent pointer, handler) stays shared, which is correct and cheap.
492
492
  */
493
493
  export declare function getIsolatedLazyParent(captured: EntryData | null | undefined): EntryData | null;
494
- /** loader(Def, { ssr: false }): a document awaits it before the first flush. */
494
+ /** loader(Def, { ssr: "await" }): a document awaits it before the first flush. */
495
495
  export declare function documentModeLoader(entry: LoaderEntry): LoaderEntry;
496
496
  /** loading(fallback, { ssr: false }): a document renders no fallback. */
497
497
  export declare function applyDocumentModeLoading(entry: EntryPropRender): void;
@@ -710,7 +710,7 @@ export interface RequestContext<TEnv = DefaultEnv, TParams = Record<string, stri
710
710
  _handlerLoaderDeps?: Set<string>;
711
711
  /**
712
712
  * @internal Loader IDs ($$id) whose entries carry `awaitBeforeFlush`
713
- * (loader(Def, { ssr: false })): segment resolution awaits these
713
+ * (loader(Def, { ssr: "await" })): segment resolution awaits these
714
714
  * before returning, so the render barrier cannot resolve until they settle.
715
715
  * rendered() checks this set to fail fast — a flagged loader awaiting the
716
716
  * barrier is a guaranteed cycle, not a race. Registered by resolveOwnerLoaders
@@ -171,7 +171,7 @@ export interface SSRDependencies<TEnv = unknown> {
171
171
  *
172
172
  * When UNSET, createSSRHandler and createShellCaptureHandler auto-raise to
173
173
  * Number.MAX_SAFE_INTEGER for document renders and shell captures whose
174
- * matched chain has a `loader(Def, { ssr: false })` entry (payload segments
174
+ * matched chain has a `loader(Def, { ssr: "await" })` entry (payload segments
175
175
  * carry `awaitBeforeFlush`): the loader was awaited before first flush
176
176
  * precisely so its content ships in-place, and React 19.2's Fizz would
177
177
  * otherwise move any completed boundary over ~500 bytes to an end-of-stream
@@ -30,7 +30,7 @@ export interface RscPayload {
30
30
  * Deserialize the Flight stream branch into the payload SsrRoot renders,
31
31
  * fragment expansion included. Factored out of SsrRoot so createSSRHandler and
32
32
  * createShellCaptureHandler can create the promise BEFORE fizz starts and read
33
- * metadata that must influence the fizz call itself (the ssr:false
33
+ * metadata that must influence the fizz call itself (the ssr: "await"
34
34
  * progressiveChunkSize auto-raise) — awaiting it there is latency-neutral
35
35
  * because fizz cannot emit even <html> until this same promise settles.
36
36
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Dev-only diagnostic for the { ssr: false } SSR-completeness opt-in.
2
+ * Dev-only diagnostic for the { ssr: "await" } SSR-completeness opt-in.
3
3
  *
4
4
  * A document render that awaits flagged loaders before first flush is usually
5
5
  * expected to produce an SSR-complete boundary — but the await is scoped per
@@ -63,7 +63,7 @@ export type LoaderContext<TParams = Record<string, string | undefined>, TEnv = D
63
63
  * later ones stream to the client and apply post-hydration (document lane)
64
64
  * or progressively (navigation/action lanes). To guarantee a loader's
65
65
  * handles are in the SSR'd document, register it as
66
- * `loader(Def, { ssr: false })` so the document render awaits it
66
+ * `loader(Def, { ssr: "await" })` so the document render awaits it
67
67
  * (see {@link LoaderOptions}).
68
68
  *
69
69
  * @example
@@ -150,29 +150,29 @@ export type LoaderFn<T, TParams = Record<string, string | undefined>, TEnv = Def
150
150
  /**
151
151
  * SSR delivery for a DSL-registered loader: `loader(Def, { ssr })`.
152
152
  *
153
- * Default (omitted / `true`): the loader streams on every render. Its data,
154
- * its `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it throws
155
- * may land AFTER the document Response is constructed, so none of them are
156
- * guaranteed to be in the SSR'd HTML.
153
+ * Default (omitted / `"stream"`): the loader streams on every render. Its
154
+ * data, its `ctx.use(Handle)` pushes, and any `notFound()`/`redirect()` it
155
+ * throws may land AFTER the document Response is constructed, so none of
156
+ * them are guaranteed to be in the SSR'd HTML.
157
157
  *
158
- * `ssr: false` turns SSR streaming off for this loader — the same knob
159
- * `loading(fallback, { ssr: false })` is for the fallback: on a DOCUMENT
160
- * request the loader is awaited before first flush, so no fallback paints for
161
- * it (`useLoader` still suspends, but on an already-settled promise). Client
162
- * navigations keep streaming it. This is the SSR-completeness opt-in. Choose
163
- * it when the loader feeds something that must exist in the document:
164
- * `<head>` meta via a handle, or a real 404 status (an awaited `notFound()`
165
- * deterministically precedes Response construction, where the streamed
166
- * default only wins that race opportunistically). Under ppr it is also the
167
- * BAKE lane: the loader executes at shell capture and its settled return is
158
+ * `ssr: "await"` makes the server renders that produce HTML await this
159
+ * loader: on a DOCUMENT request the loader is awaited before first flush, so
160
+ * no fallback paints for it (`useLoader` still suspends, but on an
161
+ * already-settled promise); a ppr shell capture, at runtime or at build time
162
+ * for a Prerender + ppr route, awaits it too. Client navigations keep
163
+ * streaming it. This is the SSR-completeness opt-in. Choose it when the
164
+ * loader feeds something that must exist in the document: `<head>` meta via
165
+ * a handle, or a real 404 status (an awaited `notFound()` deterministically
166
+ * precedes Response construction, where the streamed default only wins
167
+ * that race opportunistically). Under ppr it is also the BAKE lane: the loader executes at shell capture and its settled return is
168
168
  * shell material (nested promises stay live holes), and so are its handle
169
169
  * pushes, a pushed promise or `.defer()` included: the capture awaits it and
170
170
  * every HIT serves the value it settled to (#1182). A pushed promise that
171
171
  * rejects, or misses `ppr.captureTimeout`, keeps the route on MISS. A value
172
- * that must stay per request belongs to a loader without `ssr: false` — see
172
+ * that must stay per request belongs to a loader without `ssr: "await"` — see
173
173
  * the ppr docs.
174
174
  *
175
- * Scoped per LOADER, not per segment: an `ssr: false` loader alongside a
175
+ * Scoped per LOADER, not per segment: an `ssr: "await"` loader alongside a
176
176
  * deliberately streaming sibling awaits only itself, and the sibling keeps
177
177
  * streaming behind its `loading()`/Suspense boundary. Watch the scoping when
178
178
  * the boundary must be SSR-complete: ONE unflagged read under it still
@@ -181,7 +181,7 @@ export type LoaderFn<T, TParams = Record<string, string | undefined>, TEnv = Def
181
181
  * site naming the unflagged loader (ssr-suspension-warning.ts).
182
182
  */
183
183
  export type LoaderOptions = {
184
- ssr?: boolean;
184
+ ssr?: "stream" | "await";
185
185
  /**
186
186
  * `prefetch: false` keeps this loader out of every `<Link>` prefetch: a
187
187
  * prefetch (a partial request with `X-Rango-Prefetch`) does not execute
@@ -211,7 +211,7 @@ export type LoaderOptions = {
211
211
  * Limits: a handler that awaits the loader with `ctx.use()` makes it run in
212
212
  * the prefetch anyway; the loader cannot call `ctx.rendered()` (it throws
213
213
  * on every request); on a `ppr` route the flag is ignored for an
214
- * `ssr: false` loader (the bake lane), with a dev warning. In the fill
214
+ * `ssr: "await"` loader (the bake lane), with a dev warning. In the fill
215
215
  * request only deferred work and middleware run, so values a held handler
216
216
  * would have set with `ctx.set()` are not visible.
217
217
  */
@@ -150,7 +150,7 @@ export interface ResolvedSegment {
150
150
  loaderData?: any;
151
151
  /**
152
152
  * True when this loader was awaited before first flush
153
- * (loader(Def, { ssr: false })). Stamped by resolveOwnerLoaders on
153
+ * (loader(Def, { ssr: "await" })). Stamped by resolveOwnerLoaders on
154
154
  * document AND shell-capture renders — capture bakes flagged loaders and
155
155
  * awaits the same lane. Feeds segment-system's settled-value delivery and
156
156
  * the dev SSR suspension warning (ssr-suspension-warning.ts).
@@ -170,7 +170,7 @@ export interface ResolvedSegment {
170
170
  loaderIds?: string[];
171
171
  /**
172
172
  * Per-loader UNDECODED results for a layout/route stream map.
173
- * Flagged (ssr:false) entries are settled values; unflagged siblings
173
+ * Flagged (ssr: "await") entries are settled values; unflagged siblings
174
174
  * stay promises. Parallel slots do not use this channel — they pin
175
175
  * loading() / live-lane holes via use(loaderDataPromise).
176
176
  */