@rangojs/router 0.22.1 → 0.24.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 (259) hide show
  1. package/dist/bin/rango.js +10 -4
  2. package/dist/testing/vitest.js +31 -1
  3. package/dist/types/browser/defer.d.ts +48 -0
  4. package/dist/types/browser/document-revealed.d.ts +17 -0
  5. package/dist/types/browser/event-controller.d.ts +7 -1
  6. package/dist/types/browser/merge-segment-loaders.d.ts +5 -2
  7. package/dist/types/browser/navigation-transaction.d.ts +21 -7
  8. package/dist/types/browser/partial-update.d.ts +39 -8
  9. package/dist/types/browser/react/NavigationProvider.d.ts +19 -5
  10. package/dist/types/browser/react/context.d.ts +3 -2
  11. package/dist/types/browser/react/filter-segment-order.d.ts +7 -0
  12. package/dist/types/browser/response-adapter.d.ts +7 -0
  13. package/dist/types/browser/types.d.ts +24 -10
  14. package/dist/types/browser/validate-redirect-origin.d.ts +11 -0
  15. package/dist/types/build/generate-route-types.d.ts +1 -1
  16. package/dist/types/build/route-types/router-processing.d.ts +1 -1
  17. package/dist/types/build/route-types/scan-filter.d.ts +22 -2
  18. package/dist/types/cache/cf/cf-cache-store.d.ts +16 -1
  19. package/dist/types/cache/cf/cf-cache-types.d.ts +23 -2
  20. package/dist/types/cache/document-cache-policy.d.ts +24 -0
  21. package/dist/types/cache/document-cache.d.ts +2 -10
  22. package/dist/types/client-urls/client-urls.d.ts +1 -1
  23. package/dist/types/client-urls/hoisted-revalidate.d.ts +13 -0
  24. package/dist/types/client-urls/navigation.d.ts +0 -37
  25. package/dist/types/client-urls/revalidate-registry.d.ts +4 -0
  26. package/dist/types/client-urls/server-projection.d.ts +29 -1
  27. package/dist/types/client-urls/types.d.ts +34 -69
  28. package/dist/types/client.d.ts +0 -1
  29. package/dist/types/context-var.d.ts +12 -0
  30. package/dist/types/errors.d.ts +15 -0
  31. package/dist/types/handles/deferred-resolution.d.ts +2 -1
  32. package/dist/types/handles/pushed-by.d.ts +31 -0
  33. package/dist/types/index.d.ts +4 -0
  34. package/dist/types/index.rsc.d.ts +2 -0
  35. package/dist/types/loader-store.d.ts +8 -0
  36. package/dist/types/prefetch-markers.d.ts +11 -0
  37. package/dist/types/response-utils.d.ts +7 -0
  38. package/dist/types/reverse.d.ts +8 -8
  39. package/dist/types/route-definition/helpers-types.d.ts +27 -0
  40. package/dist/types/route-definition/rewrite.d.ts +56 -0
  41. package/dist/types/route-map-builder.d.ts +7 -2
  42. package/dist/types/router/basename.d.ts +7 -0
  43. package/dist/types/router/handler-context.d.ts +1 -5
  44. package/dist/types/router/internal-params.d.ts +8 -0
  45. package/dist/types/router/is-action.d.ts +2 -1
  46. package/dist/types/router/middleware-types.d.ts +15 -0
  47. package/dist/types/router/revalidation.d.ts +1 -2
  48. package/dist/types/router/route-trie-builder.d.ts +10 -0
  49. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -2
  50. package/dist/types/router/segment-resolution/mask-nested.d.ts +4 -2
  51. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +264 -0
  52. package/dist/types/rsc/full-payload.d.ts +11 -0
  53. package/dist/types/rsc/handler.d.ts +33 -0
  54. package/dist/types/rsc/helpers.d.ts +8 -0
  55. package/dist/types/rsc/loader-fetch.d.ts +16 -0
  56. package/dist/types/rsc/rewrite-passes.d.ts +74 -0
  57. package/dist/types/rsc/ssr-setup.d.ts +3 -1
  58. package/dist/types/server/context.d.ts +21 -1
  59. package/dist/types/server/handle-store.d.ts +9 -1
  60. package/dist/types/server/request-context.d.ts +52 -2
  61. package/dist/types/server/rewrite-trail.d.ts +49 -0
  62. package/dist/types/server/slot-accessors.d.ts +29 -0
  63. package/dist/types/server.d.ts +1 -0
  64. package/dist/types/testing/flight.entry.d.ts +1 -1
  65. package/dist/types/testing/index.d.ts +4 -4
  66. package/dist/types/testing/internal/action-id.d.ts +10 -1
  67. package/dist/types/testing/internal/client-urls-projection.d.ts +16 -0
  68. package/dist/types/testing/internal/test-action.d.ts +8 -0
  69. package/dist/types/testing/render-route.d.ts +8 -0
  70. package/dist/types/testing/run-loader.d.ts +2 -1
  71. package/dist/types/testing/run-middleware.d.ts +20 -0
  72. package/dist/types/testing/run-revalidate.d.ts +74 -0
  73. package/dist/types/testing/run-transition-when.d.ts +2 -3
  74. package/dist/types/testing/serve-shell-request.d.ts +40 -1
  75. package/dist/types/testing/vitest.d.ts +11 -0
  76. package/dist/types/thenable-status.d.ts +6 -0
  77. package/dist/types/types/boundaries.d.ts +9 -0
  78. package/dist/types/types/handler-context.d.ts +14 -0
  79. package/dist/types/types/loader-types.d.ts +30 -0
  80. package/dist/types/types/request-scope.d.ts +7 -0
  81. package/dist/types/types/segments.d.ts +21 -4
  82. package/dist/types/urls/path-helper-types.d.ts +25 -4
  83. package/dist/types/urls/type-extraction.d.ts +6 -2
  84. package/dist/types/vite/discovery/discover-routers.d.ts +9 -0
  85. package/dist/types/vite/discovery/state.d.ts +14 -4
  86. package/dist/types/vite/plugins/client-urls-revalidate-hoist.d.ts +54 -0
  87. package/dist/types/vite/plugins/hoist/literal.d.ts +83 -0
  88. package/dist/types/vite/plugins/hoist/scope.d.ts +40 -0
  89. package/dist/types/vite/plugins/hoist/source.d.ts +41 -0
  90. package/dist/types/vite/plugins/transition-when-hoist.d.ts +8 -11
  91. package/dist/types/vite/router-discovery.d.ts +10 -0
  92. package/dist/vite/index.js +974 -379
  93. package/package.json +1 -1
  94. package/skills/caching/SKILL.md +54 -5
  95. package/skills/catalog.json +3 -3
  96. package/skills/client-urls/SKILL.md +197 -24
  97. package/skills/cloudflare/SKILL.md +14 -9
  98. package/skills/hooks/SKILL.md +16 -3
  99. package/skills/hooks/data.md +3 -1
  100. package/skills/links/SKILL.md +24 -0
  101. package/skills/loader/SKILL.md +199 -2
  102. package/skills/middleware/SKILL.md +183 -2
  103. package/skills/migrate-nextjs/SKILL.md +2 -2
  104. package/skills/migrate-react-router/SKILL.md +10 -7
  105. package/skills/migrate-react-router/data-and-actions.md +1 -1
  106. package/skills/ppr/SKILL.md +22 -0
  107. package/skills/prerender/SKILL.md +22 -8
  108. package/skills/rango/SKILL.md +13 -7
  109. package/skills/route/SKILL.md +139 -0
  110. package/skills/server-actions/SKILL.md +27 -0
  111. package/skills/testing/SKILL.md +3 -2
  112. package/skills/testing/client-components.md +2 -1
  113. package/skills/testing/middleware.md +31 -10
  114. package/skills/testing/predicates.md +67 -32
  115. package/skills/testing/response-routes.md +1 -0
  116. package/skills/testing/serve-shell-request.md +107 -19
  117. package/skills/testing/setup.md +2 -2
  118. package/skills/typesafety/route-types.md +2 -2
  119. package/skills/use-cache/SKILL.md +4 -2
  120. package/skills/view-transitions/SKILL.md +26 -0
  121. package/src/__augment-tests__/augmented.check.ts +20 -0
  122. package/src/browser/defer.ts +82 -0
  123. package/src/browser/document-revealed.ts +52 -0
  124. package/src/browser/event-controller.ts +46 -32
  125. package/src/browser/merge-segment-loaders.ts +31 -18
  126. package/src/browser/navigation-bridge.ts +82 -16
  127. package/src/browser/navigation-client.ts +48 -36
  128. package/src/browser/navigation-store.ts +13 -0
  129. package/src/browser/navigation-transaction.ts +25 -8
  130. package/src/browser/partial-update.ts +554 -146
  131. package/src/browser/prefetch/fetch.ts +2 -1
  132. package/src/browser/react/Link.tsx +7 -9
  133. package/src/browser/react/NavigationProvider.tsx +88 -49
  134. package/src/browser/react/context.ts +3 -2
  135. package/src/browser/react/filter-segment-order.ts +13 -2
  136. package/src/browser/react/use-router.ts +2 -4
  137. package/src/browser/response-adapter.ts +20 -0
  138. package/src/browser/rsc-router.tsx +17 -45
  139. package/src/browser/server-action-bridge.ts +25 -63
  140. package/src/browser/types.ts +24 -10
  141. package/src/browser/validate-redirect-origin.ts +16 -0
  142. package/src/build/generate-route-types.ts +1 -0
  143. package/src/build/route-types/router-processing.ts +3 -6
  144. package/src/build/route-types/scan-filter.ts +45 -11
  145. package/src/cache/cf/cf-cache-store.ts +59 -21
  146. package/src/cache/cf/cf-cache-types.ts +24 -2
  147. package/src/cache/document-cache-policy.ts +131 -0
  148. package/src/cache/document-cache.ts +21 -107
  149. package/src/client-urls/client-urls.ts +22 -6
  150. package/src/client-urls/hoisted-revalidate.ts +25 -0
  151. package/src/client-urls/navigation.ts +0 -96
  152. package/src/client-urls/revalidate-registry.ts +30 -0
  153. package/src/client-urls/server-projection.ts +151 -65
  154. package/src/client-urls/types.ts +39 -71
  155. package/src/client.tsx +9 -5
  156. package/src/context-var.ts +32 -2
  157. package/src/errors.ts +34 -0
  158. package/src/handles/deferred-resolution.ts +28 -5
  159. package/src/handles/pushed-by.ts +148 -0
  160. package/src/index.rsc.ts +8 -0
  161. package/src/index.ts +7 -0
  162. package/src/loader-store.ts +18 -7
  163. package/src/prefetch-markers.ts +13 -0
  164. package/src/response-utils.ts +19 -0
  165. package/src/reverse.ts +17 -14
  166. package/src/route-content-wrapper.tsx +18 -4
  167. package/src/route-definition/dsl-helpers.ts +27 -4
  168. package/src/route-definition/helpers-types.ts +27 -1
  169. package/src/route-definition/redirect.ts +2 -15
  170. package/src/route-definition/rewrite.ts +146 -0
  171. package/src/route-map-builder.ts +14 -11
  172. package/src/router/basename.ts +20 -0
  173. package/src/router/handler-context.ts +3 -13
  174. package/src/router/intercept-resolution.ts +35 -16
  175. package/src/router/internal-params.ts +16 -0
  176. package/src/router/is-action.ts +2 -1
  177. package/src/router/loader-resolution.ts +85 -4
  178. package/src/router/match-api.ts +36 -1
  179. package/src/router/match-middleware/background-revalidation.ts +16 -4
  180. package/src/router/match-middleware/cache-lookup.ts +35 -7
  181. package/src/router/match-middleware/cache-store.ts +15 -1
  182. package/src/router/match-middleware/intercept-resolution.ts +4 -2
  183. package/src/router/match-result.ts +14 -2
  184. package/src/router/middleware-types.ts +16 -0
  185. package/src/router/middleware.ts +127 -49
  186. package/src/router/navigation-snapshot.ts +2 -1
  187. package/src/router/revalidation.ts +1 -1
  188. package/src/router/route-trie-builder.ts +28 -1
  189. package/src/router/segment-resolution/fresh.ts +22 -18
  190. package/src/router/segment-resolution/loader-cache.ts +2 -1
  191. package/src/router/segment-resolution/loader-mask.ts +5 -3
  192. package/src/router/segment-resolution/loader-snapshot.ts +18 -4
  193. package/src/router/segment-resolution/mask-nested.ts +8 -4
  194. package/src/router/segment-resolution/prefetch-deferral.ts +583 -0
  195. package/src/router/segment-resolution/revalidation.ts +215 -82
  196. package/src/rsc/full-payload.ts +37 -5
  197. package/src/rsc/handler.ts +82 -55
  198. package/src/rsc/helpers.ts +24 -0
  199. package/src/rsc/loader-fetch.ts +79 -24
  200. package/src/rsc/response-cache-serve.ts +18 -2
  201. package/src/rsc/response-route-handler.ts +4 -7
  202. package/src/rsc/rewrite-passes.ts +203 -0
  203. package/src/rsc/rsc-rendering.ts +54 -9
  204. package/src/rsc/shell-capture.ts +2 -1
  205. package/src/rsc/ssr-setup.ts +11 -5
  206. package/src/segment-loader-promise.ts +23 -20
  207. package/src/segment-system.tsx +60 -26
  208. package/src/server/context.ts +39 -5
  209. package/src/server/cookie-parse.ts +36 -12
  210. package/src/server/cookie-store.ts +7 -3
  211. package/src/server/handle-store.ts +152 -25
  212. package/src/server/request-context.ts +175 -64
  213. package/src/server/request-headers.ts +4 -14
  214. package/src/server/rewrite-trail.ts +190 -0
  215. package/src/server/slot-accessors.ts +57 -0
  216. package/src/server.ts +4 -0
  217. package/src/ssr/index.tsx +118 -16
  218. package/src/testing/action-form.ts +2 -11
  219. package/src/testing/dispatch.ts +138 -67
  220. package/src/testing/e2e/page-helpers.ts +3 -1
  221. package/src/testing/flight.entry.ts +1 -0
  222. package/src/testing/index.ts +7 -3
  223. package/src/testing/internal/action-id.ts +16 -1
  224. package/src/testing/internal/client-urls-projection.ts +70 -0
  225. package/src/testing/internal/test-action.ts +25 -0
  226. package/src/testing/render-route.tsx +16 -3
  227. package/src/testing/run-loader.ts +4 -1
  228. package/src/testing/run-middleware.ts +56 -11
  229. package/src/testing/run-revalidate.ts +142 -0
  230. package/src/testing/run-transition-when.ts +7 -8
  231. package/src/testing/serve-shell-request.ts +131 -8
  232. package/src/testing/vitest.ts +67 -1
  233. package/src/thenable-status.ts +11 -0
  234. package/src/types/boundaries.ts +12 -0
  235. package/src/types/handler-context.ts +14 -0
  236. package/src/types/loader-types.ts +30 -0
  237. package/src/types/request-scope.ts +8 -0
  238. package/src/types/segments.ts +26 -4
  239. package/src/urls/path-helper-types.ts +27 -5
  240. package/src/urls/type-extraction.ts +30 -10
  241. package/src/use-loader.tsx +18 -13
  242. package/src/vite/discovery/client-urls-projection.ts +20 -21
  243. package/src/vite/discovery/discover-routers.ts +94 -26
  244. package/src/vite/discovery/state.ts +28 -4
  245. package/src/vite/discovery/virtual-module-codegen.ts +22 -17
  246. package/src/vite/plugins/client-urls-revalidate-hoist.ts +564 -0
  247. package/src/vite/plugins/expose-internal-ids.ts +2 -3
  248. package/src/vite/plugins/hoist/literal.ts +417 -0
  249. package/src/vite/plugins/hoist/scope.ts +370 -0
  250. package/src/vite/plugins/hoist/source.ts +84 -0
  251. package/src/vite/plugins/transition-when-hoist.ts +113 -779
  252. package/src/vite/rango.ts +2 -0
  253. package/src/vite/router-discovery.ts +116 -53
  254. package/dist/types/client-urls/revalidate-chain.d.ts +0 -33
  255. package/dist/types/client-urls/revalidation-protocol.d.ts +0 -25
  256. package/dist/types/testing/run-client-revalidate.d.ts +0 -43
  257. package/src/client-urls/revalidate-chain.ts +0 -83
  258. package/src/client-urls/revalidation-protocol.ts +0 -56
  259. package/src/testing/run-client-revalidate.ts +0 -92
