@rangojs/router 0.0.0-experimental.9 → 0.0.0-experimental.a5f27bd5

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 (299) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -155
  4. package/dist/vite/index.js +4440 -2170
  5. package/package.json +60 -54
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  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 +333 -71
  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 +74 -15
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +15 -11
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +405 -45
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +144 -91
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +316 -87
  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 +87 -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 +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +285 -553
  41. package/src/browser/navigation-client.ts +123 -73
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +261 -309
  46. package/src/browser/prefetch/cache.ts +154 -0
  47. package/src/browser/prefetch/fetch.ts +135 -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 +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +182 -70
  53. package/src/browser/react/NavigationProvider.tsx +51 -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 +106 -27
  74. package/src/browser/scroll-restoration.ts +92 -16
  75. package/src/browser/segment-reconciler.ts +216 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +504 -599
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +107 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +82 -21
  82. package/src/build/generate-route-types.ts +36 -752
  83. package/src/build/index.ts +6 -5
  84. package/src/build/route-trie.ts +39 -13
  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 +469 -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 +338 -0
  98. package/src/cache/cache-scope.ts +120 -301
  99. package/src/cache/cf/cf-cache-store.ts +119 -7
  100. package/src/cache/cf/index.ts +8 -2
  101. package/src/cache/document-cache.ts +101 -72
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +0 -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 +17 -7
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +15 -10
  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 +133 -21
  133. package/src/index.ts +164 -52
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +25 -143
  136. package/src/loader.ts +27 -10
  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 +158 -13
  141. package/src/prerender.ts +333 -26
  142. package/src/reverse.ts +184 -121
  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 +934 -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 -1431
  151. package/src/route-map-builder.ts +156 -123
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +48 -9
  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 +158 -0
  158. package/src/router/handler-context.ts +374 -81
  159. package/src/router/intercept-resolution.ts +24 -16
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +83 -32
  164. package/src/router/match-api.ts +118 -119
  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 +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +336 -84
  169. package/src/router/match-middleware/cache-store.ts +43 -24
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -20
  171. package/src/router/match-middleware/segment-resolution.ts +16 -8
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -28
  174. package/src/router/metrics.ts +235 -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 +324 -367
  178. package/src/router/pattern-matching.ts +197 -41
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -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 +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1239 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1315
  192. package/src/router/segment-wrappers.ts +289 -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 +16 -9
  198. package/src/router.ts +590 -1983
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +661 -1015
  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 +230 -0
  216. package/src/segment-system.tsx +25 -13
  217. package/src/server/context.ts +173 -48
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  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 +430 -70
  223. package/src/server.ts +35 -155
  224. package/src/ssr/index.tsx +100 -31
  225. package/src/static-handler.ts +114 -0
  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 +687 -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 +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1757
  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 -1282
  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 +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -1963
  261. package/src/vite/plugin-types.ts +131 -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 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  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 +254 -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 +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  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/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/router.gen.ts +0 -6
  294. package/src/urls.gen.ts +0 -8
  295. package/src/vite/expose-handle-id.ts +0 -209
  296. package/src/vite/expose-loader-id.ts +0 -426
  297. package/src/vite/expose-location-state-id.ts +0 -177
  298. package/src/vite/expose-prerender-handler-id.ts +0 -429
  299. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -17,18 +17,19 @@
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,
32
29
  extractRoutesFromSource,
33
30
  generatePerModuleTypesSource,
31
+ createScanFilter,
32
+ type ScanFilter,
34
33
  } from "./generate-route-types.js";
34
+
35
+ export { hashParams } from "../prerender/param-hash.js";
@@ -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
  }
@@ -188,8 +205,8 @@ function mergeLeaves(existing: TrieLeaf | undefined, leaf: TrieLeaf): TrieLeaf {
188
205
  // Response-type was primary, new leaf is RSC: swap and move old to variants
189
206
  // RSC was defined second (response-type was already the existing leaf)
190
207
  if (!leaf.nv) leaf.nv = [];
191
- leaf.nv.push({ routeKey: existing.n, responseType: existing.rt });
192
208
  if (existing.nv) leaf.nv.push(...existing.nv);
209
+ leaf.nv.push({ routeKey: existing.n, responseType: existing.rt });
193
210
  // rf intentionally not set — RSC came after response-type variants
194
211
  return leaf;
195
212
  }
@@ -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
+ }