@rangojs/router 0.18.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/dist/types/browser/event-controller.d.ts +3 -3
  2. package/dist/types/browser/history-state.d.ts +2 -0
  3. package/dist/types/browser/navigation-transaction.d.ts +20 -2
  4. package/dist/types/browser/partial-update.d.ts +16 -1
  5. package/dist/types/browser/prefetch/cache.d.ts +6 -6
  6. package/dist/types/browser/react/Link.d.ts +19 -9
  7. package/dist/types/browser/react/context.d.ts +2 -2
  8. package/dist/types/browser/react/location-state-shared.d.ts +95 -56
  9. package/dist/types/browser/react/location-state.d.ts +1 -1
  10. package/dist/types/browser/transition-when.d.ts +80 -0
  11. package/dist/types/browser/types.d.ts +104 -9
  12. package/dist/types/build/generate-manifest.d.ts +8 -0
  13. package/dist/types/cache/background-task.d.ts +24 -2
  14. package/dist/types/cache/cache-exec-scope.d.ts +37 -0
  15. package/dist/types/cache/cache-key-utils.d.ts +44 -13
  16. package/dist/types/cache/cache-policy.d.ts +1 -1
  17. package/dist/types/cache/cache-runtime.d.ts +7 -0
  18. package/dist/types/cache/cache-scope.d.ts +117 -35
  19. package/dist/types/cache/cache-tag.d.ts +48 -5
  20. package/dist/types/cache/cf/cf-cache-store.d.ts +55 -44
  21. package/dist/types/cache/cf/cf-shell-frame.d.ts +1 -1
  22. package/dist/types/cache/cf/cf-tag-marker-memo.d.ts +0 -15
  23. package/dist/types/cache/document-cache.d.ts +10 -0
  24. package/dist/types/cache/handle-snapshot.d.ts +40 -8
  25. package/dist/types/cache/invalidation-order.d.ts +23 -0
  26. package/dist/types/cache/request-tag-mask.d.ts +54 -0
  27. package/dist/types/cache/shell-snapshot.d.ts +88 -141
  28. package/dist/types/cache/tag-invalidation.d.ts +38 -14
  29. package/dist/types/cache/types.d.ts +68 -61
  30. package/dist/types/cache/vercel/vercel-cache-store.d.ts +23 -16
  31. package/dist/types/client-urls/navigation.d.ts +28 -2
  32. package/dist/types/client-urls/server-projection.d.ts +5 -2
  33. package/dist/types/client-urls/types.d.ts +5 -5
  34. package/dist/types/client.d.ts +1 -1
  35. package/dist/types/index.d.ts +1 -1
  36. package/dist/types/index.rsc.d.ts +1 -1
  37. package/dist/types/route-definition/helpers-types.d.ts +11 -11
  38. package/dist/types/route-name.d.ts +7 -0
  39. package/dist/types/router/error-handling.d.ts +1 -1
  40. package/dist/types/router/intercept-resolution.d.ts +2 -0
  41. package/dist/types/router/match-context.d.ts +5 -0
  42. package/dist/types/router/match-middleware/background-revalidation.d.ts +0 -4
  43. package/dist/types/router/router-options.d.ts +4 -1
  44. package/dist/types/router/segment-resolution/loader-cache.d.ts +18 -1
  45. package/dist/types/router/segment-resolution/loader-mask.d.ts +3 -4
  46. package/dist/types/router/segment-resolution/loader-snapshot.d.ts +14 -40
  47. package/dist/types/router/segment-resolution/mask-nested.d.ts +11 -3
  48. package/dist/types/router/segment-resolution/view-transition-default.d.ts +7 -5
  49. package/dist/types/router/transition-when.d.ts +24 -10
  50. package/dist/types/rsc/attach-transition-when.d.ts +18 -0
  51. package/dist/types/rsc/full-payload.d.ts +14 -7
  52. package/dist/types/rsc/response-cache-serve.d.ts +15 -12
  53. package/dist/types/rsc/shell-build-manifest.d.ts +4 -3
  54. package/dist/types/rsc/shell-capture-constants.d.ts +77 -20
  55. package/dist/types/rsc/shell-capture.d.ts +78 -81
  56. package/dist/types/rsc/shell-serve.d.ts +19 -48
  57. package/dist/types/rsc/types.d.ts +2 -0
  58. package/dist/types/segment-system.d.ts +7 -0
  59. package/dist/types/server/context.d.ts +126 -61
  60. package/dist/types/server/cookie-store.d.ts +30 -18
  61. package/dist/types/server/handle-store.d.ts +52 -1
  62. package/dist/types/server/request-context.d.ts +66 -45
  63. package/dist/types/server/request-headers.d.ts +44 -0
  64. package/dist/types/server.d.ts +1 -0
  65. package/dist/types/testing/e2e/index.d.ts +1 -1
  66. package/dist/types/testing/index.d.ts +3 -2
  67. package/dist/types/testing/internal/action-id.d.ts +7 -0
  68. package/dist/types/testing/location-state-key.d.ts +24 -0
  69. package/dist/types/testing/render-route.d.ts +17 -1
  70. package/dist/types/testing/run-transition-when.d.ts +44 -69
  71. package/dist/types/testing/serve-shell-request.d.ts +7 -6
  72. package/dist/types/testing/shell-status.d.ts +34 -22
  73. package/dist/types/transition-when-ref.d.ts +79 -0
  74. package/dist/types/types/cache-types.d.ts +15 -3
  75. package/dist/types/types/index.d.ts +1 -1
  76. package/dist/types/types/segments.d.ts +86 -71
  77. package/dist/types/urls/path-helper-types.d.ts +3 -4
  78. package/dist/types/urls/pattern-types.d.ts +3 -4
  79. package/dist/types/vite/plugins/transition-when-hoist.d.ts +45 -0
  80. package/dist/vite/index.js +962 -97
  81. package/package.json +1 -1
  82. package/skills/cache-guide/SKILL.md +76 -49
  83. package/skills/caching/SKILL.md +167 -69
  84. package/skills/client-urls/SKILL.md +1 -1
  85. package/skills/comparison/references/framework-comparison.md +8 -5
  86. package/skills/document-cache/SKILL.md +24 -0
  87. package/skills/hooks/SKILL.md +3 -1
  88. package/skills/hooks/navigation.md +23 -2
  89. package/skills/hooks/state.md +98 -6
  90. package/skills/intercept/SKILL.md +29 -12
  91. package/skills/loader/SKILL.md +52 -21
  92. package/skills/middleware/SKILL.md +18 -0
  93. package/skills/migrate-nextjs/SKILL.md +3 -3
  94. package/skills/observability/SKILL.md +6 -5
  95. package/skills/parallel/SKILL.md +1 -1
  96. package/skills/ppr/SKILL.md +129 -77
  97. package/skills/prerender/SKILL.md +3 -3
  98. package/skills/rango/SKILL.md +25 -6
  99. package/skills/route/SKILL.md +2 -0
  100. package/skills/testing/SKILL.md +27 -25
  101. package/skills/testing/cache-prerender.md +9 -9
  102. package/skills/testing/client-components.md +45 -3
  103. package/skills/testing/loader.md +3 -2
  104. package/skills/testing/middleware.md +1 -0
  105. package/skills/testing/predicates.md +49 -49
  106. package/skills/testing/render-handler.md +1 -0
  107. package/skills/testing/serve-shell-request.md +14 -0
  108. package/skills/testing/server-actions.md +9 -3
  109. package/skills/theme/SKILL.md +8 -4
  110. package/skills/typesafety/env-and-bindings.md +8 -0
  111. package/skills/use-cache/SKILL.md +67 -19
  112. package/skills/view-transitions/SKILL.md +150 -29
  113. package/src/__augment-tests__/augmented.check.ts +23 -0
  114. package/src/browser/event-controller.ts +4 -4
  115. package/src/browser/history-state.ts +52 -1
  116. package/src/browser/link-interceptor.ts +4 -0
  117. package/src/browser/navigation-bridge.ts +77 -18
  118. package/src/browser/navigation-client.ts +4 -3
  119. package/src/browser/navigation-store.ts +43 -1
  120. package/src/browser/navigation-transaction.ts +31 -11
  121. package/src/browser/partial-update.ts +146 -35
  122. package/src/browser/prefetch/cache.ts +6 -6
  123. package/src/browser/prefetch/fetch.ts +2 -2
  124. package/src/browser/react/Link.tsx +38 -33
  125. package/src/browser/react/NavigationProvider.tsx +2 -2
  126. package/src/browser/react/context.ts +2 -2
  127. package/src/browser/react/location-state-shared.ts +241 -49
  128. package/src/browser/react/location-state.ts +5 -2
  129. package/src/browser/rsc-router.tsx +4 -0
  130. package/src/browser/server-action-bridge.ts +84 -15
  131. package/src/browser/transition-when.ts +205 -0
  132. package/src/browser/types.ts +124 -10
  133. package/src/build/generate-manifest.ts +77 -1
  134. package/src/cache/background-task.ts +55 -2
  135. package/src/cache/cache-exec-scope.ts +54 -1
  136. package/src/cache/cache-key-utils.ts +56 -16
  137. package/src/cache/cache-policy.ts +9 -4
  138. package/src/cache/cache-runtime.ts +202 -144
  139. package/src/cache/cache-scope.ts +304 -112
  140. package/src/cache/cache-tag.ts +209 -13
  141. package/src/cache/cf/cf-cache-store.ts +152 -76
  142. package/src/cache/cf/cf-shell-frame.ts +2 -2
  143. package/src/cache/cf/cf-tag-marker-memo.ts +11 -100
  144. package/src/cache/document-cache.ts +53 -7
  145. package/src/cache/handle-snapshot.ts +52 -9
  146. package/src/cache/invalidation-order.ts +84 -0
  147. package/src/cache/request-tag-mask.ts +147 -0
  148. package/src/cache/shell-snapshot.ts +130 -348
  149. package/src/cache/tag-invalidation.ts +57 -51
  150. package/src/cache/types.ts +71 -67
  151. package/src/cache/vercel/vercel-cache-store.ts +61 -58
  152. package/src/client-urls/client-root.tsx +8 -4
  153. package/src/client-urls/client-urls.ts +8 -6
  154. package/src/client-urls/navigation.ts +55 -4
  155. package/src/client-urls/server-projection.ts +31 -10
  156. package/src/client-urls/types.ts +12 -5
  157. package/src/client.tsx +2 -0
  158. package/src/host/cookie-handler.ts +2 -1
  159. package/src/index.rsc.ts +3 -0
  160. package/src/index.ts +3 -0
  161. package/src/loader-redirect.tsx +7 -3
  162. package/src/prerender/build-shell-capture.ts +155 -146
  163. package/src/route-definition/dsl-helpers.ts +2 -0
  164. package/src/route-definition/helpers-types.ts +11 -11
  165. package/src/route-name.ts +12 -0
  166. package/src/router/content-negotiation.ts +4 -1
  167. package/src/router/error-handling.ts +5 -2
  168. package/src/router/handler-context.ts +10 -7
  169. package/src/router/intercept-resolution.ts +11 -1
  170. package/src/router/loader-resolution.ts +42 -5
  171. package/src/router/logging.ts +5 -3
  172. package/src/router/match-api.ts +62 -34
  173. package/src/router/match-context.ts +5 -0
  174. package/src/router/match-handlers.ts +9 -29
  175. package/src/router/match-middleware/background-revalidation.ts +3 -5
  176. package/src/router/match-middleware/cache-lookup.ts +35 -19
  177. package/src/router/match-middleware/cache-store.ts +15 -9
  178. package/src/router/match-result.ts +1 -0
  179. package/src/router/middleware.ts +2 -5
  180. package/src/router/navigation-snapshot.ts +6 -5
  181. package/src/router/prerender-match.ts +4 -0
  182. package/src/router/request-classification.ts +7 -4
  183. package/src/router/router-options.ts +4 -1
  184. package/src/router/segment-resolution/fresh.ts +0 -4
  185. package/src/router/segment-resolution/loader-cache.ts +152 -26
  186. package/src/router/segment-resolution/loader-mask.ts +3 -4
  187. package/src/router/segment-resolution/loader-snapshot.ts +15 -78
  188. package/src/router/segment-resolution/mask-nested.ts +47 -22
  189. package/src/router/segment-resolution/revalidation.ts +0 -3
  190. package/src/router/segment-resolution/view-transition-default.ts +6 -21
  191. package/src/router/telemetry.ts +6 -3
  192. package/src/router/transition-when.ts +34 -52
  193. package/src/router.ts +2 -1
  194. package/src/rsc/attach-transition-when.ts +47 -0
  195. package/src/rsc/full-payload.ts +30 -10
  196. package/src/rsc/handler.ts +12 -10
  197. package/src/rsc/loader-fetch.ts +2 -1
  198. package/src/rsc/manifest-init.ts +4 -0
  199. package/src/rsc/origin-guard.ts +8 -5
  200. package/src/rsc/progressive-enhancement.ts +7 -30
  201. package/src/rsc/response-cache-serve.ts +68 -80
  202. package/src/rsc/response-route-handler.ts +2 -2
  203. package/src/rsc/rsc-rendering.ts +206 -252
  204. package/src/rsc/server-action.ts +8 -20
  205. package/src/rsc/shell-build-manifest.ts +22 -20
  206. package/src/rsc/shell-capture-constants.ts +136 -20
  207. package/src/rsc/shell-capture.ts +352 -589
  208. package/src/rsc/shell-serve.ts +73 -109
  209. package/src/rsc/ssr-setup.ts +3 -2
  210. package/src/rsc/types.ts +2 -0
  211. package/src/segment-system.tsx +40 -7
  212. package/src/server/context.ts +263 -124
  213. package/src/server/cookie-store.ts +153 -127
  214. package/src/server/handle-store.ts +245 -27
  215. package/src/server/request-context.ts +134 -87
  216. package/src/server/request-headers.ts +134 -0
  217. package/src/server.ts +4 -0
  218. package/src/testing/dispatch.ts +4 -7
  219. package/src/testing/e2e/index.ts +1 -0
  220. package/src/testing/index.ts +5 -0
  221. package/src/testing/internal/action-id.ts +25 -0
  222. package/src/testing/internal/context.ts +1 -1
  223. package/src/testing/location-state-key.ts +45 -0
  224. package/src/testing/render-handler.ts +1 -1
  225. package/src/testing/render-route.tsx +109 -16
  226. package/src/testing/run-client-revalidate.ts +3 -19
  227. package/src/testing/run-loader.ts +5 -1
  228. package/src/testing/run-transition-when.ts +109 -172
  229. package/src/testing/serve-shell-request.ts +10 -7
  230. package/src/testing/shell-status.ts +60 -56
  231. package/src/transition-when-ref.ts +220 -0
  232. package/src/types/cache-types.ts +15 -3
  233. package/src/types/index.ts +3 -0
  234. package/src/types/segments.ts +94 -91
  235. package/src/urls/path-helper-types.ts +3 -4
  236. package/src/urls/pattern-types.ts +3 -4
  237. package/src/vite/discovery/discover-routers.ts +3 -0
  238. package/src/vite/discovery/shell-prerender-phase.ts +0 -2
  239. package/src/vite/discovery/virtual-module-codegen.ts +16 -6
  240. package/src/vite/plugins/transition-when-hoist.ts +1064 -0
  241. package/src/vite/rango.ts +2 -0
  242. package/src/vite/router-discovery.ts +75 -5
  243. package/dist/types/rsc/transition-gate.d.ts +0 -23
  244. package/src/rsc/transition-gate.ts +0 -86