@@ -158,6 +158,15 @@ export interface ResolvedSegment {
158
158
  */
159
159
  awaitBeforeFlush?: true;
160
160
  parallelLoading?: ReactNode;
161
+ /**
162
+ * A prefetch skipped this segment's work (`prefetch: false`): a loader
163
+ * carries no `loaderData`, a layout/route/parallel unit carries
164
+ * `component: null` and its fallback. The browser shows the fallback and
165
+ * sends a fill request for it (browser/partial-update.ts); until that
166
+ * lands the segment is a placeholder, never a held copy. Only ever on a
167
+ * prefetch response: see docs/design/prefetch-false.md.
168
+ */
169
+ deferred?: true;
161
170
  loaderDataPromise?: Promise<any[]> | any[];
162
171
  loaderIds?: string[];
163
172
  /**
@@ -228,10 +237,12 @@ export interface MatchResult {
228
237
  /**
229
238
  * Every segment id whose handler actually ran on the server this request,
230
239
  * including ones with `component === null` that get filtered out of
231
- * `segments`/`diff` to avoid wasted bytes. Drives the client's handle-
232
- * cleanup pass — a slot that re-resolves and pushes nothing must clear
233
- * its previous handle bucket, but `diff` doesn't carry it because the
234
- * segment payload doesn't either. A superset of `diff`.
240
+ * `segments`/`diff` to avoid wasted bytes, and every loader segment that
241
+ * ran. Drives the client's handle merge (handles/pushed-by.ts) — a slot
242
+ * that re-resolves and pushes nothing must clear its previous handle
243
+ * bucket, but `diff` doesn't carry it because the segment payload doesn't
244
+ * either; a loader that ran replaces its pushes, one that did not keeps
245
+ * them. A superset of `diff`.
235
246
  */
236
247
  resolvedIds: string[];
237
248
  /**
@@ -279,3 +290,9 @@ export interface MatchResult {
279
290
  params: Record<string, string>;
280
291
  }>;
281
292
  }
293
+ /**
294
+ * A `prefetch: false` placeholder (ResolvedSegment.deferred): a segment whose
295
+ * work a prefetch skipped, waiting for its fill. Never a held copy of the
296
+ * segment it stands for.
297
+ */
298
+ export declare function isPlaceholder(segment: Pick<ResolvedSegment, "deferred">): boolean;
@@ -1,6 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { ErrorBoundaryHandler, ExtractParams, Handler, HandlerContext, LoaderDefinition, LoaderOptions, MiddlewareFn, NotFoundBoundaryHandler, PartialCacheOptions, ShouldRevalidateFn, TransitionConfig } from "../types.js";
3
- import type { AllUseItems, TypedLayoutItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ResponseRouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, TypedCacheItem, TransitionItem, TypedTransitionItem, TypedRouteItem, TypedIncludeItem, UseItems } from "../route-types.js";
3
+ import type { AllUseItems, TypedLayoutItem, ParallelItem, InterceptItem, MiddlewareItem, RevalidateItem, LoaderItem, LoadingItem, ErrorBoundaryItem, NotFoundBoundaryItem, LayoutUseItem, RouteUseItem, ResponseRouteUseItem, ParallelUseItem, InterceptUseItem, LoaderUseItem, TypedCacheItem, TypedTransitionItem, TypedRouteItem, TypedIncludeItem, UseItems } from "../route-types.js";
4
4
  import type { SearchSchema } from "../search-params.js";
5
5
  import type { PrerenderHandlerDefinition, PassthroughHandlerDefinition } from "../prerender.js";
6
6
  import type { StaticHandlerDefinition, StaticHandlerRef } from "../static-handler.js";
@@ -158,13 +158,34 @@ export type PathHelpers<TEnv> = {
158
158
  * flag BAKES: the loader executes at shell capture and its settled return
159
159
  * freezes into the shell (nested promises stay live holes) — the pre-flush
160
160
  * promise applied to the prelude.
161
+ *
162
+ * Pass `{ prefetch: false }` to keep the loader out of prefetches: a
163
+ * prefetch skips it and the click that adopts the prefetch fetches it in
164
+ * one follow-up request (see {@link LoaderOptions}). Only on a segment the
165
+ * client does not have yet: a loader the client already holds runs in the
166
+ * prefetch when it revalidates.
161
167
  */
162
168
  loader: <TData>(loaderDef: LoaderDefinition<TData>, optionsOrUse?: LoaderOptions | (() => LoaderUseItem[]), use?: () => LoaderUseItem[]) => LoaderItem;
163
169
  /**
164
- * Attach a loading component to the current route/layout
170
+ * Attach a loading component to the current route/layout.
171
+ *
172
+ * `{ ssr: false }` skips the fallback on document requests. `{ prefetch:
173
+ * false }` keeps what the fallback covers out of prefetches, when this
174
+ * entry's segment is new to the client: the entry's handler, its loaders,
175
+ * its orphan layouts and parallel slots and, for a layout, every deeper
176
+ * entry. The click that adopts the prefetch shows the fallback and fetches
177
+ * them in one follow-up request, which reads cache() and "use cache" as
178
+ * any request does (a prefetch reads neither for skipped work). A segment
179
+ * the client already holds is never deferred: a prefetch renders it as
180
+ * usual when it revalidates, and what is below it follows its own flags.
181
+ * To keep a child out of prefetches while moving inside a section, flag
182
+ * the child's own loading(). An entry whose handler output is stored
183
+ * (cache(), ppr, Prerender/Static) is served as usual and only its loaders
184
+ * are skipped.
165
185
  */
166
186
  loading: (component: ReactNode | (() => ReactNode), options?: {
167
187
  ssr?: boolean;
188
+ prefetch?: boolean;
168
189
  }) => LoadingItem;
169
190
  /**
170
191
  * Attach an error boundary to catch errors in this segment
@@ -199,8 +220,8 @@ export type PathHelpers<TEnv> = {
199
220
  * the browser (never on the server); see TransitionWhenFn.
200
221
  */
201
222
  transition: {
202
- (): TransitionItem;
203
- (config: TransitionConfig): TransitionItem;
223
+ (): TypedTransitionItem<{}, {}>;
224
+ (config: TransitionConfig): TypedTransitionItem<{}, {}>;
204
225
  <const TChildren extends readonly (AllUseItems | readonly AllUseItems[])[]>(children: () => TChildren): TypedTransitionItem<ExtractRoutes<TChildren>, ExtractResponses<TChildren>>;
205
226
  <const TChildren extends readonly (AllUseItems | readonly AllUseItems[])[]>(config: TransitionConfig, children: () => TChildren): TypedTransitionItem<ExtractRoutes<TChildren>, ExtractResponses<TChildren>>;
206
227
  };
@@ -1,6 +1,10 @@
1
1
  import type { JsonSerialize } from "../serialize.js";
2
2
  import type { TypedRouteItem, TypedIncludeItem, TypedLayoutItem, TypedCacheItem, TypedTransitionItem } from "../route-types.js";
3
3
  import type { LocalOnlyInclude, UnnamedRoute } from "./pattern-types.js";
4
+ /** Drops string index signatures, keeping literal and template keys. */
5
+ type StripStringIndex<T> = {
6
+ [K in keyof T as string extends K ? never : K]: T[K];
7
+ };
4
8
  /**
5
9
  * Prefix route names with a given prefix (e.g., "blog" + "post" = "blog.post")
6
10
  *
@@ -52,7 +56,7 @@ type ExtractRoutesFromItem<T> = T extends TypedRouteItem<infer TName, infer TPat
52
56
  readonly path: TPattern;
53
57
  readonly search: TSearch;
54
58
  };
55
- } : {} : T extends TypedIncludeItem<infer TRoutes, infer TNamePrefix, infer TUrlPrefix> ? TNamePrefix extends LocalOnlyInclude ? {} : TNamePrefix extends string ? TUrlPrefix extends string ? PrefixRoutes<PrefixPatterns<TRoutes, TUrlPrefix>, TNamePrefix> : PrefixRoutes<TRoutes, TNamePrefix> : TUrlPrefix extends string ? PrefixPatterns<TRoutes, TUrlPrefix> : TRoutes : T extends TypedLayoutItem<infer TChildRoutes> ? TChildRoutes : T extends TypedCacheItem<infer TChildRoutes> ? TChildRoutes : T extends TypedTransitionItem<infer TChildRoutes> ? TChildRoutes : {};
59
+ } : {} : T extends TypedIncludeItem<infer TRoutes, infer TNamePrefix, infer TUrlPrefix> ? TNamePrefix extends LocalOnlyInclude ? {} : TNamePrefix extends string ? TUrlPrefix extends string ? PrefixRoutes<PrefixPatterns<TRoutes, TUrlPrefix>, TNamePrefix> : PrefixRoutes<TRoutes, TNamePrefix> : TUrlPrefix extends string ? PrefixPatterns<TRoutes, TUrlPrefix> : TRoutes : T extends TypedLayoutItem<infer TChildRoutes> ? StripStringIndex<TChildRoutes> : T extends TypedCacheItem<infer TChildRoutes> ? StripStringIndex<TChildRoutes> : T extends TypedTransitionItem<infer TChildRoutes> ? StripStringIndex<TChildRoutes> : {};
56
60
  /**
57
61
  * Extract routes from an array of items using mapped types.
58
62
  * Uses UnionToIntersection to combine routes without recursive tuple processing,
@@ -82,7 +86,7 @@ type PrefixKeys<T extends Record<string, unknown>, TPrefix extends string> = TPr
82
86
  */
83
87
  type ExtractResponsesFromItem<T> = T extends TypedRouteItem<infer TName, any, infer TData> ? TName extends string ? string extends TName ? {} : TName extends UnnamedRoute ? {} : {
84
88
  [K in TName]: TData;
85
- } : {} : T extends TypedIncludeItem<any, infer TNamePrefix, any, infer TResponses> ? TNamePrefix extends LocalOnlyInclude ? {} : TNamePrefix extends string ? TResponses extends Record<string, unknown> ? PrefixKeys<TResponses, TNamePrefix> : {} : TResponses extends Record<string, unknown> ? TResponses : {} : T extends TypedLayoutItem<any, infer TChildResponses> ? TChildResponses extends Record<string, unknown> ? TChildResponses : {} : T extends TypedCacheItem<any, infer TChildResponses> ? TChildResponses extends Record<string, unknown> ? TChildResponses : {} : T extends TypedTransitionItem<any, infer TChildResponses> ? TChildResponses extends Record<string, unknown> ? TChildResponses : {} : {};
89
+ } : {} : T extends TypedIncludeItem<any, infer TNamePrefix, any, infer TResponses> ? TNamePrefix extends LocalOnlyInclude ? {} : TNamePrefix extends string ? TResponses extends Record<string, unknown> ? PrefixKeys<TResponses, TNamePrefix> : {} : TResponses extends Record<string, unknown> ? TResponses : {} : T extends TypedLayoutItem<any, infer TChildResponses> ? StripStringIndex<TChildResponses> extends infer R extends Record<string, unknown> ? R : {} : T extends TypedCacheItem<any, infer TChildResponses> ? StripStringIndex<TChildResponses> extends infer R extends Record<string, unknown> ? R : {} : T extends TypedTransitionItem<any, infer TChildResponses> ? StripStringIndex<TChildResponses> extends infer R extends Record<string, unknown> ? R : {} : {};
86
90
  /**
87
91
  * Extract responses from an array of items using mapped types.
88
92
  * Parallel to ExtractRoutesFromItems.
@@ -13,5 +13,14 @@ import type { DiscoveryState } from "./state.js";
13
13
  *
14
14
  * Returns the imported `@rangojs/router/server` module so the caller
15
15
  * can access the RouterRegistry and manifest setters.
16
+ *
17
+ * A pass reads the router registry and installs clientUrls projections on ONE
18
+ * `@rangojs/router/server` instance, and checks that the runner still serves
19
+ * it. A runner full reload during the pass (a watched-file change at boot)
20
+ * splits the realm: a router module bound to the old instance registers there
21
+ * while urls modules re-evaluate against a fresh one, so include() looks its
22
+ * projection up in the fresh, empty Map ("include() could not resolve the
23
+ * server projection"). Such a pass is redone, at most
24
+ * MAX_DISCOVERY_PASSES times; the redo evaluates the entry in one realm.
16
25
  */
17
26
  export declare function discoverRouters(state: DiscoveryState, rscEnv: any, ssrEnv?: any): Promise<any>;
@@ -188,14 +188,24 @@ export interface DiscoveryState {
188
188
  /** Cleanup function for build-time env resources (e.g., miniflare). */
189
189
  buildEnvDispose?: (() => Promise<void> | void) | null;
190
190
  /**
191
- * Set when the most recent HMR re-discovery threw. Cleared on the next
192
- * successful discovery. Surfaced via debug logs so we can detect "manifest
193
- * frozen at last-good after error → user fix in non-route file → no
194
- * rediscovery trigger" scenarios.
191
+ * Set when the most recent dev discovery failed: an HMR re-discovery, or the
192
+ * cold discovery on the Node RSC runner or the Cloudflare temp runner.
193
+ * Cleared on the next successful discovery. While set, the route-file
194
+ * watcher treats any in-scan source change as a rediscovery trigger
195
+ * (recovery mode).
195
196
  */
196
197
  lastDiscoveryError?: {
197
198
  message: string;
198
199
  at: number;
199
200
  } | null;
200
201
  }
202
+ /** Record a failed dev discovery (lastDiscoveryError): recovery mode. */
203
+ export declare function recordDiscoveryFailure(state: Pick<DiscoveryState, "lastDiscoveryError">, err: unknown): void;
204
+ /**
205
+ * Whether the route-file watcher can re-run runtime discovery: a committed
206
+ * manifest, or a recorded failure. A failed cold discovery commits none, and
207
+ * without its recorded failure the dev server stayed broken until a restart,
208
+ * on the Node runner and on Cloudflare alike.
209
+ */
210
+ export declare function canRefreshRuntimeDiscovery(state: Pick<DiscoveryState, "perRouterManifests" | "lastDiscoveryError">): boolean;
201
211
  export declare function createDiscoveryState(entryPath: string | undefined, opts: PluginOptions | undefined): DiscoveryState & ClientUrlDiscoveryState;
@@ -0,0 +1,54 @@
1
+ import type { Plugin } from "vite";
2
+ import MagicString from "magic-string";
3
+ /**
4
+ * The key of the predicate `source` declared in `relativeFile` (relative to
5
+ * the project root): a hash of the file and the predicate's syntax, so the
6
+ * same predicate gets one key in every environment and however it is
7
+ * formatted.
8
+ */
9
+ export declare function revalidateKey(relativeFile: string, source: string): string;
10
+ interface HoistClientUrlsRevalidateDeps {
11
+ /** Project root: keys hash the file relative to it. */
12
+ readonly root: string;
13
+ /**
14
+ * Whether a module a predicate imports is a "use client" module beyond its
15
+ * specifier (the plugin resolves it and reads its directive prologue).
16
+ */
17
+ isClientModule?(source: string): Promise<boolean> | boolean;
18
+ }
19
+ interface HoistClientUrlsRevalidateResult {
20
+ /** The module with each predicate replaced by its marker. */
21
+ readonly code: string;
22
+ readonly map: ReturnType<MagicString["generateMap"]>;
23
+ /** The server module that registers every hoisted predicate. */
24
+ readonly module: string;
25
+ /** The keys, in declaration order, duplicates removed. */
26
+ readonly keys: readonly string[];
27
+ }
28
+ /**
29
+ * Hoist every clientUrls() `revalidate()` predicate of a "use client"
30
+ * module. Returns null when there is none. Throws, naming file:line, for a
31
+ * predicate that is not pure.
32
+ */
33
+ export declare function hoistClientUrlsRevalidates(code: string, id: string, deps: HoistClientUrlsRevalidateDeps): Promise<HoistClientUrlsRevalidateResult | null>;
34
+ /** The file a `?rango-revalidate` id names, or null. */
35
+ export declare function parseHoistedRevalidateId(id: string): string | null;
36
+ /**
37
+ * The plugins: a pre transform that hoists in every environment (markers in
38
+ * the client and SSR copies, the server module recorded from the RSC
39
+ * transform) and serves `?rango-revalidate`, and a post transform that
40
+ * appends its import to the RSC client-reference proxy.
41
+ *
42
+ * `load` serves the module the RSC transform emitted, which hoisted the
43
+ * pipeline code; the disk source is re-hoisted only when the transform has
44
+ * not seen the current file (loadEmittedOrDisk). A file whose edit removed
45
+ * every predicate serves an empty module to a proxy that still imports it.
46
+ *
47
+ * The transform's code filter skips a module that never mentions clientUrls,
48
+ * so a file that stops doing so would keep its `emitted` entry: watchChange
49
+ * drops it, together with the cached "use client" answer for an edited
50
+ * dependency (isClientModule memoizes per resolved file; buildStart resets
51
+ * it).
52
+ */
53
+ export declare function clientUrlsRevalidateHoistPlugin(): Plugin[];
54
+ export {};
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Literal-level helpers shared by the build-time predicate hoists
3
+ * (transition-when-hoist.ts moves a `when` from a server module into a
4
+ * "use client" one; client-urls-revalidate-hoist.ts moves a `revalidate()`
5
+ * from a "use client" module into a server one): finding a urls-callback
6
+ * helper's calls, the lexical-capture check, and the free imports a literal
7
+ * re-emits in its new module.
8
+ */
9
+ import { type ImportBinding, type Node, type Reference, type Scope } from "./scope.js";
10
+ /** The offset after the directive prologue (0 when there is none). */
11
+ export declare function directivePrologueEnd(program: Node): number;
12
+ export declare function parseLang(file: string): "ts" | "tsx" | "jsx";
13
+ export declare function unwrapExpression(node: Node): Node;
14
+ export declare function isFunctionLiteral(node: Node): boolean;
15
+ export declare function propertyKeyIs(prop: Node, name: string): boolean;
16
+ /** Which helper calls {@link findHelperCalls} looks for. */
17
+ interface HelperCallSpec {
18
+ /** Module specifiers the builder (and a direct helper import) come from. */
19
+ readonly sources: ReadonlySet<string>;
20
+ /** The builder export whose callback's first parameter hands out `helper`. */
21
+ readonly builder: string;
22
+ readonly helper: string;
23
+ /** A direct import of `helper` from `sources` counts too. */
24
+ readonly directImport: boolean;
25
+ }
26
+ interface HelperCall {
27
+ readonly call: Node;
28
+ readonly scope: Scope;
29
+ }
30
+ /**
31
+ * Every call of `spec.helper` the builder's callback hands out: destructured,
32
+ * renamed or as `helpers.<helper>`, at any depth of the callback, plus a
33
+ * direct import when `spec.directImport`. A builder is recognised by its
34
+ * import (named, renamed, or through a namespace); any other function of the
35
+ * same name is left alone.
36
+ */
37
+ export declare function findHelperCalls(program: Node, scopeOf: Map<Node, Scope>, moduleScope: Scope, imports: Map<string, ImportBinding>, spec: HelperCallSpec): HelperCall[];
38
+ /**
39
+ * What a lexical-capture error says, per hoist direction. `lexical` explains
40
+ * each construct (`this`, `arguments`, `super`, `new.target`); `captureHint`
41
+ * and `loadsHint` follow every capture and module-load message;
42
+ * `importMetaWhy`, `importMetaMemberWhy` and `dynamicImportWhy` explain the
43
+ * module-loading expressions.
44
+ */
45
+ export interface LexicalCaptureMessages {
46
+ readonly lexical: Readonly<Record<string, string>>;
47
+ readonly captureHint: string;
48
+ readonly loadsHint: string;
49
+ readonly importMetaWhy: string;
50
+ readonly importMetaMemberWhy: string;
51
+ readonly dynamicImportWhy: string;
52
+ }
53
+ /**
54
+ * The first use in a literal of a lexical context it would lose when hoisted
55
+ * (`this`, `arguments`, `super`, `new.target` of an enclosing function or
56
+ * object), or of a module-loading expression the build cannot check
57
+ * (`import()`, `import.meta` other than `import.meta.env`). A nested
58
+ * non-arrow function (or class body) owns its `this`/`arguments`, so uses
59
+ * inside one are fine; a method literal owns them too, but its `super`
60
+ * loses the object it was defined on.
61
+ */
62
+ export declare function findLexicalCapture(literal: Node, references: readonly Reference[], code: string, messages: LexicalCaptureMessages): {
63
+ node: Node;
64
+ detail: string;
65
+ } | null;
66
+ /** The free references of a literal, by what they resolve to. */
67
+ interface FreeReferences {
68
+ /** Import local name -> whether any use is a value use. */
69
+ readonly imports: Map<string, boolean>;
70
+ /** Value references to a binding declared outside the literal that is not an import. */
71
+ readonly bindings: readonly Reference[];
72
+ /** References declared nowhere (window, URL, console, ...). */
73
+ readonly globals: readonly Reference[];
74
+ }
75
+ /** Every reference inside `literal` to something declared outside it. */
76
+ export declare function collectFreeReferences(literal: Node, references: readonly Reference[], moduleScope: Scope, imports: Map<string, ImportBinding>): FreeReferences;
77
+ /**
78
+ * Re-emit the imports a literal uses, grouped per source, type-only uses as
79
+ * `import type`. `checkValueImport` runs first for each value use and throws
80
+ * to reject one.
81
+ */
82
+ export declare function emitImports(used: Map<string, boolean>, imports: Map<string, ImportBinding>, checkValueImport: (local: string, binding: ImportBinding) => Promise<void> | void): Promise<string[]>;
83
+ export {};
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Scope analysis shared by the build-time predicate hoists
3
+ * (transition-when-hoist.ts, client-urls-revalidate-hoist.ts): a module's
4
+ * scope tree, every identifier reference, and its imports. Enough of ES
5
+ * scoping for the free-variable analysis of one literal.
6
+ */
7
+ export type Node = any;
8
+ export interface ImportBinding {
9
+ readonly source: string;
10
+ /** "default", "*", or the imported name. */
11
+ readonly imported: string;
12
+ readonly typeOnly: boolean;
13
+ }
14
+ export declare class Scope {
15
+ readonly parent: Scope | null;
16
+ readonly isFunction: boolean;
17
+ readonly node: Node;
18
+ readonly declarations: Set<string>;
19
+ constructor(parent: Scope | null, isFunction: boolean, node: Node);
20
+ nearestFunction(): Scope;
21
+ lookup(name: string): Scope | null;
22
+ }
23
+ export interface Reference {
24
+ readonly node: Node;
25
+ readonly scope: Scope;
26
+ readonly typeOnly: boolean;
27
+ }
28
+ export declare const TYPE_KEYS: ReadonlySet<string>;
29
+ /**
30
+ * Scope tree and every identifier reference of a module, TS type positions
31
+ * flagged. Enough of ES scoping for free-variable analysis of one literal:
32
+ * var hoists to the function scope, let/const/class to the block, function
33
+ * declarations to their block.
34
+ */
35
+ export declare function analyzeScopes(program: Node): {
36
+ moduleScope: Scope;
37
+ references: Reference[];
38
+ scopeOf: Map<Node, Scope>;
39
+ };
40
+ export declare function collectImports(program: Node): Map<string, ImportBinding>;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Source reading shared by the build-time predicate hoists
3
+ * (transition-when-hoist.ts, client-urls-revalidate-hoist.ts): which ids are
4
+ * source files, reading one off disk, resolving an import to the file it
5
+ * names, and the "emitted or disk" lookup behind their `?rango-*` modules.
6
+ */
7
+ /** A file path (query already stripped) a hoist transform may rewrite. */
8
+ export declare const SOURCE_FILE_RE: RegExp;
9
+ export declare function readSource(file: string): Promise<string | null>;
10
+ /** The part of a plugin context {@link resolveSourceTarget} needs. */
11
+ export interface SourceResolver {
12
+ resolve(source: string, importer?: string): Promise<{
13
+ id: string;
14
+ external?: boolean | string;
15
+ } | null>;
16
+ }
17
+ /**
18
+ * The file `source` names when imported from `importer`, query stripped, or
19
+ * null when it is external or virtual. `skipNodeModules` also yields null for
20
+ * a dependency: transition-when-hoist only looks for `import "server-only"`
21
+ * in project files, while client-urls-revalidate-hoist must see the
22
+ * "use client" directive of a package's modules too.
23
+ */
24
+ export declare function resolveSourceTarget(ctx: SourceResolver, source: string, importer: string, options: {
25
+ readonly skipNodeModules: boolean;
26
+ }): Promise<string | null>;
27
+ /** What a hoist's RSC transform emitted for a file, and the disk source it saw. */
28
+ export interface EmittedEntry<T> {
29
+ readonly disk: string | null;
30
+ readonly value: T;
31
+ }
32
+ export declare function recordEmitted<T>(emitted: Map<string, EmittedEntry<T>>, file: string, value: T): Promise<void>;
33
+ /**
34
+ * What a `?rango-*` load serves for `hoistedFile`: the transform's emission
35
+ * when it saw the current disk source (or the file is gone), else
36
+ * `rehoist(disk)`. The transform hoists the pipeline code, which an earlier
37
+ * plugin may have changed, so re-hoisting the disk source is the fallback only
38
+ * for an environment loading before the RSC transform ran or for an edit the
39
+ * RSC environment has not re-transformed yet. Undefined when neither exists.
40
+ */
41
+ export declare function loadEmittedOrDisk<T>(emitted: ReadonlyMap<string, EmittedEntry<T>>, hoistedFile: string, rehoist: (disk: string) => Promise<T | undefined>): Promise<T | undefined>;
@@ -2,14 +2,14 @@ import type { Plugin } from "vite";
2
2
  import MagicString from "magic-string";
3
3
  /** Specifiers that can never load in the browser. */
4
4
  export declare function isServerOnlySpecifier(source: string): boolean;
5
- export interface HoistTransitionWhenDeps {
5
+ interface HoistTransitionWhenDeps {
6
6
  /**
7
7
  * Whether an import the literal re-emits is server-only beyond its
8
8
  * specifier (the plugin resolves it and looks for `import "server-only"`).
9
9
  */
10
10
  isServerOnlyImport?(source: string): Promise<boolean> | boolean;
11
11
  }
12
- export interface HoistTransitionWhenResult {
12
+ interface HoistTransitionWhenResult {
13
13
  readonly code: string;
14
14
  readonly map: ReturnType<MagicString["generateMap"]>;
15
15
  /** Virtual module source per hoisted literal, by index. */
@@ -32,14 +32,11 @@ export declare function parseHoistedWhenId(id: string): {
32
32
  * evaluated) and serves the `?rango-when=N` modules in every environment
33
33
  * (RSC, SSR and client all load them).
34
34
  *
35
- * `load` serves the modules the transform emitted, not a re-hoist of the file
36
- * on disk: the transform hoists the pipeline code, which an earlier plugin
37
- * may have changed, so re-hoisting the disk source can number the literals
38
- * differently and serve the wrong one for index N. The disk source is the
39
- * fallback only when the transform has not seen the current file: an
40
- * environment loading before the RSC transform ran (the order environments
41
- * ask in is not fixed), or an edit the RSC environment has not re-transformed
42
- * yet (serving the last transform would then be stale). The map is per
43
- * plugin instance, so every environment of one dev server or build shares it.
35
+ * `load` serves the modules the transform emitted (loadEmittedOrDisk), not a
36
+ * re-hoist of the file on disk: re-hoisting a disk source an earlier plugin
37
+ * changed can number the literals differently and serve the wrong one for
38
+ * index N. The map is per plugin instance, so every environment of one dev
39
+ * server or build shares it.
44
40
  */
45
41
  export declare function transitionWhenHoistPlugin(): Plugin;
42
+ export {};
@@ -8,6 +8,16 @@
8
8
  import type { Plugin, ViteDevServer } from "vite";
9
9
  import { VIRTUAL_ROUTES_MANIFEST_ID, type DiscoveryState, type PluginOptions } from "./discovery/state.js";
10
10
  export { VIRTUAL_ROUTES_MANIFEST_ID };
11
+ /** The slice of a Vite environment module graph the route-file watcher reads. */
12
+ interface ModulesByFileGraph {
13
+ getModulesByFile(file: string): ReadonlySet<unknown> | undefined;
14
+ }
15
+ /**
16
+ * Whether the dev route-file watcher ignores `file`: it sits in a directory
17
+ * every discovery scan skips (isInSkippedScanDir) and the RSC graph has not
18
+ * loaded it. See docs/manifests.md, "A reload no file of the app asked for".
19
+ */
20
+ export declare function isFileOutsideApp(root: string, file: string, rscGraph: ModulesByFileGraph | undefined): boolean;
11
21
  /**
12
22
  * Create a minimal Vite server for router discovery.
13
23
  *