@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847

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 (298) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -212
  4. package/dist/vite/index.js +3995 -2489
  5. package/package.json +57 -52
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +85 -23
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +6 -4
  13. package/skills/hooks/SKILL.md +328 -70
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +62 -15
  18. package/skills/loader/SKILL.md +368 -42
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +14 -10
  21. package/skills/parallel/SKILL.md +137 -1
  22. package/skills/prerender/SKILL.md +366 -28
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +136 -83
  25. package/skills/route/SKILL.md +195 -21
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +240 -102
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +102 -4
  31. package/src/bin/rango.ts +312 -15
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +92 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +24 -4
  38. package/src/browser/logging.ts +11 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +266 -558
  41. package/src/browser/navigation-client.ts +132 -75
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +297 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +303 -309
  46. package/src/browser/prefetch/cache.ts +206 -0
  47. package/src/browser/prefetch/fetch.ts +144 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +48 -0
  50. package/src/browser/prefetch/queue.ts +128 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +190 -70
  53. package/src/browser/react/NavigationProvider.tsx +78 -11
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +6 -1
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +29 -70
  65. package/src/browser/react/use-link-status.ts +6 -5
  66. package/src/browser/react/use-navigation.ts +22 -63
  67. package/src/browser/react/use-params.ts +65 -0
  68. package/src/browser/react/use-pathname.ts +47 -0
  69. package/src/browser/react/use-router.ts +63 -0
  70. package/src/browser/react/use-search-params.ts +56 -0
  71. package/src/browser/react/use-segments.ts +80 -97
  72. package/src/browser/response-adapter.ts +73 -0
  73. package/src/browser/rsc-router.tsx +188 -57
  74. package/src/browser/scroll-restoration.ts +117 -44
  75. package/src/browser/segment-reconciler.ts +221 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +488 -606
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +116 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +63 -21
  82. package/src/build/generate-route-types.ts +36 -1038
  83. package/src/build/index.ts +2 -5
  84. package/src/build/route-trie.ts +38 -12
  85. package/src/build/route-types/ast-helpers.ts +25 -0
  86. package/src/build/route-types/ast-route-extraction.ts +98 -0
  87. package/src/build/route-types/codegen.ts +102 -0
  88. package/src/build/route-types/include-resolution.ts +411 -0
  89. package/src/build/route-types/param-extraction.ts +48 -0
  90. package/src/build/route-types/per-module-writer.ts +128 -0
  91. package/src/build/route-types/router-processing.ts +479 -0
  92. package/src/build/route-types/scan-filter.ts +78 -0
  93. package/src/build/runtime-discovery.ts +231 -0
  94. package/src/cache/background-task.ts +34 -0
  95. package/src/cache/cache-key-utils.ts +44 -0
  96. package/src/cache/cache-policy.ts +125 -0
  97. package/src/cache/cache-runtime.ts +342 -0
  98. package/src/cache/cache-scope.ts +122 -303
  99. package/src/cache/cf/cf-cache-store.ts +571 -17
  100. package/src/cache/cf/index.ts +13 -3
  101. package/src/cache/document-cache.ts +116 -77
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +1 -15
  105. package/src/cache/memory-segment-store.ts +191 -13
  106. package/src/cache/profile-registry.ts +73 -0
  107. package/src/cache/read-through-swr.ts +134 -0
  108. package/src/cache/segment-codec.ts +256 -0
  109. package/src/cache/taint.ts +98 -0
  110. package/src/cache/types.ts +72 -122
  111. package/src/client.rsc.tsx +3 -1
  112. package/src/client.tsx +84 -126
  113. package/src/component-utils.ts +4 -4
  114. package/src/components/DefaultDocument.tsx +5 -1
  115. package/src/context-var.ts +86 -0
  116. package/src/debug.ts +19 -9
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +12 -7
  119. package/src/handles/MetaTags.tsx +73 -20
  120. package/src/handles/breadcrumbs.ts +66 -0
  121. package/src/handles/index.ts +1 -0
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +21 -15
  124. package/src/host/errors.ts +8 -8
  125. package/src/host/index.ts +4 -7
  126. package/src/host/pattern-matcher.ts +27 -27
  127. package/src/host/router.ts +61 -39
  128. package/src/host/testing.ts +8 -8
  129. package/src/host/types.ts +15 -7
  130. package/src/host/utils.ts +1 -1
  131. package/src/href-client.ts +65 -45
  132. package/src/index.rsc.ts +104 -40
  133. package/src/index.ts +122 -67
  134. package/src/internal-debug.ts +9 -3
  135. package/src/loader.rsc.ts +18 -93
  136. package/src/loader.ts +26 -9
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +4 -2
  140. package/src/prerender/store.ts +121 -17
  141. package/src/prerender.ts +325 -20
  142. package/src/reverse.ts +144 -124
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +7 -4
  145. package/src/route-definition/dsl-helpers.ts +959 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1450
  151. package/src/route-map-builder.ts +87 -133
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +41 -6
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +160 -0
  158. package/src/router/handler-context.ts +324 -116
  159. package/src/router/intercept-resolution.ts +11 -4
  160. package/src/router/lazy-includes.ts +237 -0
  161. package/src/router/loader-resolution.ts +179 -133
  162. package/src/router/logging.ts +112 -6
  163. package/src/router/manifest.ts +58 -19
  164. package/src/router/match-api.ts +89 -88
  165. package/src/router/match-context.ts +4 -2
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +86 -89
  168. package/src/router/match-middleware/cache-lookup.ts +295 -49
  169. package/src/router/match-middleware/cache-store.ts +56 -13
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -22
  171. package/src/router/match-middleware/segment-resolution.ts +20 -9
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +44 -21
  174. package/src/router/metrics.ts +240 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +327 -369
  178. package/src/router/pattern-matching.ts +169 -31
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +105 -14
  182. package/src/router/router-context.ts +40 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +677 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +199 -0
  189. package/src/router/segment-resolution/revalidation.ts +1296 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1354
  192. package/src/router/segment-wrappers.ts +291 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +96 -29
  197. package/src/router/types.ts +15 -9
  198. package/src/router.ts +642 -2366
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +639 -1027
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +0 -20
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +237 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +38 -11
  215. package/src/search-params.ts +66 -54
  216. package/src/segment-system.tsx +165 -17
  217. package/src/server/context.ts +237 -54
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +11 -6
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +438 -71
  223. package/src/server.ts +26 -164
  224. package/src/ssr/index.tsx +101 -31
  225. package/src/static-handler.ts +22 -4
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +773 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +109 -0
  241. package/src/types/segments.ts +150 -0
  242. package/src/types.ts +1 -1795
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -1323
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +108 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -2259
  261. package/src/vite/plugin-types.ts +48 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -47
  266. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +266 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +445 -0
  280. package/src/vite/router-discovery.ts +777 -0
  281. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -43
  289. package/dist/vite/index.named-routes.gen.ts +0 -103
  290. package/src/browser/lru-cache.ts +0 -69
  291. package/src/browser/request-controller.ts +0 -164
  292. package/src/cache/memory-store.ts +0 -253
  293. package/src/href-context.ts +0 -33
  294. package/src/router.gen.ts +0 -6
  295. package/src/static-handler.gen.ts +0 -5
  296. package/src/urls.gen.ts +0 -8
  297. package/src/vite/expose-internal-ids.ts +0 -1167
  298. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -17,15 +17,12 @@