@@ -1587,10 +1587,10 @@ import path4 from "node:path";
1587
1587
  function isDirectivePrologueStatement(node) {
1588
1588
  return node?.type === "ExpressionStatement" && typeof node.directive === "string";
1589
1589
  }
1590
- function findImportInsertionPos(code, parseAst6) {
1590
+ function findImportInsertionPos(code, parseAst7) {
1591
1591
  let program;
1592
1592
  try {
1593
- program = parseAst6(code, { lang: "tsx" });
1593
+ program = parseAst7(code, { lang: "tsx" });
1594
1594
  } catch {
1595
1595
  return 0;
1596
1596
  }
@@ -1627,10 +1627,10 @@ function walkNode(node, parent, ancestors, enter) {
1627
1627
  }
1628
1628
  ancestors.pop();
1629
1629
  }
1630
- function findHandlerCalls(code, fnName, parseAst6) {
1630
+ function findHandlerCalls(code, fnName, parseAst7) {
1631
1631
  let program;
1632
1632
  try {
1633
- program = parseAst6(code, { lang: "tsx" });
1633
+ program = parseAst7(code, { lang: "tsx" });
1634
1634
  } catch {
1635
1635
  return [];
1636
1636
  }
@@ -1702,18 +1702,18 @@ function getImportedLocalNamesFromProgram(program, importedName) {
1702
1702
  }
1703
1703
  return localNames;
1704
1704
  }
1705
- function getImportedLocalNames(code, importedName, parseAst6) {
1705
+ function getImportedLocalNames(code, importedName, parseAst7) {
1706
1706
  try {
1707
- const program = parseAst6(code, { lang: "tsx" });
1707
+ const program = parseAst7(code, { lang: "tsx" });
1708
1708
  return getImportedLocalNamesFromProgram(program, importedName);
1709
1709
  } catch {
1710
1710
  return /* @__PURE__ */ new Set();
1711
1711
  }
1712
1712
  }
1713
- function extractImportDeclarations(code, parseAst6) {
1713
+ function extractImportDeclarations(code, parseAst7) {
1714
1714
  let program;
1715
1715
  try {
1716
- program = parseAst6(code, { lang: "tsx" });
1716
+ program = parseAst7(code, { lang: "tsx" });
1717
1717
  } catch {
1718
1718
  return [];
1719
1719
  }
@@ -1770,10 +1770,10 @@ function isSafeVariableDeclaration(node, handlerNames) {
1770
1770
  (d) => isSafeDeclaratorInit(d.init) && !(d.init?.type === "CallExpression" && d.init.callee?.type === "Identifier" && handlerNames.has(d.init.callee.name))
1771
1771
  );
1772
1772
  }
1773
- function extractModuleLevelDeclarations(code, parseAst6, handlerNames) {
1773
+ function extractModuleLevelDeclarations(code, parseAst7, handlerNames) {
1774
1774
  let program;
1775
1775
  try {
1776
- program = parseAst6(code, { lang: "tsx" });
1776
+ program = parseAst7(code, { lang: "tsx" });
1777
1777
  } catch {
1778
1778
  return [];
1779
1779
  }
@@ -1804,17 +1804,17 @@ function extractModuleLevelDeclarations(code, parseAst6, handlerNames) {
1804
1804
  }
1805
1805
  return declarations;
1806
1806
  }
1807
- function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst6) {
1808
- const sites = findHandlerCalls(code, fnName, parseAst6);
1807
+ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtualRegistry, moduleId, parseAst7) {
1808
+ const sites = findHandlerCalls(code, fnName, parseAst7);
1809
1809
  const inlineSites = sites.filter((site) => site.exportInfo === null);
1810
1810
  if (inlineSites.length === 0) return false;
1811
- const imports = extractImportDeclarations(code, parseAst6);
1812
- const staticNames = getImportedLocalNames(code, "Static", parseAst6);
1813
- const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst6);
1811
+ const imports = extractImportDeclarations(code, parseAst7);
1812
+ const staticNames = getImportedLocalNames(code, "Static", parseAst7);
1813
+ const prerenderNames = getImportedLocalNames(code, "Prerender", parseAst7);
1814
1814
  const handlerNames = /* @__PURE__ */ new Set([...staticNames, ...prerenderNames]);
1815
1815
  const declarations = extractModuleLevelDeclarations(
1816
1816
  code,
1817
- parseAst6,
1817
+ parseAst7,
1818
1818
  handlerNames
1819
1819
  );
1820
1820
  const importStatements = [];
@@ -1837,7 +1837,7 @@ function transformInlineHandlers(fnName, virtualPrefix, s, code, filePath, virtu
1837
1837
  }
1838
1838
  if (importStatements.length > 0) {
1839
1839
  const importBlock = importStatements.join("\n") + "\n";
1840
- const insertionPos = findImportInsertionPos(code, parseAst6);
1840
+ const insertionPos = findImportInsertionPos(code, parseAst7);
1841
1841
  if (insertionPos === 0) {
1842
1842
  s.prepend(importBlock);
1843
1843
  } else {
@@ -3103,8 +3103,8 @@ async function transformUseCache(code, id, options) {
3103
3103
  const { hasDirective: hasDirective2, transformWrapExport, transformHoistInlineDirective } = rscTransforms;
3104
3104
  let ast;
3105
3105
  try {
3106
- const { parseAst: parseAst6 } = await import("vite");
3107
- ast = parseAst6(code, { lang: "tsx" });
3106
+ const { parseAst: parseAst7 } = await import("vite");
3107
+ ast = parseAst7(code, { lang: "tsx" });
3108
3108
  } catch {
3109
3109
  return;
3110
3110
  }
@@ -3163,8 +3163,8 @@ async function transformFileLevelUseCache(code, ast, filePath, sourceId, isBuild
3163
3163
  );
3164
3164
  }
3165
3165
  if (exportNames.length === 0) {
3166
- const { default: MagicString4 } = await import("magic-string");
3167
- const s = new MagicString4(code);
3166
+ const { default: MagicString5 } = await import("magic-string");
3167
+ const s = new MagicString5(code);
3168
3168
  const directive2 = findFileLevelDirective(ast);
3169
3169
  if (directive2) {
3170
3170
  s.overwrite(
@@ -3288,6 +3288,782 @@ function warnOnNearMissDirectives(ast, fileId, warn) {
3288
3288
  }
3289
3289
  }
3290
3290
 
3291
+ // src/vite/plugins/transition-when-hoist.ts
3292
+ import { normalizePath as normalizePath2, parseAst as parseAst3 } from "vite";
3293
+ import MagicString4 from "magic-string";
3294
+ import { builtinModules } from "node:module";
3295
+ import { readFile } from "node:fs/promises";
3296
+
3297
+ // src/route-name.ts
3298
+ var AUTO_GENERATED_ROUTE_PREFIX = "$path_";
3299
+ var INTERNAL_INCLUDE_SCOPE_PREFIX = "$prefix_";
3300
+ function isAutoGeneratedRouteName(name) {
3301
+ return name.split(".").some((segment) => {
3302
+ return segment.startsWith(AUTO_GENERATED_ROUTE_PREFIX) || segment.startsWith(INTERNAL_INCLUDE_SCOPE_PREFIX);
3303
+ });
3304
+ }
3305
+
3306
+ // src/transition-when-ref.ts
3307
+ var TransitionWhenError = class extends Error {
3308
+ constructor() {
3309
+ super(...arguments);
3310
+ this.name = "TransitionWhenError";
3311
+ }
3312
+ };
3313
+ function findTransitionWhenError(error) {
3314
+ const seen = /* @__PURE__ */ new Set();
3315
+ let current = error;
3316
+ while (typeof current === "object" && current !== null && !seen.has(current)) {
3317
+ seen.add(current);
3318
+ if (current.name === "TransitionWhenError") {
3319
+ return current;
3320
+ }
3321
+ current = current.cause;
3322
+ }
3323
+ return void 0;
3324
+ }
3325
+
3326
+ // src/vite/plugins/transition-when-hoist.ts
3327
+ var TRANSITION_WHEN_QUERY = "rango-when";
3328
+ var EXPORT_NAME = "__rango_when";
3329
+ var LOCAL_PREFIX = "__rango_when_";
3330
+ var Scope = class {
3331
+ constructor(parent, isFunction, node) {
3332
+ this.parent = parent;
3333
+ this.isFunction = isFunction;
3334
+ this.node = node;
3335
+ this.declarations = /* @__PURE__ */ new Set();
3336
+ }
3337
+ nearestFunction() {
3338
+ return this.isFunction || !this.parent ? this : this.parent.nearestFunction();
3339
+ }
3340
+ lookup(name) {
3341
+ if (this.declarations.has(name)) return this;
3342
+ return this.parent ? this.parent.lookup(name) : null;
3343
+ }
3344
+ };
3345
+ var TYPE_KEYS = /* @__PURE__ */ new Set([
3346
+ "typeAnnotation",
3347
+ "returnType",
3348
+ "typeParameters",
3349
+ "typeArguments",
3350
+ "superTypeArguments",
3351
+ "implements"
3352
+ ]);
3353
+ function jsxNameReference(name) {
3354
+ if (name?.type === "JSXIdentifier") {
3355
+ return /^[a-z]/.test(name.name) || name.name.includes("-") ? null : name;
3356
+ }
3357
+ let root = name;
3358
+ while (root?.type === "JSXMemberExpression") root = root.object;
3359
+ return root?.type === "JSXIdentifier" && root.name !== "this" ? root : null;
3360
+ }
3361
+ function patternNames(pattern, names = []) {
3362
+ if (!pattern) return names;
3363
+ switch (pattern.type) {
3364
+ case "Identifier":
3365
+ names.push(pattern.name);
3366
+ break;
3367
+ case "ObjectPattern":
3368
+ for (const prop of pattern.properties) {
3369
+ patternNames(prop.type === "RestElement" ? prop : prop.value, names);
3370
+ }
3371
+ break;
3372
+ case "ArrayPattern":
3373
+ for (const element of pattern.elements) patternNames(element, names);
3374
+ break;
3375
+ case "RestElement":
3376
+ patternNames(pattern.argument, names);
3377
+ break;
3378
+ case "AssignmentPattern":
3379
+ patternNames(pattern.left, names);
3380
+ break;
3381
+ case "TSParameterProperty":
3382
+ patternNames(pattern.parameter, names);
3383
+ break;
3384
+ }
3385
+ return names;
3386
+ }
3387
+ function analyzeScopes(program) {
3388
+ const moduleScope = new Scope(null, true, program);
3389
+ const scopeOf = /* @__PURE__ */ new Map([[program, moduleScope]]);
3390
+ const references = [];
3391
+ const declarePattern = (pattern, scope) => {
3392
+ for (const name of patternNames(pattern)) scope.declarations.add(name);
3393
+ };
3394
+ const walkPattern = (pattern, scope, typeOnly) => {
3395
+ if (!pattern) return;
3396
+ switch (pattern.type) {
3397
+ case "Identifier":
3398
+ if (pattern.typeAnnotation) walk2(pattern.typeAnnotation, scope, true);
3399
+ return;
3400
+ case "ObjectPattern":
3401
+ for (const prop of pattern.properties) {
3402
+ if (prop.type === "RestElement") {
3403
+ walkPattern(prop.argument, scope, typeOnly);
3404
+ } else {
3405
+ if (prop.computed) walk2(prop.key, scope, typeOnly);
3406
+ walkPattern(prop.value, scope, typeOnly);
3407
+ }
3408
+ }
3409
+ if (pattern.typeAnnotation) walk2(pattern.typeAnnotation, scope, true);
3410
+ return;
3411
+ case "ArrayPattern":
3412
+ for (const element of pattern.elements) {
3413
+ walkPattern(element, scope, typeOnly);
3414
+ }
3415
+ if (pattern.typeAnnotation) walk2(pattern.typeAnnotation, scope, true);
3416
+ return;
3417
+ case "RestElement":
3418
+ walkPattern(pattern.argument, scope, typeOnly);
3419
+ return;
3420
+ case "AssignmentPattern":
3421
+ walkPattern(pattern.left, scope, typeOnly);
3422
+ walk2(pattern.right, scope, typeOnly);
3423
+ return;
3424
+ case "TSParameterProperty":
3425
+ walkPattern(pattern.parameter, scope, typeOnly);
3426
+ return;
3427
+ default:
3428
+ walk2(pattern, scope, typeOnly);
3429
+ }
3430
+ };
3431
+ const hoistDeclarations = (statements, scope) => {
3432
+ for (const statement of statements) {
3433
+ const node = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
3434
+ if (!node) continue;
3435
+ switch (node.type) {
3436
+ case "ImportDeclaration":
3437
+ for (const spec of node.specifiers) {
3438
+ scope.declarations.add(spec.local.name);
3439
+ }
3440
+ break;
3441
+ case "FunctionDeclaration":
3442
+ case "ClassDeclaration":
3443
+ case "TSEnumDeclaration":
3444
+ case "TSTypeAliasDeclaration":
3445
+ case "TSInterfaceDeclaration":
3446
+ case "TSModuleDeclaration":
3447
+ if (node.id?.type === "Identifier") {
3448
+ scope.declarations.add(node.id.name);
3449
+ }
3450
+ break;
3451
+ case "VariableDeclaration":
3452
+ for (const declarator of node.declarations) {
3453
+ declarePattern(
3454
+ declarator.id,
3455
+ node.kind === "var" ? scope.nearestFunction() : scope
3456
+ );
3457
+ }
3458
+ break;
3459
+ }
3460
+ }
3461
+ };
3462
+ const walk2 = (node, scope, typeOnly) => {
3463
+ if (!node || typeof node !== "object") return;
3464
+ if (Array.isArray(node)) {
3465
+ for (const item of node) walk2(item, scope, typeOnly);
3466
+ return;
3467
+ }
3468
+ if (typeof node.type !== "string") return;
3469
+ switch (node.type) {
3470
+ case "Identifier":
3471
+ references.push({ node, scope, typeOnly });
3472
+ if (node.typeAnnotation) walk2(node.typeAnnotation, scope, true);
3473
+ return;
3474
+ case "ImportDeclaration":
3475
+ case "ExportAllDeclaration":
3476
+ return;
3477
+ case "ExportNamedDeclaration":
3478
+ if (node.declaration) walk2(node.declaration, scope, typeOnly);
3479
+ else if (!node.source) {
3480
+ for (const spec of node.specifiers) {
3481
+ walk2(spec.local, scope, typeOnly || node.exportKind === "type");
3482
+ }
3483
+ }
3484
+ return;
3485
+ case "MemberExpression":
3486
+ walk2(node.object, scope, typeOnly);
3487
+ if (node.computed) walk2(node.property, scope, typeOnly);
3488
+ return;
3489
+ case "Property":
3490
+ case "MethodDefinition":
3491
+ case "PropertyDefinition":
3492
+ case "TSPropertySignature":
3493
+ case "TSMethodSignature":
3494
+ if (node.computed) walk2(node.key, scope, typeOnly);
3495
+ walk2(node.value, scope, typeOnly);
3496
+ if (node.typeAnnotation) walk2(node.typeAnnotation, scope, true);
3497
+ return;
3498
+ case "LabeledStatement":
3499
+ walk2(node.body, scope, typeOnly);
3500
+ return;
3501
+ case "BreakStatement":
3502
+ case "ContinueStatement":
3503
+ case "MetaProperty":
3504
+ return;
3505
+ case "JSXOpeningElement":
3506
+ case "JSXClosingElement": {
3507
+ const ref = jsxNameReference(node.name);
3508
+ if (ref) references.push({ node: ref, scope, typeOnly });
3509
+ walk2(node.typeArguments, scope, true);
3510
+ walk2(node.attributes, scope, typeOnly);
3511
+ return;
3512
+ }
3513
+ case "TSQualifiedName":
3514
+ walk2(node.left, scope, typeOnly);
3515
+ return;
3516
+ case "TSEnumMember":
3517
+ walk2(node.initializer, scope, typeOnly);
3518
+ return;
3519
+ case "TSTypeAliasDeclaration":
3520
+ case "TSInterfaceDeclaration":
3521
+ walk2(node.typeParameters, scope, true);
3522
+ walk2(node.typeAnnotation ?? node.body, scope, true);
3523
+ walk2(node.extends, scope, true);
3524
+ return;
3525
+ case "VariableDeclarator":
3526
+ walkPattern(node.id, scope, typeOnly);
3527
+ walk2(node.init, scope, typeOnly);
3528
+ return;
3529
+ case "FunctionDeclaration":
3530
+ case "FunctionExpression":
3531
+ case "ArrowFunctionExpression": {
3532
+ const fnScope = new Scope(scope, true, node);
3533
+ scopeOf.set(node, fnScope);
3534
+ if (node.type === "FunctionExpression" && node.id) {
3535
+ fnScope.declarations.add(node.id.name);
3536
+ }
3537
+ for (const param of node.params) declarePattern(param, fnScope);
3538
+ walk2(node.typeParameters, fnScope, true);
3539
+ for (const param of node.params) walkPattern(param, fnScope, typeOnly);
3540
+ walk2(node.returnType, fnScope, true);
3541
+ if (node.body?.type === "BlockStatement") {
3542
+ hoistDeclarations(node.body.body, fnScope);
3543
+ walk2(node.body.body, fnScope, typeOnly);
3544
+ } else {
3545
+ walk2(node.body, fnScope, typeOnly);
3546
+ }
3547
+ return;
3548
+ }
3549
+ case "ClassDeclaration":
3550
+ case "ClassExpression": {
3551
+ let classScope = scope;
3552
+ if (node.type === "ClassExpression" && node.id) {
3553
+ classScope = new Scope(scope, false, node);
3554
+ classScope.declarations.add(node.id.name);
3555
+ scopeOf.set(node, classScope);
3556
+ }
3557
+ walk2(node.superClass, classScope, typeOnly);
3558
+ walk2(node.superTypeArguments, classScope, true);
3559
+ walk2(node.typeParameters, classScope, true);
3560
+ walk2(node.implements, classScope, true);
3561
+ walk2(node.body, classScope, typeOnly);
3562
+ return;
3563
+ }
3564
+ case "BlockStatement":
3565
+ case "StaticBlock": {
3566
+ const blockScope = new Scope(scope, false, node);
3567
+ scopeOf.set(node, blockScope);
3568
+ hoistDeclarations(node.body, blockScope);
3569
+ walk2(node.body, blockScope, typeOnly);
3570
+ return;
3571
+ }
3572
+ case "ForStatement":
3573
+ case "ForInStatement":
3574
+ case "ForOfStatement": {
3575
+ const loopScope = new Scope(scope, false, node);
3576
+ scopeOf.set(node, loopScope);
3577
+ const head = node.type === "ForStatement" ? node.init : node.left;
3578
+ if (head?.type === "VariableDeclaration") {
3579
+ hoistDeclarations([head], loopScope);
3580
+ }
3581
+ for (const key of ["init", "left", "right", "test", "update", "body"]) {
3582
+ walk2(node[key], loopScope, typeOnly);
3583
+ }
3584
+ return;
3585
+ }
3586
+ case "SwitchStatement": {
3587
+ walk2(node.discriminant, scope, typeOnly);
3588
+ const switchScope = new Scope(scope, false, node);
3589
+ scopeOf.set(node, switchScope);
3590
+ hoistDeclarations(
3591
+ node.cases.flatMap((c) => c.consequent),
3592
+ switchScope
3593
+ );
3594
+ for (const c of node.cases) {
3595
+ walk2(c.test, switchScope, typeOnly);
3596
+ walk2(c.consequent, switchScope, typeOnly);
3597
+ }
3598
+ return;
3599
+ }
3600
+ case "CatchClause": {
3601
+ const catchScope = new Scope(scope, false, node);
3602
+ scopeOf.set(node, catchScope);
3603
+ if (node.param) {
3604
+ declarePattern(node.param, catchScope);
3605
+ walkPattern(node.param, catchScope, typeOnly);
3606
+ }
3607
+ walk2(node.body, catchScope, typeOnly);
3608
+ return;
3609
+ }
3610
+ }
3611
+ for (const key of Object.keys(node)) {
3612
+ if (key === "type" || key === "start" || key === "end") continue;
3613
+ walk2(node[key], scope, typeOnly || TYPE_KEYS.has(key));
3614
+ }
3615
+ };
3616
+ hoistDeclarations(program.body, moduleScope);
3617
+ walk2(program.body, moduleScope, false);
3618
+ return { moduleScope, references, scopeOf };
3619
+ }
3620
+ function collectImports(program) {
3621
+ const imports = /* @__PURE__ */ new Map();
3622
+ for (const statement of program.body) {
3623
+ if (statement.type !== "ImportDeclaration") continue;
3624
+ const declTypeOnly = statement.importKind === "type";
3625
+ for (const spec of statement.specifiers) {
3626
+ imports.set(spec.local.name, {
3627
+ source: statement.source.value,
3628
+ imported: spec.type === "ImportDefaultSpecifier" ? "default" : spec.type === "ImportNamespaceSpecifier" ? "*" : spec.imported.type === "Identifier" ? spec.imported.name : String(spec.imported.value),
3629
+ typeOnly: declTypeOnly || spec.importKind === "type"
3630
+ });
3631
+ }
3632
+ }
3633
+ return imports;
3634
+ }
3635
+ function hasUseClientDirective(program) {
3636
+ for (const statement of program.body) {
3637
+ if (statement.type !== "ExpressionStatement" || typeof statement.directive !== "string") {
3638
+ return false;
3639
+ }
3640
+ if (statement.directive === "use client") return true;
3641
+ }
3642
+ return false;
3643
+ }
3644
+ function lineColumn(code, offset) {
3645
+ let line = 1;
3646
+ let column = 1;
3647
+ for (let i = 0; i < offset && i < code.length; i++) {
3648
+ if (code[i] === "\n") {
3649
+ line++;
3650
+ column = 1;
3651
+ } else {
3652
+ column++;
3653
+ }
3654
+ }
3655
+ return `${line}:${column}`;
3656
+ }
3657
+ var ROUTER_SOURCE = "@rangojs/router";
3658
+ var EXPRESSION_WRAPPERS = /* @__PURE__ */ new Set([
3659
+ "TSAsExpression",
3660
+ "TSSatisfiesExpression",
3661
+ "TSNonNullExpression",
3662
+ "TSTypeAssertion",
3663
+ "ParenthesizedExpression"
3664
+ ]);
3665
+ function unwrapExpression(node) {
3666
+ while (node && EXPRESSION_WRAPPERS.has(node.type)) node = node.expression;
3667
+ return node;
3668
+ }
3669
+ function isFunctionLiteral(node) {
3670
+ return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
3671
+ }
3672
+ function propertyKeyIs(prop, name) {
3673
+ return prop?.type === "Property" && !prop.computed && (prop.key.type === "Identifier" && prop.key.name === name || prop.key.type === "Literal" && prop.key.value === name);
3674
+ }
3675
+ function findWhenSites(program, scopeOf, moduleScope, imports) {
3676
+ const sites = [];
3677
+ const helpers = /* @__PURE__ */ new Map();
3678
+ const routerExport = (callee, scope) => {
3679
+ const routerImport = (id) => {
3680
+ if (id?.type !== "Identifier") return null;
3681
+ if (scope.lookup(id.name) !== moduleScope) return null;
3682
+ const binding = imports.get(id.name);
3683
+ return binding?.source === ROUTER_SOURCE && !binding.typeOnly ? binding : null;
3684
+ };
3685
+ if (callee.type === "Identifier") {
3686
+ const imported = routerImport(callee)?.imported;
3687
+ return imported && imported !== "*" && imported !== "default" ? imported : null;
3688
+ }
3689
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && routerImport(callee.object)?.imported === "*") {
3690
+ return callee.property.name;
3691
+ }
3692
+ return null;
3693
+ };
3694
+ const registerUrlsCallback = (callback) => {
3695
+ const fnScope = scopeOf.get(callback);
3696
+ let param = callback.params[0];
3697
+ if (param?.type === "AssignmentPattern") param = param.left;
3698
+ if (!fnScope || !param) return;
3699
+ const bindings = {
3700
+ transitions: /* @__PURE__ */ new Set(),
3701
+ objects: /* @__PURE__ */ new Set()
3702
+ };
3703
+ if (param.type === "Identifier") bindings.objects.add(param.name);
3704
+ if (param.type === "ObjectPattern") {
3705
+ for (const prop of param.properties) {
3706
+ if (!propertyKeyIs(prop, "transition")) continue;
3707
+ const target = prop.value.type === "AssignmentPattern" ? prop.value.left : prop.value;
3708
+ if (target.type === "Identifier") bindings.transitions.add(target.name);
3709
+ }
3710
+ }
3711
+ helpers.set(fnScope, bindings);
3712
+ };
3713
+ const isRouterTransition = (callee, scope) => {
3714
+ if (routerExport(callee, scope) === "transition") return true;
3715
+ if (callee.type === "Identifier") {
3716
+ const decl = scope.lookup(callee.name);
3717
+ return !!decl && !!helpers.get(decl)?.transitions.has(callee.name);
3718
+ }
3719
+ if (callee.type === "MemberExpression" && !callee.computed && callee.property.type === "Identifier" && callee.property.name === "transition" && callee.object.type === "Identifier") {
3720
+ const decl = scope.lookup(callee.object.name);
3721
+ return !!decl && !!helpers.get(decl)?.objects.has(callee.object.name);
3722
+ }
3723
+ return false;
3724
+ };
3725
+ const visit = (node, scope) => {
3726
+ if (!node || typeof node !== "object") return;
3727
+ if (Array.isArray(node)) {
3728
+ for (const item of node) visit(item, scope);
3729
+ return;
3730
+ }
3731
+ if (typeof node.type !== "string") return;
3732
+ const own = scopeOf.get(node) ?? scope;
3733
+ if (node.type === "CallExpression") {
3734
+ const callback = node.arguments[0];
3735
+ if (isFunctionLiteral(callback) && routerExport(node.callee, own) === "urls") {
3736
+ registerUrlsCallback(callback);
3737
+ }
3738
+ if (node.arguments[0]?.type === "ObjectExpression" && isRouterTransition(node.callee, own)) {
3739
+ for (const prop of node.arguments[0].properties) {
3740
+ if (propertyKeyIs(prop, "when")) {
3741
+ sites.push({
3742
+ prop,
3743
+ value: unwrapExpression(prop.value),
3744
+ scope: own
3745
+ });
3746
+ }
3747
+ }
3748
+ }
3749
+ }
3750
+ for (const key of Object.keys(node)) {
3751
+ if (key === "type" || key === "start" || key === "end") continue;
3752
+ visit(node[key], own);
3753
+ }
3754
+ };
3755
+ visit(program.body, moduleScope);
3756
+ return sites;
3757
+ }
3758
+ var LEXICAL_CAPTURE = {
3759
+ this: "`this` in an arrow is the enclosing server function's `this`.",
3760
+ arguments: "`arguments` in an arrow is the enclosing server function's `arguments`.",
3761
+ super: "`super` refers to the enclosing server object.",
3762
+ "new.target": "`new.target` in an arrow is the enclosing server function's."
3763
+ };
3764
+ function findLexicalCapture(literal, references, code) {
3765
+ const ownArguments = [];
3766
+ let found = null;
3767
+ const capture = (node, what) => {
3768
+ found = {
3769
+ node,
3770
+ detail: `${LEXICAL_CAPTURE[what]} Read what you need from \`to.params\`/\`to.state\`, or export the predicate from a "use client" module.`
3771
+ };
3772
+ };
3773
+ const loads = (node, expression, why) => {
3774
+ found = {
3775
+ node,
3776
+ detail: `\`${expression}\` ${why} Import client-safe modules statically, or export the predicate from a "use client" module.`
3777
+ };
3778
+ };
3779
+ const visit = (node, boundary) => {
3780
+ if (found || !node || typeof node !== "object") return;
3781
+ if (Array.isArray(node)) {
3782
+ for (const item of node) visit(item, boundary);
3783
+ return;
3784
+ }
3785
+ if (typeof node.type !== "string") return;
3786
+ switch (node.type) {
3787
+ case "FunctionExpression":
3788
+ case "FunctionDeclaration":
3789
+ ownArguments.push(node);
3790
+ boundary = node;
3791
+ break;
3792
+ case "ClassBody":
3793
+ boundary = node;
3794
+ break;
3795
+ case "ThisExpression":
3796
+ if (!boundary) capture(node, "this");
3797
+ return;
3798
+ case "Super":
3799
+ if (!boundary || boundary === literal) capture(node, "super");
3800
+ return;
3801
+ case "JSXMemberExpression": {
3802
+ let root = node;
3803
+ while (root.type === "JSXMemberExpression") root = root.object;
3804
+ if (root.name === "this" && !boundary) capture(root, "this");
3805
+ return;
3806
+ }
3807
+ case "MetaProperty":
3808
+ if (node.meta.name === "import") {
3809
+ loads(node, "import.meta", "is the server module's.");
3810
+ } else if (!boundary) {
3811
+ capture(node, "new.target");
3812
+ }
3813
+ return;
3814
+ case "MemberExpression":
3815
+ if (node.object?.type === "MetaProperty") {
3816
+ if (node.object.meta.name !== "import") break;
3817
+ if (!node.computed && node.property.name === "env") return;
3818
+ loads(
3819
+ node,
3820
+ node.computed ? "import.meta[...]" : `import.meta.${node.property.name}`,
3821
+ "is resolved against the server module; only `import.meta.env` is available to an inline predicate."
3822
+ );
3823
+ return;
3824
+ }
3825
+ break;
3826
+ case "ImportExpression":
3827
+ loads(
3828
+ node,
3829
+ `import(${code.slice(node.source.start, node.source.end)})`,
3830
+ "loads a module at run time, which the build cannot check is client-safe."
3831
+ );
3832
+ return;
3833
+ }
3834
+ for (const key of Object.keys(node)) {
3835
+ if (key === "type" || key === "start" || key === "end") continue;
3836
+ if (TYPE_KEYS.has(key)) continue;
3837
+ visit(node[key], boundary);
3838
+ }
3839
+ };
3840
+ visit(literal, null);
3841
+ if (found) return found;
3842
+ for (const ref of references) {
3843
+ if (ref.typeOnly || ref.node.name !== "arguments") continue;
3844
+ if (ref.node.start < literal.start || ref.node.end > literal.end) continue;
3845
+ const owned = ownArguments.some(
3846
+ (fn) => fn.start <= ref.node.start && ref.node.end <= fn.end
3847
+ );
3848
+ if (!owned) {
3849
+ capture(ref.node, "arguments");
3850
+ return found;
3851
+ }
3852
+ }
3853
+ return null;
3854
+ }
3855
+ var NODE_BUILTINS = new Set(builtinModules);
3856
+ function isServerOnlySpecifier(source) {
3857
+ return source === "server-only" || source.startsWith("node:") || source.startsWith("cloudflare:") || NODE_BUILTINS.has(source);
3858
+ }
3859
+ var ROUTER_IMPORT_RE = /["']@rangojs\/router["']/;
3860
+ var WHEN_RE = /\bwhen\b/;
3861
+ function parseLang(file) {
3862
+ if (/\.[cm]?ts$/.test(file)) return "ts";
3863
+ if (/\.[cm]?jsx?$/.test(file)) return "jsx";
3864
+ return "tsx";
3865
+ }
3866
+ function whenError(file, code, node, detail) {
3867
+ return new TransitionWhenError(
3868
+ `[rango] transition({ when }) at ${file}:${lineColumn(code, node.start)}: \`when\` runs in the browser; ${detail}`
3869
+ );
3870
+ }
3871
+ async function hoistTransitionWhens(code, id, deps = {}) {
3872
+ if (!ROUTER_IMPORT_RE.test(code) || !WHEN_RE.test(code)) return null;
3873
+ const file = normalizePath2(id.split("?", 1)[0]);
3874
+ let program;
3875
+ try {
3876
+ program = parseAst3(code, { lang: parseLang(file) });
3877
+ } catch {
3878
+ return null;
3879
+ }
3880
+ if (hasUseClientDirective(program)) return null;
3881
+ const { moduleScope, references, scopeOf } = analyzeScopes(program);
3882
+ const imports = collectImports(program);
3883
+ const sites = findWhenSites(program, scopeOf, moduleScope, imports);
3884
+ if (sites.length === 0) return null;
3885
+ const s = new MagicString4(code);
3886
+ const modules = [];
3887
+ const header = [];
3888
+ for (const site of sites) {
3889
+ const { prop, value } = site;
3890
+ if (prop.kind === "get" || prop.kind === "set") {
3891
+ throw whenError(
3892
+ file,
3893
+ code,
3894
+ prop,
3895
+ `\`${prop.kind} when()\` is an accessor; \`when\` must be the predicate itself: \`when: (ctx) => ...\` or \`when(ctx) { ... }\`.`
3896
+ );
3897
+ }
3898
+ if (value.type === "Identifier") {
3899
+ const decl = site.scope.lookup(value.name);
3900
+ if (decl && !(decl === moduleScope && imports.has(value.name))) {
3901
+ throw whenError(
3902
+ file,
3903
+ code,
3904
+ value,
3905
+ `\`${value.name}\` is a server-module binding. Export the predicate from a "use client" module and import it, or write it inline.`
3906
+ );
3907
+ }
3908
+ continue;
3909
+ }
3910
+ if (!isFunctionLiteral(value)) continue;
3911
+ const lexical = findLexicalCapture(value, references, code);
3912
+ if (lexical) throw whenError(file, code, lexical.node, lexical.detail);
3913
+ const used = /* @__PURE__ */ new Map();
3914
+ for (const ref of references) {
3915
+ if (ref.node.start < value.start || ref.node.end > value.end) continue;
3916
+ const decl = ref.scope.lookup(ref.node.name);
3917
+ if (!decl) continue;
3918
+ if (decl.node.start >= value.start && decl.node.end <= value.end && decl !== moduleScope) {
3919
+ continue;
3920
+ }
3921
+ if (decl === moduleScope && imports.has(ref.node.name)) {
3922
+ const valueUse = !ref.typeOnly && !imports.get(ref.node.name).typeOnly;
3923
+ used.set(ref.node.name, (used.get(ref.node.name) ?? false) || valueUse);
3924
+ continue;
3925
+ }
3926
+ if (ref.typeOnly) continue;
3927
+ throw whenError(
3928
+ file,
3929
+ code,
3930
+ ref.node,
3931
+ `\`${ref.node.name}\` is a server-module binding. Read it from \`to.params\`/\`to.state\`, or export the predicate from a "use client" module.`
3932
+ );
3933
+ }
3934
+ const bySource = /* @__PURE__ */ new Map();
3935
+ for (const [local2, valueUse] of used) {
3936
+ const binding = imports.get(local2);
3937
+ if (valueUse) {
3938
+ if (isServerOnlySpecifier(binding.source) || await deps.isServerOnlyImport?.(binding.source)) {
3939
+ throw whenError(
3940
+ file,
3941
+ code,
3942
+ value,
3943
+ `\`${local2}\` is imported from "${binding.source}", a server-only module. Import only client-safe modules into an inline predicate, or export it from a "use client" module.`
3944
+ );
3945
+ }
3946
+ }
3947
+ const lines = bySource.get(binding.source) ?? [];
3948
+ const typePrefix = valueUse ? "" : "type ";
3949
+ if (binding.imported === "*") {
3950
+ lines.push(
3951
+ `import ${typePrefix}* as ${local2} from ${JSON.stringify(binding.source)};`
3952
+ );
3953
+ } else if (binding.imported === "default") {
3954
+ lines.push(
3955
+ `import ${typePrefix}${local2} from ${JSON.stringify(binding.source)};`
3956
+ );
3957
+ } else {
3958
+ const spec = binding.imported === local2 ? local2 : `${JSON.stringify(binding.imported)} as ${local2}`;
3959
+ lines.push(
3960
+ `import ${typePrefix}{ ${spec} } from ${JSON.stringify(binding.source)};`
3961
+ );
3962
+ }
3963
+ bySource.set(binding.source, lines);
3964
+ }
3965
+ const source = prop.method ? `${value.async ? "async " : ""}function${value.generator ? "*" : ""} ` + code.slice(value.start, value.end) : code.slice(value.start, value.end);
3966
+ const index = modules.length;
3967
+ modules.push(
3968
+ [
3969
+ `"use client";`,
3970
+ ...[...bySource.values()].flat(),
3971
+ `export const ${EXPORT_NAME} = ${source};`,
3972
+ ""
3973
+ ].join("\n")
3974
+ );
3975
+ const local = `${LOCAL_PREFIX}${index}`;
3976
+ s.overwrite(
3977
+ prop.start,
3978
+ prop.end,
3979
+ `${code.slice(prop.key.start, prop.key.end)}: ${local}`
3980
+ );
3981
+ header.push(
3982
+ `import { ${EXPORT_NAME} as ${local} } from ${JSON.stringify(
3983
+ `${file}?${TRANSITION_WHEN_QUERY}=${index}`
3984
+ )};`
3985
+ );
3986
+ }
3987
+ if (modules.length === 0) return null;
3988
+ let insertAt = 0;
3989
+ for (const statement of program.body) {
3990
+ if (statement.type !== "ExpressionStatement" || typeof statement.directive !== "string") {
3991
+ break;
3992
+ }
3993
+ insertAt = statement.end;
3994
+ }
3995
+ s.appendLeft(insertAt, `${insertAt ? "\n" : ""}${header.join("\n")}
3996
+ `);
3997
+ return {
3998
+ code: s.toString(),
3999
+ map: s.generateMap({ hires: true, source: file }),
4000
+ modules
4001
+ };
4002
+ }
4003
+ function parseHoistedWhenId(id) {
4004
+ const q = id.indexOf("?");
4005
+ if (q === -1) return null;
4006
+ const params = new URLSearchParams(id.slice(q + 1));
4007
+ const raw = params.get(TRANSITION_WHEN_QUERY);
4008
+ if (raw === null || !/^\d+$/.test(raw)) return null;
4009
+ return { file: id.slice(0, q), index: Number(raw) };
4010
+ }
4011
+ var SERVER_ONLY_IMPORT_RE = /\bimport\s+["']server-only["']/;
4012
+ async function readSource(file) {
4013
+ try {
4014
+ return await readFile(file, "utf-8");
4015
+ } catch {
4016
+ return null;
4017
+ }
4018
+ }
4019
+ function transitionWhenHoistPlugin() {
4020
+ const emitted = /* @__PURE__ */ new Map();
4021
+ return {
4022
+ name: "@rangojs/router:transition-when-hoist",
4023
+ enforce: "pre",
4024
+ async load(id) {
4025
+ const hoisted = parseHoistedWhenId(id);
4026
+ if (!hoisted) return;
4027
+ const file = normalizePath2(hoisted.file);
4028
+ const disk = await readSource(hoisted.file);
4029
+ const entry = emitted.get(file);
4030
+ const modules = entry && (disk === null || entry.disk === disk) ? entry.modules : disk === null ? void 0 : (await hoistTransitionWhens(disk, hoisted.file))?.modules;
4031
+ const module = modules?.[hoisted.index];
4032
+ if (module === void 0) {
4033
+ throw new TransitionWhenError(
4034
+ `[rango] ${hoisted.file} has no inline transition({ when }) #${hoisted.index}.`
4035
+ );
4036
+ }
4037
+ return module;
4038
+ },
4039
+ async transform(code, id) {
4040
+ if (this.environment?.name !== "rsc") return;
4041
+ if (id.includes("/node_modules/") || parseHoistedWhenId(id)) return;
4042
+ const file = normalizePath2(id.split("?", 1)[0]);
4043
+ if (!/\.[cm]?[jt]sx?$/.test(file)) return;
4044
+ emitted.delete(file);
4045
+ const result = await hoistTransitionWhens(code, id, {
4046
+ isServerOnlyImport: async (source) => {
4047
+ const resolved = await this.resolve(source, id);
4048
+ if (!resolved || resolved.external) return false;
4049
+ const target = resolved.id.split("?", 1)[0];
4050
+ if (target.includes("/node_modules/") || target.startsWith("\0")) {
4051
+ return false;
4052
+ }
4053
+ const text = await readSource(target);
4054
+ return text !== null && SERVER_ONLY_IMPORT_RE.test(text);
4055
+ }
4056
+ });
4057
+ if (!result) return;
4058
+ emitted.set(file, {
4059
+ disk: await readSource(file),
4060
+ modules: result.modules
4061
+ });
4062
+ return { code: result.code, map: result.map };
4063
+ }
4064
+ };
4065
+ }
4066
+
3291
4067
  // src/vite/plugins/client-ref-dedup.ts
3292
4068
  init_debug();
3293
4069
  import { existsSync, readFileSync } from "node:fs";
@@ -3802,7 +4578,7 @@ import { resolve } from "node:path";
3802
4578
  // package.json
3803
4579
  var package_default = {
3804
4580
  name: "@rangojs/router",
3805
- version: "0.18.0",
4581
+ version: "0.19.1",
3806
4582
  description: "Django-inspired RSC router with composable URL patterns",
3807
4583
  keywords: [
3808
4584
  "react",
@@ -4220,15 +4996,6 @@ function extractRouteFromCallExpression(node) {
4220
4996
  };
4221
4997
  }
4222
4998
 
4223
- // src/route-name.ts
4224
- var AUTO_GENERATED_ROUTE_PREFIX = "$path_";
4225
- var INTERNAL_INCLUDE_SCOPE_PREFIX = "$prefix_";
4226
- function isAutoGeneratedRouteName(name) {
4227
- return name.split(".").some((segment) => {
4228
- return segment.startsWith(AUTO_GENERATED_ROUTE_PREFIX) || segment.startsWith(INTERNAL_INCLUDE_SCOPE_PREFIX);
4229
- });
4230
- }
4231
-
4232
4999
  // src/build/route-types/codegen.ts
4233
5000
  function generateRouteTypesSource(routeManifest, searchSchemas) {
4234
5001
  const entries = Object.entries(routeManifest).filter(([name]) => !isAutoGeneratedRouteName(name)).sort(([a], [b]) => a.localeCompare(b));
@@ -5269,14 +6036,14 @@ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
5269
6036
  }
5270
6037
 
5271
6038
  // src/vite/plugins/version-plugin.ts
5272
- import { parseAst as parseAst4 } from "vite";
6039
+ import { parseAst as parseAst5 } from "vite";
5273
6040
 
5274
6041
  // src/vite/utils/directive-prologue.ts
5275
- import { parseAst as parseAst3 } from "vite";
5276
- function hasUseClientDirective(source) {
6042
+ import { parseAst as parseAst4 } from "vite";
6043
+ function hasUseClientDirective2(source) {
5277
6044
  let program;
5278
6045
  try {
5279
- program = parseAst3(source, { lang: "tsx" });
6046
+ program = parseAst4(source, { lang: "tsx" });
5280
6047
  } catch {
5281
6048
  return false;
5282
6049
  }
@@ -5298,10 +6065,10 @@ function normalizeModuleId(id) {
5298
6065
  return id.split("?", 1)[0];
5299
6066
  }
5300
6067
  function getClientModuleSignature(source) {
5301
- if (!hasUseClientDirective(source)) return void 0;
6068
+ if (!hasUseClientDirective2(source)) return void 0;
5302
6069
  let program;
5303
6070
  try {
5304
- program = parseAst4(source, { lang: "tsx" });
6071
+ program = parseAst5(source, { lang: "tsx" });
5305
6072
  } catch {
5306
6073
  return void 0;
5307
6074
  }
@@ -5488,7 +6255,7 @@ import { isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
5488
6255
 
5489
6256
  // src/vite/plugins/performance-tracks.ts
5490
6257
  init_debug();
5491
- import { readFile } from "node:fs/promises";
6258
+ import { readFile as readFile2 } from "node:fs/promises";
5492
6259
  var debug5 = createRangoDebugger(NS.transform);
5493
6260
  var RSDW_PATCH_RE = /((?:var|let|const)\s+\w+\s*=\s*root\._children\s*,\s*(\w+)\s*=\s*root\._debugInfo\s*[;,])/;
5494
6261
  function buildPatchReplacement(match, debugInfoVar) {
@@ -5517,7 +6284,7 @@ function performanceTracksOptimizeDepsPlugin() {
5517
6284
  async load(id) {
5518
6285
  const cleanId = id.split("?")[0] ?? id;
5519
6286
  if (!RSDW_CLIENT_RE.test(cleanId)) return null;
5520
- const code = await readFile(cleanId, "utf8");
6287
+ const code = await readFile2(cleanId, "utf8");
5521
6288
  const patched = patchRsdwClientDebugInfoRecovery(code);
5522
6289
  return { code: patched.code };
5523
6290
  }
@@ -6149,6 +6916,7 @@ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
6149
6916
 
6150
6917
  // src/rsc/shell-capture-constants.ts
6151
6918
  var SHELL_CAPTURE_MAX_WAIT_MS = 15e3;
6919
+ 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).';
6152
6920
 
6153
6921
  // src/cache/cache-exec-scope.ts
6154
6922
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -6161,6 +6929,8 @@ function _setCacheExecScopeProbe(probe) {
6161
6929
 
6162
6930
  // src/cache/cache-exec-scope.ts
6163
6931
  var cacheExecStorage = new AsyncLocalStorage();
6932
+ var IDENTITY_EXEMPT_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:identity-exempt");
6933
+ var identityExemptStorage = globalThis[IDENTITY_EXEMPT_KEY] ??= new AsyncLocalStorage();
6164
6934
  function isInsideCacheExecScope() {
6165
6935
  return cacheExecStorage.getStore() !== void 0;
6166
6936
  }
@@ -6173,6 +6943,21 @@ var LOADER_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-scope")
6173
6943
  var loaderScopeALS = globalThis[LOADER_SCOPE_KEY] ??= new AsyncLocalStorage2();
6174
6944
  var LOADER_BODY_SCOPE_KEY = /* @__PURE__ */ Symbol.for("rangojs-router:loader-body-scope");
6175
6945
  var loaderBodyScopeALS = globalThis[LOADER_BODY_SCOPE_KEY] ??= new AsyncLocalStorage2();
6946
+ function requestReadCaptureFix(what) {
6947
+ return {
6948
+ 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}`,
6949
+ 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."
6950
+ };
6951
+ }
6952
+ var NON_CACHEABLE_READ = {
6953
+ verb: "called",
6954
+ fix: {
6955
+ useCache: "The variable was created with { cache: false } or set with { cache: false }, and the cache key does not include its value, so the first caller's value would be served to later callers. Read it before calling the cached function and pass the value in as an argument so it becomes part of the cache key.",
6956
+ cacheScope: "The variable was created with { cache: false } or set with { cache: false }, and its value would be stale on cache hit. Move the read outside the cached scope.",
6957
+ ...requestReadCaptureFix("per-request variables")
6958
+ }
6959
+ };
6960
+ var loaderRunALS = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:loader-run-identity")] ??= new AsyncLocalStorage2();
6176
6961
  function getLoaderBodyTags() {
6177
6962
  return loaderBodyScopeALS.getStore()?.tags;
6178
6963
  }
@@ -7720,7 +8505,7 @@ init_client_ref_hashing();
7720
8505
  // src/vite/discovery/client-urls-projection.ts
7721
8506
  import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
7722
8507
  import { isAbsolute as isAbsolute3, relative as relative4, resolve as resolve8 } from "node:path";
7723
- import { normalizePath as normalizePath4, parseAst as parseAst5 } from "vite";
8508
+ import { normalizePath as normalizePath5, parseAst as parseAst6 } from "vite";
7724
8509
 
7725
8510
  // src/client-urls/server-projection.ts
7726
8511
  import * as React5 from "react";
@@ -8574,6 +9359,11 @@ init_logging();
8574
9359
  init_cache();
8575
9360
  init_rango_state();
8576
9361
 
9362
+ // src/browser/react/location-state-shared.ts
9363
+ function isLocationStateDefinition(value) {
9364
+ return typeof value === "function" && "__rsc_ls_flash" in value;
9365
+ }
9366
+
8577
9367
  // src/browser/react/NavigationProvider.tsx
8578
9368
  import { jsx as jsx7 } from "react/jsx-runtime";
8579
9369
 
@@ -8582,9 +9372,9 @@ import {
8582
9372
  forwardRef,
8583
9373
  useCallback as useCallback5,
8584
9374
  useContext as useContext15,
8585
- useEffect as useEffect15,
9375
+ useEffect as useEffect14,
8586
9376
  useMemo as useMemo7,
8587
- useRef as useRef13,
9377
+ useRef as useRef12,
8588
9378
  useSyncExternalStore
8589
9379
  } from "react";
8590
9380
 
@@ -8824,21 +9614,6 @@ function isPrefetchScopeDisabled(element) {
8824
9614
  return typeof element.closest === "function" && element.closest(PREFETCH_SCOPE_OPT_OUT_SELECTOR) !== null;
8825
9615
  }
8826
9616
 
8827
- // src/browser/react/location-state.ts
8828
- import { useState as useState14, useEffect as useEffect14, useRef as useRef12 } from "react";
8829
-
8830
- // src/browser/react/location-state-shared.ts
8831
- function isLocationStateEntry(value) {
8832
- return value !== null && typeof value === "object" && "__rsc_ls_key" in value && "__rsc_ls_value" in value && typeof value.__rsc_ls_key === "string";
8833
- }
8834
- function resolveLocationStateEntries(entries) {
8835
- const result = {};
8836
- for (const entry of entries) {
8837
- result[entry.__rsc_ls_key] = entry.__rsc_ls_lazy ? entry.__rsc_ls_value() : entry.__rsc_ls_value;
8838
- }
8839
- return result;
8840
- }
8841
-
8842
9617
  // src/browser/react/Link.tsx
8843
9618
  init_loader();
8844
9619
  import { jsx as jsx8 } from "react/jsx-runtime";
@@ -8865,6 +9640,7 @@ var Link = forwardRef(function Link2({
8865
9640
  scroll = true,
8866
9641
  reloadDocument = false,
8867
9642
  revalidate: revalidate2,
9643
+ transition: transition2,
8868
9644
  prefetch,
8869
9645
  prefetchKey,
8870
9646
  state,
@@ -8889,7 +9665,7 @@ var Link = forwardRef(function Link2({
8889
9665
  () => resolveAdaptiveStrategy(configuredStrategy),
8890
9666
  () => configuredStrategy === "adaptive" ? "hover" : configuredStrategy
8891
9667
  );
8892
- const internalRef = useRef13(null);
9668
+ const internalRef = useRef12(null);
8893
9669
  const setRef = useCallback5(
8894
9670
  (node) => {
8895
9671
  internalRef.current = node;
@@ -8901,7 +9677,7 @@ var Link = forwardRef(function Link2({
8901
9677
  },
8902
9678
  [ref]
8903
9679
  );
8904
- const stateRef = useRef13(state);
9680
+ const stateRef = useRef12(state);
8905
9681
  stateRef.current = state;
8906
9682
  const handleClick = useCallback5(
8907
9683
  (e) => {
@@ -8923,21 +9699,13 @@ var Link = forwardRef(function Link2({
8923
9699
  e.preventDefault();
8924
9700
  e.stopPropagation();
8925
9701
  const currentState = stateRef.current;
8926
- let resolvedState;
8927
- if (Array.isArray(currentState) && currentState.length > 0 && isLocationStateEntry(currentState[0])) {
8928
- resolvedState = resolveLocationStateEntries(
8929
- currentState
8930
- );
8931
- } else if (typeof currentState === "function") {
8932
- resolvedState = currentState();
8933
- } else if (currentState != null) {
8934
- resolvedState = currentState;
8935
- }
9702
+ const state2 = typeof currentState === "function" && !(process.env.NODE_ENV !== "production" && isLocationStateDefinition(currentState)) ? currentState() : currentState ?? void 0;
8936
9703
  ctx.navigate(resolvedTo, {
8937
9704
  replace,
8938
9705
  scroll,
8939
- state: resolvedState,
8940
- revalidate: revalidate2
9706
+ state: state2,
9707
+ revalidate: revalidate2,
9708
+ transition: transition2
8941
9709
  });
8942
9710
  },
8943
9711
  [
@@ -8947,6 +9715,7 @@ var Link = forwardRef(function Link2({
8947
9715
  replace,
8948
9716
  scroll,
8949
9717
  revalidate2,
9718
+ transition2,
8950
9719
  ctx,
8951
9720
  onClick
8952
9721
  ]
@@ -8964,7 +9733,7 @@ var Link = forwardRef(function Link2({
8964
9733
  );
8965
9734
  }
8966
9735
  }, [resolvedStrategy, resolvedTo, isExternal, ctx, prefetchKey]);
8967
- useEffect15(() => {
9736
+ useEffect14(() => {
8968
9737
  if (isExternal || !ctx?.store) return;
8969
9738
  const isViewport = resolvedStrategy === "viewport";
8970
9739
  const isRender = resolvedStrategy === "render";
@@ -9044,6 +9813,7 @@ var Link = forwardRef(function Link2({
9044
9813
  "data-scroll": scroll === false ? "false" : void 0,
9045
9814
  "data-replace": replace ? "true" : void 0,
9046
9815
  "data-revalidate": revalidate2 === false ? "false" : void 0,
9816
+ "data-transition": transition2 === false ? "false" : void 0,
9047
9817
  ...props,
9048
9818
  children: /* @__PURE__ */ jsx8(LinkContext.Provider, { value: resolvedTo, children })
9049
9819
  }
@@ -9077,6 +9847,9 @@ var Breadcrumbs = createHandle(
9077
9847
  "__rsc_router_breadcrumbs__"
9078
9848
  );
9079
9849
 
9850
+ // src/browser/react/location-state.ts
9851
+ import { useState as useState14, useEffect as useEffect15, useRef as useRef13 } from "react";
9852
+
9080
9853
  // src/href-client.ts
9081
9854
  function href(path6, mount) {
9082
9855
  if (mount && mount !== "/") {
@@ -9143,6 +9916,9 @@ var ALL_USE_ITEM_TYPES = new Set(
9143
9916
  import { AsyncLocalStorage as AsyncLocalStorage5 } from "node:async_hooks";
9144
9917
  init_cookie_name();
9145
9918
 
9919
+ // src/router/segment-resolution/mask-nested.ts
9920
+ import { cloneElement, isValidElement as isValidElement2 } from "react";
9921
+
9146
9922
  // src/server/handle-store.ts
9147
9923
  var LOADER_SLOT = Object.freeze({ loader: true });
9148
9924
 
@@ -9163,11 +9939,48 @@ globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:identity-read-recorder")]
9163
9939
  if (set === void 0 || identityReads.has(set)) return;
9164
9940
  identityReads.set(set, { surface, verb, bodyId: getCurrentLoaderBodyId() });
9165
9941
  };
9942
+ var loaderRuns = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:loader-runs")] ??= /* @__PURE__ */ new WeakMap();
9166
9943
 
9167
9944
  // src/router/router-context.ts
9168
9945
  import { AsyncLocalStorage as AsyncLocalStorage4 } from "async_hooks";
9169
9946
  var routerContext = new AsyncLocalStorage4();
9170
9947
 
9948
+ // src/server/cookie-store.ts
9949
+ var REQUEST_READ_CACHE_FIX = {
9950
+ useCache: 'Request-scoped data (cookies, headers) varies per request but is not reflected in the cache key, so cached results would be served to the wrong users. Extract the value before the cached function and pass it as an argument:\n\n const locale = cookies().get("locale")?.value ?? "en";\n const data = await getCachedData(locale); // locale is now in the cache key',
9951
+ cacheScope: 'A cache() scope caches everything except loaders, so request-scoped data (cookies, headers) read here would be frozen into the shared cached shell and served to other users. Read it inside a loader instead \u2014 loaders always run fresh on every request, even on a cache hit:\n\n loader("user", () => getUser(cookies().get("session")?.value));'
9952
+ };
9953
+ function requestReadWording(what) {
9954
+ return {
9955
+ verb: "called",
9956
+ fix: { ...REQUEST_READ_CACHE_FIX, ...requestReadCaptureFix(what) },
9957
+ record: false
9958
+ };
9959
+ }
9960
+ var COOKIES_READ = requestReadWording("cookies");
9961
+ var HEADERS_READ = requestReadWording("headers");
9962
+ var REQUEST_HEADERS_READ = {
9963
+ verb: "read",
9964
+ fix: HEADERS_READ.fix
9965
+ };
9966
+ var RAW_COOKIES_READ = {
9967
+ verb: "called",
9968
+ fix: COOKIES_READ.fix
9969
+ };
9970
+ 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).";
9971
+ var THEME_READ = {
9972
+ verb: "read",
9973
+ fix: {
9974
+ useCache: "The theme comes from the visitor's cookie and is not in the cache key, so the first caller's theme would be served to later callers. Read it before the cached function and pass it in as an argument.",
9975
+ cacheScope: "The theme comes from the visitor's cookie, so the first visitor's theme would be stored in the shared entry and served to everyone. " + THEME_READ_FIX,
9976
+ capture: `The captured shell is served to every visitor of this URL, so the capturing visitor's theme would reach everyone. ${THEME_READ_FIX}`,
9977
+ warning: THEME_READ_FIX
9978
+ }
9979
+ };
9980
+
9981
+ // src/cache/invalidation-order.ts
9982
+ var order = globalThis[/* @__PURE__ */ Symbol.for("rangojs-router:tag-invalidation-order")] ??= { seq: 0, tags: /* @__PURE__ */ new Map(), forgotten: 0 };
9983
+
9171
9984
  // src/server/request-context.ts
9172
9985
  var requestContextStorage = new AsyncLocalStorage5();
9173
9986
 
@@ -9306,7 +10119,7 @@ function serializeTransition(route2) {
9306
10119
  if (!source) return void 0;
9307
10120
  const config = {};
9308
10121
  for (const [key, value] of Object.entries(source)) {
9309
- if (value === void 0) continue;
10122
+ if (value === void 0 || key === "when") continue;
9310
10123
  config[key] = typeof value === "object" && value !== null ? Object.freeze({ ...value }) : value;
9311
10124
  }
9312
10125
  return Object.freeze(config);
@@ -9331,7 +10144,8 @@ function serializeRoute(route2) {
9331
10144
  loaderIds: Object.freeze(loaderIds),
9332
10145
  hasLoading: route2.loading !== void 0,
9333
10146
  ...awaitedLoaderIndices.length > 0 ? { awaitedLoaderIndices: Object.freeze(awaitedLoaderIndices) } : {},
9334
- ...transition2 ? { transition: transition2 } : {}
10147
+ ...transition2 ? { transition: transition2 } : {},
10148
+ ...typeof route2.transition?.when === "function" ? { transitionWhen: true } : {}
9335
10149
  });
9336
10150
  }
9337
10151
  function serializeIntercept(record, index) {
@@ -9367,7 +10181,7 @@ var debug10 = createRangoDebugger(NS.discovery);
9367
10181
  function hasDefaultExport(code) {
9368
10182
  let program;
9369
10183
  try {
9370
- program = parseAst5(code, { lang: "tsx" });
10184
+ program = parseAst6(code, { lang: "tsx" });
9371
10185
  } catch {
9372
10186
  return false;
9373
10187
  }
@@ -9384,18 +10198,18 @@ function hasDefaultExport(code) {
9384
10198
  function normalizeSource(state, id) {
9385
10199
  const cleanId = id.split("?", 1)[0];
9386
10200
  const absoluteId = isAbsolute3(cleanId) ? cleanId : resolve8(state.projectRoot, cleanId);
9387
- return normalizePath4(absoluteId);
10201
+ return normalizePath5(absoluteId);
9388
10202
  }
9389
10203
  function referenceId(referenceKey) {
9390
10204
  return `${referenceKey}#default`;
9391
10205
  }
9392
10206
  function recordClientUrlsModule(state, code, id) {
9393
- if (!code.includes("clientUrls(") || !hasUseClientDirective(code) || !hasDefaultExport(code)) {
10207
+ if (!code.includes("clientUrls(") || !hasUseClientDirective2(code) || !hasDefaultExport(code)) {
9394
10208
  return;
9395
10209
  }
9396
10210
  const source = normalizeSource(state, id);
9397
- const projectRoot = normalizePath4(resolve8(state.projectRoot));
9398
- const relativeId = normalizePath4(relative4(projectRoot, source));
10211
+ const projectRoot = normalizePath5(resolve8(state.projectRoot));
10212
+ const relativeId = normalizePath5(relative4(projectRoot, source));
9399
10213
  const outsideProject = relativeId === ".." || relativeId.startsWith("../") || isAbsolute3(relativeId);
9400
10214
  const devReferenceKey = outsideProject ? `/@fs${source}` : `/${relativeId}`;
9401
10215
  const sourceByReferenceId = state.clientUrlSourceByReferenceId ??= /* @__PURE__ */ new Map();
@@ -9631,7 +10445,10 @@ async function discoverRouters(state, rscEnv, ssrEnv) {
9631
10445
  await generateManifestFull(mount.patterns, routerMountIndex, {
9632
10446
  routerId: id,
9633
10447
  ...router.__basename ? { urlPrefix: router.__basename } : {},
9634
- ...collectClientFallbackRef ? { collectClientFallbackRef } : {}
10448
+ ...collectClientFallbackRef ? { collectClientFallbackRef } : {},
10449
+ // Discovery evaluates the tree in the react-server graph, where a
10450
+ // "use client" import is a client reference: reject server `when`s.
10451
+ validateTransitionWhen: true
9635
10452
  })
9636
10453
  );
9637
10454
  routerMountIndex++;
@@ -9915,9 +10732,7 @@ async function runShellPrerenderPhase(s, builder) {
9915
10732
  const value = {
9916
10733
  entry: res.entry,
9917
10734
  ttl: policy.ttl,
9918
- swr: policy.swr,
9919
- tags: res.tags,
9920
- routeName: cand.routeName
10735
+ tags: res.tags
9921
10736
  };
9922
10737
  staged.push({
9923
10738
  key: buildShellManifestKey(cand.urlPath),
@@ -10159,6 +10974,7 @@ function supplementGenFilesWithRuntimeRoutes(state) {
10159
10974
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync5 } from "node:fs";
10160
10975
  import { dirname as dirname4, basename, join as join7 } from "node:path";
10161
10976
  var MANIFEST_EXTERNALIZE_THRESHOLD = 512 * 1024;
10977
+ var TRANSITION_WHEN_VALIDATION = `enableTransitionWhenValidation();`;
10162
10978
  function devDiscoveryBootstrap(state) {
10163
10979
  if (state.isBuildMode || state.opts?.preset !== "cloudflare" || state.devDiscoveryEpoch === void 0) {
10164
10980
  return [];
@@ -10194,6 +11010,7 @@ function generateRoutesManifestModule(state) {
10194
11010
  }
10195
11011
  }
10196
11012
  const serverImports = [
11013
+ "enableTransitionWhenValidation",
10197
11014
  "setCachedManifest",
10198
11015
  "setRouterManifest",
10199
11016
  ...state.isBuildMode ? ["registerRouterManifestLoader"] : [],
@@ -10208,7 +11025,8 @@ function generateRoutesManifestModule(state) {
10208
11025
  // this virtual module but the route-map-builder singleton retains old data
10209
11026
  // because it's not in the HMR invalidation chain. Without this clear, the
10210
11027
  // handler finds stale trie data and never rebuilds from updated urlpatterns.
10211
- `clearAllRouterData();`
11028
+ `clearAllRouterData();`,
11029
+ TRANSITION_WHEN_VALIDATION
10212
11030
  ];
10213
11031
  if (hasClientUrlModules) {
10214
11032
  lines2.push(`clearClientUrlProjections();`);
@@ -10258,12 +11076,12 @@ function generateRoutesManifestModule(state) {
10258
11076
  lines2.push(...devDiscoveryBootstrap(state));
10259
11077
  return lines2.join("\n");
10260
11078
  }
10261
- const lines = [];
11079
+ const lines = [
11080
+ `import { ${hasClientUrlModules ? "clearClientUrlProjections, " : ""}enableTransitionWhenValidation } from "@rangojs/router/server";`,
11081
+ TRANSITION_WHEN_VALIDATION
11082
+ ];
10262
11083
  if (hasClientUrlModules) {
10263
- lines.push(
10264
- `import { clearClientUrlProjections } from "@rangojs/router/server";`,
10265
- `clearClientUrlProjections();`
10266
- );
11084
+ lines.push(`clearClientUrlProjections();`);
10267
11085
  }
10268
11086
  if (!state.isBuildMode) {
10269
11087
  const origin = state.devServerOrigin || state.devServer?.resolvedUrls?.local?.[0]?.replace(/\/$/, "") || state.devServer && `http://localhost:${state.devServer.config.server.port || 5173}`;
@@ -10273,7 +11091,7 @@ function generateRoutesManifestModule(state) {
10273
11091
  }
10274
11092
  }
10275
11093
  lines.push(...devDiscoveryBootstrap(state));
10276
- return lines.join("\n") || `// Route manifest will be populated at runtime`;
11094
+ return lines.join("\n");
10277
11095
  }
10278
11096
  function shortHash(input) {
10279
11097
  let h = 2166136261;
@@ -10692,6 +11510,9 @@ async function createTempRscServer(state, options = {}) {
10692
11510
  ] : [],
10693
11511
  createVirtualStubPlugin(),
10694
11512
  createCloudflareProtocolStubPlugin(),
11513
+ // Inline transition({ when }) literals become client references here
11514
+ // too, or discovery would see (and reject) the plain function.
11515
+ transitionWhenHoistPlugin(),
10695
11516
  // Dev prerender must use dev-mode IDs (path-based) to match the workerd
10696
11517
  // runtime. forceBuild produces hashed IDs for production bundle consistency.
10697
11518
  exposeInternalIds(options.forceBuild ? { forceBuild: true } : void 0),
@@ -10779,6 +11600,25 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
10779
11600
  const s = createDiscoveryState(entryPath, opts);
10780
11601
  let viteCommand = "build";
10781
11602
  let viteMode = "production";
11603
+ let transitionWhenOverlay = null;
11604
+ const sendTransitionWhenOverlay = () => {
11605
+ if (!transitionWhenOverlay) return;
11606
+ s.devServer?.environments?.client?.hot?.send({
11607
+ type: "error",
11608
+ err: transitionWhenOverlay
11609
+ });
11610
+ };
11611
+ const showTransitionWhenError = (err) => {
11612
+ const whenError2 = findTransitionWhenError(err);
11613
+ if (!whenError2) return void 0;
11614
+ transitionWhenOverlay = {
11615
+ message: whenError2.message,
11616
+ stack: whenError2.stack ?? "",
11617
+ plugin: "@rangojs/router"
11618
+ };
11619
+ sendTransitionWhenOverlay();
11620
+ return whenError2;
11621
+ };
10782
11622
  return {
10783
11623
  name: "@rangojs/router:discovery",
10784
11624
  // Make INTERNAL_RANGO_DEBUG reach the CLIENT debug logs by just setting the
@@ -10829,6 +11669,25 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
10829
11669
  if (s.isBuildMode) return;
10830
11670
  if (globalThis.__rscRouterDiscoveryActive) return;
10831
11671
  s.devServer = server;
11672
+ server.environments?.client?.hot?.on?.(
11673
+ "vite:client:connect",
11674
+ (_payload, client) => {
11675
+ if (transitionWhenOverlay) {
11676
+ client?.send?.({ type: "error", err: transitionWhenOverlay });
11677
+ }
11678
+ }
11679
+ );
11680
+ let overlayResendQueued = false;
11681
+ server.httpServer?.on("request", (req, res) => {
11682
+ const isPage = req.url?.includes("_rsc") === true || req.headers.accept?.includes("text/html") === true;
11683
+ if (transitionWhenOverlay && isPage && !overlayResendQueued) {
11684
+ overlayResendQueued = true;
11685
+ res.once("close", () => {
11686
+ overlayResendQueued = false;
11687
+ sendTransitionWhenOverlay();
11688
+ });
11689
+ }
11690
+ });
10832
11691
  let workerReadyEpoch;
10833
11692
  const publishDevDiscoveryReady = (epoch) => {
10834
11693
  if (epoch <= (workerReadyEpoch ?? -1)) return;
@@ -11047,6 +11906,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
11047
11906
  } else {
11048
11907
  console.error(report.message);
11049
11908
  }
11909
+ showTransitionWhenError(err);
11050
11910
  };
11051
11911
  const discover = async () => {
11052
11912
  const discoverStart = performance.now();
@@ -11463,9 +12323,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
11463
12323
  const body = JSON.stringify({
11464
12324
  entry: result.entry,
11465
12325
  ttl,
11466
- swr,
11467
- tags: result.tags,
11468
- routeName
12326
+ tags: result.tags
11469
12327
  });
11470
12328
  devPrerenderCache.set(routerInstance, cacheKey, body);
11471
12329
  res.setHeader("content-type", "application/json");
@@ -11575,6 +12433,7 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
11575
12433
  );
11576
12434
  s.lastDiscoveryError = null;
11577
12435
  }
12436
+ transitionWhenOverlay = null;
11578
12437
  if (rscEnv && !rscEnv.runner) {
11579
12438
  const nextRouteShape = routeShapeSignature();
11580
12439
  let expectedEpoch;
@@ -11593,9 +12452,14 @@ function createRouterDiscoveryPlugin(entryPath, opts) {
11593
12452
  message: err?.message ?? String(err),
11594
12453
  at: Date.now()
11595
12454
  };
11596
- console.warn(
11597
- `[rango] Runtime re-discovery failed: ${err.message}`
11598
- );
12455
+ const whenError2 = showTransitionWhenError(err);
12456
+ if (whenError2) {
12457
+ console.error(whenError2.message);
12458
+ } else {
12459
+ console.warn(
12460
+ `[rango] Runtime re-discovery failed: ${err.message}`
12461
+ );
12462
+ }
11599
12463
  debugDiscovery?.(
11600
12464
  "hmr: lastDiscoveryError set (%s) \u2014 manifest preserved at last-good; recovery mode active (any in-scan source change will trigger rediscovery)",
11601
12465
  err?.message
@@ -12495,7 +13359,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
12495
13359
  return;
12496
13360
  try {
12497
13361
  const source = readFileSync10(file, "utf-8");
12498
- if (hasUseClientDirective(source)) {
13362
+ if (hasUseClientDirective2(source)) {
12499
13363
  return [];
12500
13364
  }
12501
13365
  } catch {
@@ -12503,6 +13367,7 @@ If this is a multi-app host router, export a createHostRouter() instance and set
12503
13367
  }
12504
13368
  });
12505
13369
  plugins.push(createLoaderDirectiveGuardPlugin());
13370
+ plugins.push(transitionWhenHoistPlugin());
12506
13371
  plugins.push(createLoaderScanStubPlugin());
12507
13372
  plugins.push(exposeActionId());
12508
13373
  plugins.push(