17
17
 
18
18
  export {
19
19
  generateManifest,
20
+ generateManifestFull,
20
21
  generateManifestCode,
21
22
  type GeneratedManifest,
22
23
  } from "./generate-manifest.js";
23
24
 
24
- export {
25
- buildRouteTrie,
26
- type TrieNode,
27
- type TrieLeaf,
28
- } from "./route-trie.js";
25
+ export { buildRouteTrie, type TrieNode, type TrieLeaf } from "./route-trie.js";
29
26
 
30
27
  export {
31
28
  writePerModuleRouteTypes,
@@ -6,7 +6,10 @@
6
6
  * shortCodes for layout pruning.
7
7
  */
8
8
 
9
- import { parsePattern, type ParsedSegment } from "../router/pattern-matching.js";
9
+ import {
10
+ parsePattern,
11
+ type ParsedSegment,
12
+ } from "../router/pattern-matching.js";
10
13
 
11
14
  // -- Trie data structures (compact keys for JSON serialization) --
12
15
 
@@ -21,6 +24,8 @@ export interface TrieLeaf {
21
24
  op?: string[];
22
25
  /** Constraint validation: paramName -> allowed values */
23
26
  cv?: Record<string, string[]>;
27
+ /** Ordered param names for this route (positional) */
28
+ pa?: string[];
24
29
  /** Trailing slash mode */
25
30
  ts?: string;
26
31
  /** Route has pre-rendered data available */
@@ -42,6 +47,8 @@ export interface TrieNode {
42
47
  s?: Record<string, TrieNode>;
43
48
  /** Param child: { n: paramName, c: child node } */
44
49
  p?: { n: string; c: TrieNode };
50
+ /** Suffix-param children keyed by suffix (e.g., ".html" → { n: "productId", c: ... }) */
51
+ xp?: Record<string, { n: string; c: TrieNode }>;
45
52
  /** Wildcard terminal: leaf + paramName */
46
53
  w?: TrieLeaf & { pn: string };
47
54
  }
@@ -98,23 +105,28 @@ function insertRoute(
98
105
  node: TrieNode,
99
106
  segments: ParsedSegment[],
100
107
  index: number,
101
- leaf: Omit<TrieLeaf, "op" | "cv">,
108
+ leaf: Omit<TrieLeaf, "op" | "cv" | "pa">,
102
109
  ): void {
103
- // Collect optional param names and constraints across all segments
110
+ // Collect param names, optional param names, and constraints across all segments
111
+ const paramNames: string[] = [];
104
112
  const optionalParams: string[] = [];
105
113
  const constraints: Record<string, string[]> = {};
106
114
 
107
115
  for (const seg of segments) {
108
- if (seg.type === "param" && seg.optional) {
109
- optionalParams.push(seg.value);
110
- }
111
- if (seg.type === "param" && seg.constraint) {
112
- constraints[seg.value] = seg.constraint;
116
+ if (seg.type === "param") {
117
+ paramNames.push(seg.value);
118
+ if (seg.optional) {
119
+ optionalParams.push(seg.value);
120
+ }
121
+ if (seg.constraint) {
122
+ constraints[seg.value] = seg.constraint;
123
+ }
113
124
  }
114
125
  }
115
126
 
116
127
  const fullLeaf: TrieLeaf = {
117
128
  ...leaf,
129
+ ...(paramNames.length > 0 ? { pa: paramNames } : {}),
118
130
  ...(optionalParams.length > 0 ? { op: optionalParams } : {}),
119
131
  ...(Object.keys(constraints).length > 0 ? { cv: constraints } : {}),
120
132
  };
@@ -148,6 +160,11 @@ export function extractAncestryFromTrie(
148
160
  visit(child);
149
161
  }
150
162
  }
163
+ if (node.xp) {
164
+ for (const child of Object.values(node.xp)) {
165
+ visit(child.c);
166
+ }
167
+ }
151
168
  if (node.p) {
152
169
  visit(node.p.c);
153
170
  }
@@ -225,14 +242,23 @@ function insertSegments(
225
242
  mergeLeaf(node, leaf);
226
243
  // AND continue with param child (param present)
227
244
  }
228
- if (!node.p) {
229
- node.p = { n: segment.value, c: {} };
245
+ if (segment.suffix) {
246
+ // Suffix param: keyed by suffix string (e.g., ".html")
247
+ if (!node.xp) node.xp = {};
248
+ if (!node.xp[segment.suffix]) {
249
+ node.xp[segment.suffix] = { n: segment.value, c: {} };
250
+ }
251
+ insertSegments(node.xp[segment.suffix].c, segments, index + 1, leaf);
252
+ } else {
253
+ if (!node.p) {
254
+ node.p = { n: segment.value, c: {} };
255
+ }
256
+ insertSegments(node.p.c, segments, index + 1, leaf);
230
257
  }
231
- insertSegments(node.p.c, segments, index + 1, leaf);
232
258
  } else if (segment.type === "wildcard") {
233
259
  // Wildcard consumes all remaining segments
234
260
  const wildLeaf = { ...leaf, pn: "*" };
235
- const existing = node.w ? { ...node.w } as TrieLeaf : undefined;
261
+ const existing = node.w ? ({ ...node.w } as TrieLeaf) : undefined;
236
262
  const merged = mergeLeaves(existing, wildLeaf);
237
263
  node.w = merged as TrieLeaf & { pn: string };
238
264
  }
@@ -0,0 +1,25 @@
1
+ import ts from "typescript";
2
+
3
+ export function getStringValue(node: ts.Node): string | null {
4
+ if (ts.isStringLiteral(node)) return node.text;
5
+ if (ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
6
+ return null;
7
+ }
8
+
9
+ export function extractObjectStringProperties(
10
+ node: ts.ObjectLiteralExpression,
11
+ ): Record<string, string> {
12
+ const result: Record<string, string> = {};
13
+ for (const prop of node.properties) {
14
+ if (!ts.isPropertyAssignment(prop)) continue;
15
+ const key = ts.isIdentifier(prop.name)
16
+ ? prop.name.text
17
+ : ts.isStringLiteral(prop.name)
18
+ ? prop.name.text
19
+ : null;
20
+ if (!key) continue;
21
+ const val = getStringValue(prop.initializer);
22
+ if (val !== null) result[key] = val;
23
+ }
24
+ return result;
25
+ }
@@ -0,0 +1,98 @@
1
+ import ts from "typescript";
2
+ import {
3
+ getStringValue,
4
+ extractObjectStringProperties,
5
+ } from "./ast-helpers.js";
6
+ import { extractParamsFromPattern } from "./param-extraction.js";
7
+
8
+ // ---------------------------------------------------------------------------
9
+ // AST-based route extraction
10
+ // ---------------------------------------------------------------------------
11
+
12
+ /**
13
+ * Extract route definitions from source code by walking the TypeScript AST.
14
+ * Finds path() and path.json(), path.md(), etc. call expressions and extracts
15
+ * the pattern, name, params, and optional search schema from each.
16
+ * Skips unnamed paths (no { name: "..." }).
17
+ */
18
+ export function extractRoutesFromSource(code: string): Array<{
19
+ name: string;
20
+ pattern: string;
21
+ params?: Record<string, string>;
22
+ search?: Record<string, string>;
23
+ }> {
24
+ const sourceFile = ts.createSourceFile(
25
+ "input.tsx",
26
+ code,
27
+ ts.ScriptTarget.Latest,
28
+ true,
29
+ ts.ScriptKind.TSX,
30
+ );
31
+ const routes: Array<{
32
+ name: string;
33
+ pattern: string;
34
+ params?: Record<string, string>;
35
+ search?: Record<string, string>;
36
+ }> = [];
37
+
38
+ function visit(node: ts.Node) {
39
+ if (ts.isCallExpression(node)) {
40
+ const callee = node.expression;
41
+ const isPath =
42
+ (ts.isIdentifier(callee) && callee.text === "path") ||
43
+ (ts.isPropertyAccessExpression(callee) &&
44
+ ts.isIdentifier(callee.expression) &&
45
+ callee.expression.text === "path");
46
+
47
+ if (isPath && node.arguments.length >= 1) {
48
+ const route = extractRouteFromCallExpression(node);
49
+ if (route) routes.push(route);
50
+ }
51
+ }
52
+ ts.forEachChild(node, visit);
53
+ }
54
+
55
+ visit(sourceFile);
56
+ return routes;
57
+ }
58
+
59
+ function extractRouteFromCallExpression(node: ts.CallExpression): {
60
+ name: string;
61
+ pattern: string;
62
+ params?: Record<string, string>;
63
+ search?: Record<string, string>;
64
+ } | null {
65
+ const patternNode = node.arguments[0];
66
+ const pattern = getStringValue(patternNode);
67
+ if (pattern === null) return null;
68
+
69
+ let name: string | null = null;
70
+ let search: Record<string, string> | undefined;
71
+
72
+ for (let i = 1; i < node.arguments.length; i++) {
73
+ const arg = node.arguments[i];
74
+ if (ts.isObjectLiteralExpression(arg)) {
75
+ for (const prop of arg.properties) {
76
+ if (!ts.isPropertyAssignment(prop)) continue;
77
+ const propName = ts.isIdentifier(prop.name) ? prop.name.text : null;
78
+ if (propName === "name") {
79
+ name = getStringValue(prop.initializer);
80
+ } else if (
81
+ propName === "search" &&
82
+ ts.isObjectLiteralExpression(prop.initializer)
83
+ ) {
84
+ search = extractObjectStringProperties(prop.initializer);
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ if (!name) return null;
91
+ const params = extractParamsFromPattern(pattern);
92
+ return {
93
+ name,
94
+ pattern,
95
+ ...(params ? { params } : {}),
96
+ ...(search && Object.keys(search).length > 0 ? { search } : {}),
97
+ };
98
+ }
@@ -0,0 +1,102 @@
1
+ import {
2
+ extractParamsFromPattern,
3
+ formatRouteEntry,
4
+ } from "./param-extraction.js";
5
+ import { isAutoGeneratedRouteName } from "../../route-name.js";
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Code generation
9
+ // ---------------------------------------------------------------------------
10
+
11
+ /**
12
+ * Generate a per-module types file from extracted routes.
13
+ * Output has zero imports, preventing circular references.
14
+ */
15
+ export function generatePerModuleTypesSource(
16
+ routes: Array<{
17
+ name: string;
18
+ pattern: string;
19
+ params?: Record<string, string>;
20
+ search?: Record<string, string>;
21
+ }>,
22
+ ): string {
23
+ const valid = routes.filter(({ name }) => {
24
+ if (!name || /["'\\`\n\r]/.test(name)) {
25
+ console.warn(
26
+ `[rsc-router] Skipping route with invalid name: ${JSON.stringify(name)}`,
27
+ );
28
+ return false;
29
+ }
30
+ return true;
31
+ });
32
+
33
+ // Deduplicate by name (first definition wins -- primary route before variants)
34
+ const deduped = new Map<
35
+ string,
36
+ {
37
+ pattern: string;
38
+ params?: Record<string, string>;
39
+ search?: Record<string, string>;
40
+ }
41
+ >();
42
+ for (const { name, pattern, params, search } of valid) {
43
+ if (deduped.has(name)) {
44
+ console.warn(
45
+ `[rsc-router] Duplicate route name "${name}" — keeping first definition`,
46
+ );
47
+ continue;
48
+ }
49
+ deduped.set(name, { pattern, params, search });
50
+ }
51
+ const sorted = [...deduped.entries()].sort(([a], [b]) => a.localeCompare(b));
52
+ const body = sorted
53
+ .map(([name, { pattern, params, search }]) => {
54
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
55
+ return formatRouteEntry(key, pattern, params, search);
56
+ })
57
+ .join("\n");
58
+ return `// Auto-generated by @rangojs/router - do not edit\nexport const routes = {\n${body}\n} as const;\nexport type routes = typeof routes;\n`;
59
+ }
60
+
61
+ /**
62
+ * Generates a .ts file that augments RSCRouter.GeneratedRouteMap
63
+ * with route name -> pattern mappings. This enables Handler<"routeName">
64
+ * without circular references since the file has no imports from the app.
65
+ */
66
+ export function generateRouteTypesSource(
67
+ routeManifest: Record<string, string>,
68
+ searchSchemas?: Record<string, Record<string, string>>,
69
+ ): string {
70
+ const entries = Object.entries(routeManifest)
71
+ .filter(([name]) => !isAutoGeneratedRouteName(name))
72
+ .sort(([a], [b]) => a.localeCompare(b));
73
+
74
+ const filteredSearchSchemas = searchSchemas
75
+ ? Object.fromEntries(
76
+ Object.entries(searchSchemas).filter(
77
+ ([name]) => !isAutoGeneratedRouteName(name),
78
+ ),
79
+ )
80
+ : undefined;
81
+
82
+ const objectBody = entries
83
+ .map(([name, pattern]) => {
84
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
85
+ const params = extractParamsFromPattern(pattern);
86
+ const search = filteredSearchSchemas?.[name];
87
+ return formatRouteEntry(key, pattern, params, search);
88
+ })
89
+ .join("\n");
90
+
91
+ return `// Auto-generated by @rangojs/router - do not edit
92
+ export const NamedRoutes = {
93
+ ${objectBody}
94
+ } as const;
95
+
96
+ declare global {
97
+ namespace RSCRouter {
98
+ interface GeneratedRouteMap extends Readonly<typeof NamedRoutes> {}
99
+ }
100
+ }
101
+ `;
102
+ }