@llui/compiler 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (310) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accessor-resolver.d.ts +58 -0
  3. package/dist/accessor-resolver.d.ts.map +1 -0
  4. package/dist/accessor-resolver.js +119 -0
  5. package/dist/accessor-resolver.js.map +1 -0
  6. package/dist/binding-descriptors.d.ts +105 -0
  7. package/dist/binding-descriptors.d.ts.map +1 -0
  8. package/dist/binding-descriptors.js +340 -0
  9. package/dist/binding-descriptors.js.map +1 -0
  10. package/dist/collect-deps.d.ts +49 -0
  11. package/dist/collect-deps.d.ts.map +1 -0
  12. package/dist/collect-deps.js +444 -0
  13. package/dist/collect-deps.js.map +1 -0
  14. package/dist/compiler-cache.d.ts +20 -0
  15. package/dist/compiler-cache.d.ts.map +1 -0
  16. package/dist/compiler-cache.js +20 -0
  17. package/dist/compiler-cache.js.map +1 -0
  18. package/dist/cross-file-resolver.d.ts +109 -0
  19. package/dist/cross-file-resolver.d.ts.map +1 -0
  20. package/dist/cross-file-resolver.js +530 -0
  21. package/dist/cross-file-resolver.js.map +1 -0
  22. package/dist/cross-file-walker.d.ts +63 -0
  23. package/dist/cross-file-walker.d.ts.map +1 -0
  24. package/dist/cross-file-walker.js +516 -0
  25. package/dist/cross-file-walker.js.map +1 -0
  26. package/dist/diagnostic.d.ts +76 -0
  27. package/dist/diagnostic.d.ts.map +1 -0
  28. package/dist/diagnostic.js +59 -0
  29. package/dist/diagnostic.js.map +1 -0
  30. package/dist/index.d.ts +27 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +37 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/introspection-factory.d.ts +54 -0
  35. package/dist/introspection-factory.d.ts.map +1 -0
  36. package/dist/introspection-factory.js +46 -0
  37. package/dist/introspection-factory.js.map +1 -0
  38. package/dist/manifest.d.ts +144 -0
  39. package/dist/manifest.d.ts.map +1 -0
  40. package/dist/manifest.js +209 -0
  41. package/dist/manifest.js.map +1 -0
  42. package/dist/module.d.ts +222 -0
  43. package/dist/module.d.ts.map +1 -0
  44. package/dist/module.js +256 -0
  45. package/dist/module.js.map +1 -0
  46. package/dist/modules/_element-helpers.d.ts +4 -0
  47. package/dist/modules/_element-helpers.d.ts.map +1 -0
  48. package/dist/modules/_element-helpers.js +138 -0
  49. package/dist/modules/_element-helpers.js.map +1 -0
  50. package/dist/modules/_msg-variants.d.ts +10 -0
  51. package/dist/modules/_msg-variants.d.ts.map +1 -0
  52. package/dist/modules/_msg-variants.js +97 -0
  53. package/dist/modules/_msg-variants.js.map +1 -0
  54. package/dist/modules/_shared.d.ts +16 -0
  55. package/dist/modules/_shared.d.ts.map +1 -0
  56. package/dist/modules/_shared.js +30 -0
  57. package/dist/modules/_shared.js.map +1 -0
  58. package/dist/modules/accessibility.d.ts +3 -0
  59. package/dist/modules/accessibility.d.ts.map +1 -0
  60. package/dist/modules/accessibility.js +82 -0
  61. package/dist/modules/accessibility.js.map +1 -0
  62. package/dist/modules/accessor-side-effect.d.ts +3 -0
  63. package/dist/modules/accessor-side-effect.d.ts.map +1 -0
  64. package/dist/modules/accessor-side-effect.js +113 -0
  65. package/dist/modules/accessor-side-effect.js.map +1 -0
  66. package/dist/modules/agent-emits-drift.d.ts +3 -0
  67. package/dist/modules/agent-emits-drift.d.ts.map +1 -0
  68. package/dist/modules/agent-emits-drift.js +158 -0
  69. package/dist/modules/agent-emits-drift.js.map +1 -0
  70. package/dist/modules/agent-example-on-payload.d.ts +3 -0
  71. package/dist/modules/agent-example-on-payload.d.ts.map +1 -0
  72. package/dist/modules/agent-example-on-payload.js +53 -0
  73. package/dist/modules/agent-example-on-payload.js.map +1 -0
  74. package/dist/modules/agent-exclusive-annotations.d.ts +3 -0
  75. package/dist/modules/agent-exclusive-annotations.d.ts.map +1 -0
  76. package/dist/modules/agent-exclusive-annotations.js +68 -0
  77. package/dist/modules/agent-exclusive-annotations.js.map +1 -0
  78. package/dist/modules/agent-missing-intent.d.ts +3 -0
  79. package/dist/modules/agent-missing-intent.d.ts.map +1 -0
  80. package/dist/modules/agent-missing-intent.js +47 -0
  81. package/dist/modules/agent-missing-intent.js.map +1 -0
  82. package/dist/modules/agent-msg-resolvable.d.ts +3 -0
  83. package/dist/modules/agent-msg-resolvable.d.ts.map +1 -0
  84. package/dist/modules/agent-msg-resolvable.js +161 -0
  85. package/dist/modules/agent-msg-resolvable.js.map +1 -0
  86. package/dist/modules/agent-nonextractable-handler.d.ts +3 -0
  87. package/dist/modules/agent-nonextractable-handler.d.ts.map +1 -0
  88. package/dist/modules/agent-nonextractable-handler.js +127 -0
  89. package/dist/modules/agent-nonextractable-handler.js.map +1 -0
  90. package/dist/modules/agent-optional-field-undocumented.d.ts +3 -0
  91. package/dist/modules/agent-optional-field-undocumented.d.ts.map +1 -0
  92. package/dist/modules/agent-optional-field-undocumented.js +67 -0
  93. package/dist/modules/agent-optional-field-undocumented.js.map +1 -0
  94. package/dist/modules/agent-tagsend-translator-missing.d.ts +3 -0
  95. package/dist/modules/agent-tagsend-translator-missing.d.ts.map +1 -0
  96. package/dist/modules/agent-tagsend-translator-missing.js +58 -0
  97. package/dist/modules/agent-tagsend-translator-missing.js.map +1 -0
  98. package/dist/modules/agent-warning-on-confirm.d.ts +3 -0
  99. package/dist/modules/agent-warning-on-confirm.d.ts.map +1 -0
  100. package/dist/modules/agent-warning-on-confirm.js +46 -0
  101. package/dist/modules/agent-warning-on-confirm.js.map +1 -0
  102. package/dist/modules/async-update.d.ts +3 -0
  103. package/dist/modules/async-update.d.ts.map +1 -0
  104. package/dist/modules/async-update.js +86 -0
  105. package/dist/modules/async-update.js.map +1 -0
  106. package/dist/modules/binding-descriptors.d.ts +4 -0
  107. package/dist/modules/binding-descriptors.d.ts.map +1 -0
  108. package/dist/modules/binding-descriptors.js +48 -0
  109. package/dist/modules/binding-descriptors.js.map +1 -0
  110. package/dist/modules/bitmask-overflow.d.ts +3 -0
  111. package/dist/modules/bitmask-overflow.d.ts.map +1 -0
  112. package/dist/modules/bitmask-overflow.js +152 -0
  113. package/dist/modules/bitmask-overflow.js.map +1 -0
  114. package/dist/modules/compiler-stamp.d.ts +3 -0
  115. package/dist/modules/compiler-stamp.d.ts.map +1 -0
  116. package/dist/modules/compiler-stamp.js +44 -0
  117. package/dist/modules/compiler-stamp.js.map +1 -0
  118. package/dist/modules/component-meta.d.ts +3 -0
  119. package/dist/modules/component-meta.d.ts.map +1 -0
  120. package/dist/modules/component-meta.js +44 -0
  121. package/dist/modules/component-meta.js.map +1 -0
  122. package/dist/modules/controlled-input.d.ts +3 -0
  123. package/dist/modules/controlled-input.d.ts.map +1 -0
  124. package/dist/modules/controlled-input.js +68 -0
  125. package/dist/modules/controlled-input.js.map +1 -0
  126. package/dist/modules/core-synthesis.d.ts +18 -0
  127. package/dist/modules/core-synthesis.d.ts.map +1 -0
  128. package/dist/modules/core-synthesis.js +748 -0
  129. package/dist/modules/core-synthesis.js.map +1 -0
  130. package/dist/modules/direct-state-in-view.d.ts +3 -0
  131. package/dist/modules/direct-state-in-view.d.ts.map +1 -0
  132. package/dist/modules/direct-state-in-view.js +103 -0
  133. package/dist/modules/direct-state-in-view.js.map +1 -0
  134. package/dist/modules/each-closure-violation.d.ts +3 -0
  135. package/dist/modules/each-closure-violation.d.ts.map +1 -0
  136. package/dist/modules/each-closure-violation.js +255 -0
  137. package/dist/modules/each-closure-violation.js.map +1 -0
  138. package/dist/modules/each-memo.d.ts +15 -0
  139. package/dist/modules/each-memo.d.ts.map +1 -0
  140. package/dist/modules/each-memo.js +115 -0
  141. package/dist/modules/each-memo.js.map +1 -0
  142. package/dist/modules/effect-without-handler.d.ts +3 -0
  143. package/dist/modules/effect-without-handler.d.ts.map +1 -0
  144. package/dist/modules/effect-without-handler.js +92 -0
  145. package/dist/modules/effect-without-handler.js.map +1 -0
  146. package/dist/modules/element-rewrite.d.ts +22 -0
  147. package/dist/modules/element-rewrite.d.ts.map +1 -0
  148. package/dist/modules/element-rewrite.js +1017 -0
  149. package/dist/modules/element-rewrite.js.map +1 -0
  150. package/dist/modules/empty-props.d.ts +3 -0
  151. package/dist/modules/empty-props.d.ts.map +1 -0
  152. package/dist/modules/empty-props.js +50 -0
  153. package/dist/modules/empty-props.js.map +1 -0
  154. package/dist/modules/exhaustive-effect-handling.d.ts +3 -0
  155. package/dist/modules/exhaustive-effect-handling.d.ts.map +1 -0
  156. package/dist/modules/exhaustive-effect-handling.js +61 -0
  157. package/dist/modules/exhaustive-effect-handling.js.map +1 -0
  158. package/dist/modules/exhaustive-update.d.ts +3 -0
  159. package/dist/modules/exhaustive-update.d.ts.map +1 -0
  160. package/dist/modules/exhaustive-update.js +146 -0
  161. package/dist/modules/exhaustive-update.js.map +1 -0
  162. package/dist/modules/forgotten-spread.d.ts +3 -0
  163. package/dist/modules/forgotten-spread.d.ts.map +1 -0
  164. package/dist/modules/forgotten-spread.js +51 -0
  165. package/dist/modules/forgotten-spread.js.map +1 -0
  166. package/dist/modules/form-boilerplate.d.ts +3 -0
  167. package/dist/modules/form-boilerplate.d.ts.map +1 -0
  168. package/dist/modules/form-boilerplate.js +101 -0
  169. package/dist/modules/form-boilerplate.js.map +1 -0
  170. package/dist/modules/imperative-dom-in-view.d.ts +3 -0
  171. package/dist/modules/imperative-dom-in-view.d.ts.map +1 -0
  172. package/dist/modules/imperative-dom-in-view.js +123 -0
  173. package/dist/modules/imperative-dom-in-view.js.map +1 -0
  174. package/dist/modules/item-dedup.d.ts +7 -0
  175. package/dist/modules/item-dedup.d.ts.map +1 -0
  176. package/dist/modules/item-dedup.js +204 -0
  177. package/dist/modules/item-dedup.js.map +1 -0
  178. package/dist/modules/map-on-state-array.d.ts +3 -0
  179. package/dist/modules/map-on-state-array.d.ts.map +1 -0
  180. package/dist/modules/map-on-state-array.js +84 -0
  181. package/dist/modules/map-on-state-array.js.map +1 -0
  182. package/dist/modules/mask-legend.d.ts +10 -0
  183. package/dist/modules/mask-legend.d.ts.map +1 -0
  184. package/dist/modules/mask-legend.js +50 -0
  185. package/dist/modules/mask-legend.js.map +1 -0
  186. package/dist/modules/missing-memo.d.ts +3 -0
  187. package/dist/modules/missing-memo.d.ts.map +1 -0
  188. package/dist/modules/missing-memo.js +114 -0
  189. package/dist/modules/missing-memo.js.map +1 -0
  190. package/dist/modules/msg-annotations.d.ts +9 -0
  191. package/dist/modules/msg-annotations.d.ts.map +1 -0
  192. package/dist/modules/msg-annotations.js +54 -0
  193. package/dist/modules/msg-annotations.js.map +1 -0
  194. package/dist/modules/msg-schema.d.ts +10 -0
  195. package/dist/modules/msg-schema.d.ts.map +1 -0
  196. package/dist/modules/msg-schema.js +70 -0
  197. package/dist/modules/msg-schema.js.map +1 -0
  198. package/dist/modules/namespace-import.d.ts +3 -0
  199. package/dist/modules/namespace-import.d.ts.map +1 -0
  200. package/dist/modules/namespace-import.js +80 -0
  201. package/dist/modules/namespace-import.js.map +1 -0
  202. package/dist/modules/nested-send-in-update.d.ts +3 -0
  203. package/dist/modules/nested-send-in-update.d.ts.map +1 -0
  204. package/dist/modules/nested-send-in-update.js +77 -0
  205. package/dist/modules/nested-send-in-update.js.map +1 -0
  206. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts +3 -0
  207. package/dist/modules/no-barrel-import-when-subpath-exists.d.ts.map +1 -0
  208. package/dist/modules/no-barrel-import-when-subpath-exists.js +100 -0
  209. package/dist/modules/no-barrel-import-when-subpath-exists.js.map +1 -0
  210. package/dist/modules/no-eager-item-accessor.d.ts +3 -0
  211. package/dist/modules/no-eager-item-accessor.d.ts.map +1 -0
  212. package/dist/modules/no-eager-item-accessor.js +74 -0
  213. package/dist/modules/no-eager-item-accessor.js.map +1 -0
  214. package/dist/modules/no-let-reactive-accessor.d.ts +3 -0
  215. package/dist/modules/no-let-reactive-accessor.d.ts.map +1 -0
  216. package/dist/modules/no-let-reactive-accessor.js +227 -0
  217. package/dist/modules/no-let-reactive-accessor.js.map +1 -0
  218. package/dist/modules/no-list-render-in-sample.d.ts +3 -0
  219. package/dist/modules/no-list-render-in-sample.d.ts.map +1 -0
  220. package/dist/modules/no-list-render-in-sample.js +89 -0
  221. package/dist/modules/no-list-render-in-sample.js.map +1 -0
  222. package/dist/modules/no-sample-in-accessor.d.ts +3 -0
  223. package/dist/modules/no-sample-in-accessor.d.ts.map +1 -0
  224. package/dist/modules/no-sample-in-accessor.js +141 -0
  225. package/dist/modules/no-sample-in-accessor.js.map +1 -0
  226. package/dist/modules/no-sample-in-reactive-position.d.ts +3 -0
  227. package/dist/modules/no-sample-in-reactive-position.d.ts.map +1 -0
  228. package/dist/modules/no-sample-in-reactive-position.js +72 -0
  229. package/dist/modules/no-sample-in-reactive-position.js.map +1 -0
  230. package/dist/modules/pure-update-function.d.ts +3 -0
  231. package/dist/modules/pure-update-function.d.ts.map +1 -0
  232. package/dist/modules/pure-update-function.js +127 -0
  233. package/dist/modules/pure-update-function.js.map +1 -0
  234. package/dist/modules/reactive-paths.d.ts +3 -0
  235. package/dist/modules/reactive-paths.d.ts.map +1 -0
  236. package/dist/modules/reactive-paths.js +77 -0
  237. package/dist/modules/reactive-paths.js.map +1 -0
  238. package/dist/modules/row-factory.d.ts +12 -0
  239. package/dist/modules/row-factory.d.ts.map +1 -0
  240. package/dist/modules/row-factory.js +385 -0
  241. package/dist/modules/row-factory.js.map +1 -0
  242. package/dist/modules/schema-hash.d.ts +15 -0
  243. package/dist/modules/schema-hash.d.ts.map +1 -0
  244. package/dist/modules/schema-hash.js +70 -0
  245. package/dist/modules/schema-hash.js.map +1 -0
  246. package/dist/modules/spread-in-children.d.ts +3 -0
  247. package/dist/modules/spread-in-children.d.ts.map +1 -0
  248. package/dist/modules/spread-in-children.js +144 -0
  249. package/dist/modules/spread-in-children.js.map +1 -0
  250. package/dist/modules/state-mutation.d.ts +3 -0
  251. package/dist/modules/state-mutation.d.ts.map +1 -0
  252. package/dist/modules/state-mutation.js +138 -0
  253. package/dist/modules/state-mutation.js.map +1 -0
  254. package/dist/modules/state-schema.d.ts +8 -0
  255. package/dist/modules/state-schema.d.ts.map +1 -0
  256. package/dist/modules/state-schema.js +55 -0
  257. package/dist/modules/state-schema.js.map +1 -0
  258. package/dist/modules/static-items.d.ts +3 -0
  259. package/dist/modules/static-items.d.ts.map +1 -0
  260. package/dist/modules/static-items.js +125 -0
  261. package/dist/modules/static-items.js.map +1 -0
  262. package/dist/modules/static-on.d.ts +3 -0
  263. package/dist/modules/static-on.d.ts.map +1 -0
  264. package/dist/modules/static-on.js +100 -0
  265. package/dist/modules/static-on.js.map +1 -0
  266. package/dist/modules/string-effect-callback.d.ts +3 -0
  267. package/dist/modules/string-effect-callback.d.ts.map +1 -0
  268. package/dist/modules/string-effect-callback.js +50 -0
  269. package/dist/modules/string-effect-callback.js.map +1 -0
  270. package/dist/modules/structural-mask.d.ts +8 -0
  271. package/dist/modules/structural-mask.d.ts.map +1 -0
  272. package/dist/modules/structural-mask.js +76 -0
  273. package/dist/modules/structural-mask.js.map +1 -0
  274. package/dist/modules/subapp-requires-reason.d.ts +3 -0
  275. package/dist/modules/subapp-requires-reason.d.ts.map +1 -0
  276. package/dist/modules/subapp-requires-reason.js +129 -0
  277. package/dist/modules/subapp-requires-reason.js.map +1 -0
  278. package/dist/modules/text-mask.d.ts +12 -0
  279. package/dist/modules/text-mask.d.ts.map +1 -0
  280. package/dist/modules/text-mask.js +63 -0
  281. package/dist/modules/text-mask.js.map +1 -0
  282. package/dist/modules/view-bag-import.d.ts +3 -0
  283. package/dist/modules/view-bag-import.d.ts.map +1 -0
  284. package/dist/modules/view-bag-import.js +80 -0
  285. package/dist/modules/view-bag-import.js.map +1 -0
  286. package/dist/msg-annotations.d.ts +104 -0
  287. package/dist/msg-annotations.d.ts.map +1 -0
  288. package/dist/msg-annotations.js +242 -0
  289. package/dist/msg-annotations.js.map +1 -0
  290. package/dist/msg-schema.d.ts +130 -0
  291. package/dist/msg-schema.d.ts.map +1 -0
  292. package/dist/msg-schema.js +770 -0
  293. package/dist/msg-schema.js.map +1 -0
  294. package/dist/schema-hash.d.ts +16 -0
  295. package/dist/schema-hash.d.ts.map +1 -0
  296. package/dist/schema-hash.js +31 -0
  297. package/dist/schema-hash.js.map +1 -0
  298. package/dist/state-schema.d.ts +41 -0
  299. package/dist/state-schema.d.ts.map +1 -0
  300. package/dist/state-schema.js +156 -0
  301. package/dist/state-schema.js.map +1 -0
  302. package/dist/transform.d.ts +109 -0
  303. package/dist/transform.d.ts.map +1 -0
  304. package/dist/transform.js +1390 -0
  305. package/dist/transform.js.map +1 -0
  306. package/dist/version.d.ts +11 -0
  307. package/dist/version.d.ts.map +1 -0
  308. package/dist/version.js +11 -0
  309. package/dist/version.js.map +1 -0
  310. package/package.json +47 -0
@@ -0,0 +1,770 @@
1
+ import ts from 'typescript';
2
+ /** True when `f` is a rich descriptor (object with `type` key). */
3
+ export function isRichField(f) {
4
+ return typeof f === 'object' && f !== null && !Array.isArray(f) && 'type' in f;
5
+ }
6
+ /**
7
+ * Build a TS expression for a single field descriptor in a MsgSchema's
8
+ * variant map. Used by `msgSchemaToLiteral` (this file) for the
9
+ * `__msgSchema` / `__effectSchema` emissions. Migrated from inline
10
+ * `buildFieldDescriptorExpr` in transform.ts (v2c/decomp-5).
11
+ */
12
+ export function buildFieldDescriptorExpr(descriptor, f) {
13
+ if (typeof descriptor === 'string') {
14
+ return f.createStringLiteral(descriptor);
15
+ }
16
+ if (isRichField(descriptor)) {
17
+ const props = [];
18
+ props.push(f.createPropertyAssignment('type', buildFieldDescriptorExpr(descriptor.type, f)));
19
+ if (descriptor.optional) {
20
+ props.push(f.createPropertyAssignment('optional', f.createTrue()));
21
+ }
22
+ if (descriptor.priority) {
23
+ props.push(f.createPropertyAssignment('priority', f.createStringLiteral(descriptor.priority)));
24
+ }
25
+ if (descriptor.hint !== undefined) {
26
+ props.push(f.createPropertyAssignment('hint', f.createStringLiteral(descriptor.hint)));
27
+ }
28
+ if (descriptor.validates !== undefined) {
29
+ props.push(f.createPropertyAssignment('validates', f.createStringLiteral(descriptor.validates)));
30
+ }
31
+ return f.createObjectLiteralExpression(props);
32
+ }
33
+ if ('enum' in descriptor) {
34
+ return f.createObjectLiteralExpression([
35
+ f.createPropertyAssignment('enum', f.createArrayLiteralExpression(descriptor.enum.map((v) => emitEnumValue(v, f)))),
36
+ ]);
37
+ }
38
+ if ('kind' in descriptor && descriptor.kind === 'object') {
39
+ const shapeProps = [];
40
+ for (const [k, v] of Object.entries(descriptor.shape)) {
41
+ shapeProps.push(f.createPropertyAssignment(f.createStringLiteral(k), buildFieldDescriptorExpr(v, f)));
42
+ }
43
+ return f.createObjectLiteralExpression([
44
+ f.createPropertyAssignment('kind', f.createStringLiteral('object')),
45
+ f.createPropertyAssignment('shape', f.createObjectLiteralExpression(shapeProps)),
46
+ ]);
47
+ }
48
+ if ('kind' in descriptor && descriptor.kind === 'discriminated-union') {
49
+ const variantProps = [];
50
+ for (const [discValue, fields] of Object.entries(descriptor.variants)) {
51
+ const fieldProps = [];
52
+ for (const [k, v] of Object.entries(fields)) {
53
+ fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(k), buildFieldDescriptorExpr(v, f)));
54
+ }
55
+ variantProps.push(f.createPropertyAssignment(f.createStringLiteral(discValue), f.createObjectLiteralExpression(fieldProps, true)));
56
+ }
57
+ return f.createObjectLiteralExpression([
58
+ f.createPropertyAssignment('kind', f.createStringLiteral('discriminated-union')),
59
+ f.createPropertyAssignment('discriminant', f.createStringLiteral(descriptor.discriminant)),
60
+ f.createPropertyAssignment('variants', f.createObjectLiteralExpression(variantProps, true)),
61
+ ]);
62
+ }
63
+ return f.createObjectLiteralExpression([
64
+ f.createPropertyAssignment('kind', f.createStringLiteral('array')),
65
+ f.createPropertyAssignment('element', buildFieldDescriptorExpr(descriptor.element, f)),
66
+ ]);
67
+ }
68
+ function emitEnumValue(v, f) {
69
+ if (typeof v === 'string')
70
+ return f.createStringLiteral(v);
71
+ if (typeof v === 'number')
72
+ return f.createNumericLiteral(v);
73
+ return v ? f.createTrue() : f.createFalse();
74
+ }
75
+ /**
76
+ * Build the full `{ discriminant, variants }` object literal for a
77
+ * MsgSchema. Symmetric for `__msgSchema` and `__effectSchema` emission
78
+ * (both use the discriminated-union shape).
79
+ */
80
+ export function msgSchemaToLiteral(schema, f) {
81
+ const variantProps = [];
82
+ for (const [variant, fields] of Object.entries(schema.variants)) {
83
+ const fieldProps = [];
84
+ for (const [field, descriptor] of Object.entries(fields)) {
85
+ fieldProps.push(f.createPropertyAssignment(f.createStringLiteral(field), buildFieldDescriptorExpr(descriptor, f)));
86
+ }
87
+ variantProps.push(f.createPropertyAssignment(f.createStringLiteral(variant), f.createObjectLiteralExpression(fieldProps)));
88
+ }
89
+ return f.createObjectLiteralExpression([
90
+ f.createPropertyAssignment('discriminant', f.createStringLiteral(schema.discriminant)),
91
+ f.createPropertyAssignment('variants', f.createObjectLiteralExpression(variantProps, true)),
92
+ ], true);
93
+ }
94
+ /** Extracts the bare type from either descriptor form. */
95
+ export function fieldType(f) {
96
+ return isRichField(f) ? f.type : f;
97
+ }
98
+ export function extractMsgSchema(source, typeName = 'Msg') {
99
+ return extractDiscriminatedUnionSchema(source, typeName);
100
+ }
101
+ export function extractEffectSchema(source, typeName = 'Effect') {
102
+ return extractDiscriminatedUnionSchema(source, typeName);
103
+ }
104
+ function extractDiscriminatedUnionSchema(source, typeName) {
105
+ const sf = ts.createSourceFile('input.ts', source, ts.ScriptTarget.Latest, true);
106
+ const typeIndex = buildTypeIndex(sf);
107
+ for (const stmt of sf.statements) {
108
+ if (!ts.isTypeAliasDeclaration(stmt))
109
+ continue;
110
+ if (stmt.name.text !== typeName)
111
+ continue;
112
+ const variants = {};
113
+ collectVariants(stmt.type, variants, source, typeIndex);
114
+ if (Object.keys(variants).length === 0)
115
+ return null;
116
+ return { discriminant: 'type', variants };
117
+ }
118
+ return null;
119
+ }
120
+ function buildTypeIndex(sf) {
121
+ const index = new Map();
122
+ for (const stmt of sf.statements) {
123
+ if (ts.isTypeAliasDeclaration(stmt)) {
124
+ index.set(stmt.name.text, stmt.type);
125
+ }
126
+ else if (ts.isInterfaceDeclaration(stmt)) {
127
+ index.set(stmt.name.text, stmt);
128
+ }
129
+ }
130
+ return index;
131
+ }
132
+ function collectVariants(type, variants, source, typeIndex) {
133
+ if (ts.isUnionTypeNode(type)) {
134
+ for (const member of type.types) {
135
+ collectVariants(member, variants, source, typeIndex);
136
+ }
137
+ return;
138
+ }
139
+ if (ts.isTypeLiteralNode(type)) {
140
+ let discriminantValue = null;
141
+ const fields = {};
142
+ for (const member of type.members) {
143
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
144
+ continue;
145
+ const name = member.name.text;
146
+ const memberType = member.type;
147
+ if (name === 'type' && memberType) {
148
+ // Extract the discriminant value
149
+ if (ts.isLiteralTypeNode(memberType) && ts.isStringLiteral(memberType.literal)) {
150
+ discriminantValue = memberType.literal.text;
151
+ }
152
+ continue;
153
+ }
154
+ fields[name] = buildFieldDescriptor(member, source, typeIndex);
155
+ }
156
+ if (discriminantValue) {
157
+ variants[discriminantValue] = fields;
158
+ }
159
+ }
160
+ }
161
+ /**
162
+ * Build a single field descriptor from a property signature: type,
163
+ * optionality, and any `@should("…")` JSDoc hint. Emits the compact
164
+ * bare form when there's nothing extra to communicate; otherwise the
165
+ * rich `{type, optional?, priority?, hint?}` shape.
166
+ *
167
+ * Exported so the cross-file resolver (which walks the same property
168
+ * signatures when the Msg type lives in a different file from the
169
+ * `component()` call) can produce identical descriptors. Without
170
+ * sharing this helper, JSDoc hints would silently disappear whenever
171
+ * a Msg union got resolved across module boundaries.
172
+ */
173
+ export function buildFieldDescriptor(member, source, typeIndex = new Map()) {
174
+ const peeled = member.type ? peelOptionalUnion(member.type) : null;
175
+ const innerType = peeled?.type ?? member.type;
176
+ const baseType = innerType
177
+ ? resolveFieldType(innerType, typeIndex, MAX_FIELD_DEPTH)
178
+ : 'unknown';
179
+ const optional = member.questionToken !== undefined || peeled?.isImplicitOptional === true;
180
+ const jsdoc = readMemberJSDoc(source, member);
181
+ const hint = readShouldHint(jsdoc);
182
+ const validates = readValidatesTag(jsdoc);
183
+ if (!optional && hint === null && validates === null) {
184
+ return baseType;
185
+ }
186
+ const rich = { type: baseType };
187
+ if (optional)
188
+ rich.optional = true;
189
+ if (hint !== null) {
190
+ rich.priority = 'should';
191
+ rich.hint = hint;
192
+ }
193
+ if (validates !== null) {
194
+ rich.validates = validates;
195
+ }
196
+ return rich;
197
+ }
198
+ /**
199
+ * Detect `T | undefined` (or `undefined | T`, or `T1 | T2 | undefined`)
200
+ * and return the union without the `undefined` branch plus a flag
201
+ * marking the field as implicitly optional. Mirrors the runtime
202
+ * semantics: `field: T | undefined` is exactly equivalent to
203
+ * `field?: T` — the agent should be able to omit the field entirely.
204
+ *
205
+ * Pre-strict-null codebases (decisive among them) declare optional
206
+ * fields as `field: T | undefined` rather than `field?: T`. Without
207
+ * this peel, the union doesn't match `tryExtractLiteralUnion` (one
208
+ * branch isn't a literal) or `tryExtractDiscriminatedUnion` (the
209
+ * `undefined` branch isn't an object literal), so the whole thing
210
+ * collapses to `'unknown'` and the agent has to spell out
211
+ * `field: undefined` literally on every payload.
212
+ *
213
+ * Returns the original node and `isImplicitOptional: false` when the
214
+ * union has no `undefined` branch — caller then resolves it via the
215
+ * normal pipeline. Returns the original node and `false` when ALL
216
+ * branches are `undefined` (pathological — let it fall through to
217
+ * unknown rather than fabricating a shape).
218
+ */
219
+ function peelOptionalUnion(type) {
220
+ if (!ts.isUnionTypeNode(type))
221
+ return { type, isImplicitOptional: false };
222
+ const isUndefined = (t) => t.kind === ts.SyntaxKind.UndefinedKeyword;
223
+ const nonUndefined = type.types.filter((t) => !isUndefined(t));
224
+ if (nonUndefined.length === type.types.length)
225
+ return { type, isImplicitOptional: false };
226
+ if (nonUndefined.length === 0)
227
+ return { type, isImplicitOptional: false };
228
+ if (nonUndefined.length === 1 && nonUndefined[0]) {
229
+ return { type: nonUndefined[0], isImplicitOptional: true };
230
+ }
231
+ // 'a' | 'b' | undefined → rebuild as 'a' | 'b' so it can run through
232
+ // tryExtractLiteralUnion / tryExtractDiscriminatedUnion as if the
233
+ // undefined branch had never been there.
234
+ return {
235
+ type: ts.factory.createUnionTypeNode(nonUndefined),
236
+ isImplicitOptional: true,
237
+ };
238
+ }
239
+ /**
240
+ * Recursion bound for nested type resolution. Stops the extractor
241
+ * before it spirals on self-referential or mutually-recursive types
242
+ * (`type Tree = { children: Tree[] }`).
243
+ *
244
+ * Only NAMED-TYPE LOOKUPS decrement this budget — chasing
245
+ * `type Foo = …` through the typeIndex, or expanding an
246
+ * `interface X` reference. Inline structural traversal (array
247
+ * elements, inline object literals, inline discriminated unions) is
248
+ * free, since cycles can only re-enter resolution via a named
249
+ * reference. This means a deeply-nested but finite type tree like
250
+ * `Matrix/AddCriteria.criteria[].type(quantity).clamp` (5+ inline
251
+ * hops, 3 named-type hops: Criterion → CriterionType → Clamp) fully
252
+ * resolves rather than collapsing to `'unknown'` half-way through.
253
+ *
254
+ * 5 named-type hops is plenty for production Msg payloads. Cyclic
255
+ * named types still terminate via the decrement-and-bail rule.
256
+ */
257
+ const MAX_FIELD_DEPTH = 5;
258
+ /**
259
+ * Detect literal-only unions whose members all share one primitive
260
+ * type — `'a' | 'b' | 'c'`, `1 | 2 | 3`, or `true | false`. Returns
261
+ * the enum descriptor on success; null if any member isn't a literal
262
+ * of the same type as the others.
263
+ *
264
+ * Mixed-type unions (`'a' | 1`) and unions that include non-literal
265
+ * members fall through. The agent gets `'unknown'` for those rather
266
+ * than an enum that loses the type information mid-list.
267
+ */
268
+ function tryExtractLiteralUnion(union) {
269
+ const values = [];
270
+ let kind = null;
271
+ for (const member of union.types) {
272
+ if (!ts.isLiteralTypeNode(member))
273
+ return null;
274
+ const lit = member.literal;
275
+ if (ts.isStringLiteral(lit)) {
276
+ if (kind === null)
277
+ kind = 'string';
278
+ else if (kind !== 'string')
279
+ return null;
280
+ values.push(lit.text);
281
+ }
282
+ else if (ts.isNumericLiteral(lit)) {
283
+ if (kind === null)
284
+ kind = 'number';
285
+ else if (kind !== 'number')
286
+ return null;
287
+ const n = Number(lit.text);
288
+ if (!Number.isFinite(n))
289
+ return null;
290
+ values.push(n);
291
+ }
292
+ else if (lit.kind === ts.SyntaxKind.TrueKeyword) {
293
+ if (kind === null)
294
+ kind = 'boolean';
295
+ else if (kind !== 'boolean')
296
+ return null;
297
+ values.push(true);
298
+ }
299
+ else if (lit.kind === ts.SyntaxKind.FalseKeyword) {
300
+ if (kind === null)
301
+ kind = 'boolean';
302
+ else if (kind !== 'boolean')
303
+ return null;
304
+ values.push(false);
305
+ }
306
+ else {
307
+ return null;
308
+ }
309
+ }
310
+ if (values.length === 0)
311
+ return null;
312
+ return { enum: values };
313
+ }
314
+ /**
315
+ * Detect a discriminated union of object types — every member is an
316
+ * object literal (or named type alias resolving to one) and every
317
+ * member declares the same property as a string-literal type with a
318
+ * value distinct from every other member's. Examples:
319
+ *
320
+ * {kind:'a'} | {kind:'b', x:number} → discriminant 'kind'
321
+ * {tag:'x',v:1} | {tag:'y',v:'s'} → discriminant 'tag'
322
+ *
323
+ * Returns the union descriptor on success; null on any failure
324
+ * (different shape per branch, no shared discriminant key, non-literal
325
+ * discriminant value, primitive member, etc.). Bailing to null lets
326
+ * the caller emit `'unknown'` rather than a partially-valid descriptor.
327
+ *
328
+ * `depth` is the budget for resolving each branch's payload. The
329
+ * caller subtracts one before calling, since detecting the union
330
+ * itself doesn't consume budget — recursing into branches does.
331
+ */
332
+ function tryExtractDiscriminatedUnion(union, typeIndex, depth) {
333
+ // Resolve each branch to its underlying object literal node, chasing
334
+ // through type-alias references in the local index. Returns null if
335
+ // any branch isn't an object-literal-shaped type.
336
+ const branches = [];
337
+ for (const member of union.types) {
338
+ const lit = resolveToTypeLiteral(member, typeIndex);
339
+ if (lit === null)
340
+ return null;
341
+ branches.push(lit);
342
+ }
343
+ if (branches.length === 0)
344
+ return null;
345
+ // Find a property name that EVERY branch declares with a string-
346
+ // literal value, and where the values are pairwise distinct.
347
+ // Iterate over the first branch's properties; for each candidate
348
+ // name, check the rest.
349
+ const first = branches[0];
350
+ if (!first)
351
+ return null;
352
+ let discriminant = null;
353
+ let firstBranchValue = null;
354
+ for (const member of first.members) {
355
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
356
+ continue;
357
+ if (!member.type)
358
+ continue;
359
+ if (!ts.isLiteralTypeNode(member.type) || !ts.isStringLiteral(member.type.literal))
360
+ continue;
361
+ const candidate = member.name.text;
362
+ const valuesByBranch = [member.type.literal.text];
363
+ let ok = true;
364
+ for (let i = 1; i < branches.length; i++) {
365
+ const branch = branches[i];
366
+ if (!branch) {
367
+ ok = false;
368
+ break;
369
+ }
370
+ const otherValue = literalDiscriminantValue(branch, candidate);
371
+ if (otherValue === null) {
372
+ ok = false;
373
+ break;
374
+ }
375
+ valuesByBranch.push(otherValue);
376
+ }
377
+ if (!ok)
378
+ continue;
379
+ // All distinct?
380
+ const uniq = new Set(valuesByBranch);
381
+ if (uniq.size !== valuesByBranch.length)
382
+ continue;
383
+ discriminant = candidate;
384
+ firstBranchValue = member.type.literal.text;
385
+ break;
386
+ }
387
+ if (discriminant === null || firstBranchValue === null)
388
+ return null;
389
+ // Build the variant payload map. Each variant's payload is the
390
+ // branch's properties EXCEPT the discriminant itself (which the
391
+ // synthesizer re-adds at example time, like the top-level Msg `type`).
392
+ const variants = {};
393
+ for (const branch of branches) {
394
+ const value = literalDiscriminantValue(branch, discriminant);
395
+ if (value === null)
396
+ return null;
397
+ const fields = {};
398
+ for (const member of branch.members) {
399
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
400
+ continue;
401
+ const name = member.name.text;
402
+ if (name === discriminant)
403
+ continue;
404
+ fields[name] = resolveMember(member, typeIndex, depth);
405
+ }
406
+ variants[value] = fields;
407
+ }
408
+ return { kind: 'discriminated-union', discriminant, variants };
409
+ }
410
+ /**
411
+ * Resolve a type node down to an inline object-literal type node,
412
+ * following one level of named-reference indirection through the local
413
+ * index. Returns null when the type isn't (or can't be reduced to) an
414
+ * object literal. We only chase one hop because every additional hop
415
+ * needs a depth budget to terminate, and discriminated-union detection
416
+ * is bounded by the outer caller's budget already.
417
+ */
418
+ function resolveToTypeLiteral(t, typeIndex) {
419
+ if (ts.isTypeLiteralNode(t))
420
+ return t;
421
+ if (ts.isTypeReferenceNode(t) && ts.isIdentifier(t.typeName)) {
422
+ const target = typeIndex.get(t.typeName.text);
423
+ if (!target)
424
+ return null;
425
+ if (ts.isInterfaceDeclaration(target)) {
426
+ // Synthesize a TypeLiteralNode-like shape from the interface
427
+ // members. Cheaper than reconstructing the AST: we only need
428
+ // the members to drive collectInlineShape semantics, but the
429
+ // discriminated-union detector reads property signatures, which
430
+ // interfaces have directly. We shim via a property-list view.
431
+ return interfaceToTypeLiteralLike(target);
432
+ }
433
+ if (ts.isTypeNode(target)) {
434
+ // Type alias: recurse one level.
435
+ return resolveToTypeLiteral(target, typeIndex);
436
+ }
437
+ }
438
+ return null;
439
+ }
440
+ /**
441
+ * Adapter for interface declarations: discriminated-union detection
442
+ * and field iteration only need the members list, which both
443
+ * `TypeLiteralNode` and `InterfaceDeclaration` expose. We return the
444
+ * interface cast as a TypeLiteralNode-shaped object so the rest of
445
+ * this file's helpers (which check `ts.isPropertySignature(member)`)
446
+ * work uniformly across both node kinds.
447
+ */
448
+ function interfaceToTypeLiteralLike(iface) {
449
+ return { members: iface.members };
450
+ }
451
+ /**
452
+ * Detect a branded-primitive intersection — `string & {__brand: B}`,
453
+ * `number & {readonly __brand: 'UID'}`, etc. Returns the underlying
454
+ * primitive keyword on success; null when the intersection isn't this
455
+ * shape.
456
+ *
457
+ * Conventional encodings recognised:
458
+ * string & {__brand: T} // type-fest's basic brand
459
+ * string & { __brand: 'UID' } // hand-rolled
460
+ * number & { readonly __brand } // readonly form
461
+ *
462
+ * Branches:
463
+ * - Exactly one primitive-keyword member (StringKeyword, NumberKeyword,
464
+ * BooleanKeyword) — the runtime base type.
465
+ * - One or more TypeLiteralNode members whose every property is a
466
+ * `__brand`-prefixed marker. Other property names disqualify (the
467
+ * intersection is mixing in real fields, not a brand).
468
+ */
469
+ function tryExtractBrandedPrimitive(intersection) {
470
+ let primitive = null;
471
+ let sawBrandTag = false;
472
+ for (const member of intersection.types) {
473
+ if (member.kind === ts.SyntaxKind.StringKeyword) {
474
+ if (primitive !== null)
475
+ return null;
476
+ primitive = 'string';
477
+ continue;
478
+ }
479
+ if (member.kind === ts.SyntaxKind.NumberKeyword) {
480
+ if (primitive !== null)
481
+ return null;
482
+ primitive = 'number';
483
+ continue;
484
+ }
485
+ if (member.kind === ts.SyntaxKind.BooleanKeyword) {
486
+ if (primitive !== null)
487
+ return null;
488
+ primitive = 'boolean';
489
+ continue;
490
+ }
491
+ if (ts.isTypeLiteralNode(member)) {
492
+ // Every property in this literal must look like a brand marker
493
+ // (`__brand`, `__type`, etc. — anything starting with `__`). If
494
+ // a real-named field appears, this isn't a brand intersection.
495
+ let onlyBrandFields = true;
496
+ let hasField = false;
497
+ for (const prop of member.members) {
498
+ if (!ts.isPropertySignature(prop) || !prop.name)
499
+ continue;
500
+ hasField = true;
501
+ const propName = ts.isIdentifier(prop.name)
502
+ ? prop.name.text
503
+ : ts.isStringLiteral(prop.name)
504
+ ? prop.name.text
505
+ : '';
506
+ if (!propName.startsWith('__')) {
507
+ onlyBrandFields = false;
508
+ break;
509
+ }
510
+ }
511
+ if (!onlyBrandFields || !hasField)
512
+ return null;
513
+ sawBrandTag = true;
514
+ continue;
515
+ }
516
+ // Any other member shape (e.g. another intersection, generic
517
+ // reference, etc.) — bail to be conservative.
518
+ return null;
519
+ }
520
+ if (primitive === null || !sawBrandTag)
521
+ return null;
522
+ return primitive;
523
+ }
524
+ /**
525
+ * Read a string-literal property value from an object-literal-like
526
+ * member list, or null if the named property isn't present, isn't a
527
+ * property signature, or isn't typed as a string literal.
528
+ */
529
+ function literalDiscriminantValue(lit, name) {
530
+ for (const member of lit.members) {
531
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
532
+ continue;
533
+ if (member.name.text !== name)
534
+ continue;
535
+ if (!member.type)
536
+ return null;
537
+ if (ts.isLiteralTypeNode(member.type) && ts.isStringLiteral(member.type.literal)) {
538
+ return member.type.literal.text;
539
+ }
540
+ return null;
541
+ }
542
+ return null;
543
+ }
544
+ export function resolveFieldType(type, typeIndex = new Map(), depth = MAX_FIELD_DEPTH) {
545
+ // Primitive keywords
546
+ if (type.kind === ts.SyntaxKind.StringKeyword)
547
+ return 'string';
548
+ if (type.kind === ts.SyntaxKind.NumberKeyword)
549
+ return 'number';
550
+ if (type.kind === ts.SyntaxKind.BooleanKeyword)
551
+ return 'boolean';
552
+ // Standalone literal type — `flag: true` or `value: 5`. Single-value
553
+ // enum so the schema records the constant rather than collapsing it
554
+ // to 'unknown'. Useful for sentinel discriminants outside discriminated
555
+ // unions (e.g. `kind: 'always-this-one'` on a non-union type).
556
+ if (ts.isLiteralTypeNode(type)) {
557
+ const lit = type.literal;
558
+ if (ts.isStringLiteral(lit))
559
+ return { enum: [lit.text] };
560
+ if (ts.isNumericLiteral(lit)) {
561
+ const n = Number(lit.text);
562
+ return Number.isFinite(n) ? { enum: [n] } : 'unknown';
563
+ }
564
+ if (lit.kind === ts.SyntaxKind.TrueKeyword)
565
+ return { enum: [true] };
566
+ if (lit.kind === ts.SyntaxKind.FalseKeyword)
567
+ return { enum: [false] };
568
+ }
569
+ // Union of literals — 'a' | 'b' (strings), 1 | 2 | 3 (numbers), or
570
+ // true / false (booleans). Mixed-type unions ('a' | 1) bail to
571
+ // 'unknown' — the LLM can't reason about that shape from the schema
572
+ // alone, so we'd rather not emit a misleading enum than enumerate
573
+ // the values without their types.
574
+ if (ts.isUnionTypeNode(type)) {
575
+ const enumResult = tryExtractLiteralUnion(type);
576
+ if (enumResult !== null)
577
+ return enumResult;
578
+ // Discriminated union of object literals. Inline structural move
579
+ // — depth budget unchanged. Only named-type lookups decrement.
580
+ const discResult = tryExtractDiscriminatedUnion(type, typeIndex, depth);
581
+ if (discResult !== null)
582
+ return discResult;
583
+ }
584
+ // Branded primitive intersection — `string & {__brand: B}`,
585
+ // `number & {__brand: 'UID'}`, etc. The brand tag is a TS-only
586
+ // distinction that doesn't survive into runtime; from the
587
+ // validator's POV the value is just the underlying primitive. We
588
+ // unwrap to the primitive so the schema records the right runtime
589
+ // shape rather than collapsing to `'unknown'`.
590
+ //
591
+ // Heuristic: any IntersectionTypeNode where exactly one member is
592
+ // a primitive keyword and every other member is a TypeLiteralNode
593
+ // (the brand tag, conventionally `{__brand: …}` or
594
+ // `{readonly __brand: …}`). Real-world brands also use the form
595
+ // `Opaque<string, 'UID'>` from libraries like type-fest — those
596
+ // resolve via the typeIndex and are handled in the named-reference
597
+ // branch below.
598
+ if (ts.isIntersectionTypeNode(type)) {
599
+ const brandResult = tryExtractBrandedPrimitive(type);
600
+ if (brandResult !== null)
601
+ return brandResult;
602
+ }
603
+ // Inline object literal — `{a: number; b: string}` directly.
604
+ // Inline structural move; depth unchanged.
605
+ if (ts.isTypeLiteralNode(type)) {
606
+ return { kind: 'object', shape: collectInlineShape(type, typeIndex, depth) };
607
+ }
608
+ // Array type — `T[]` and `readonly T[]`. Inline structural move.
609
+ if (ts.isArrayTypeNode(type)) {
610
+ return { kind: 'array', element: resolveFieldType(type.elementType, typeIndex, depth) };
611
+ }
612
+ // Generic Array<T> (less common in app code but compiler may produce it).
613
+ if (ts.isTypeReferenceNode(type) &&
614
+ ts.isIdentifier(type.typeName) &&
615
+ type.typeName.text === 'Array' &&
616
+ type.typeArguments?.length === 1 &&
617
+ type.typeArguments[0]) {
618
+ return {
619
+ kind: 'array',
620
+ element: resolveFieldType(type.typeArguments[0], typeIndex, depth),
621
+ };
622
+ }
623
+ // ReadonlyArray<T> → same shape; the readonly modifier is purely a
624
+ // TypeScript-side concern that the agent never observes at runtime.
625
+ if (ts.isTypeReferenceNode(type) &&
626
+ ts.isIdentifier(type.typeName) &&
627
+ type.typeName.text === 'ReadonlyArray' &&
628
+ type.typeArguments?.length === 1 &&
629
+ type.typeArguments[0]) {
630
+ return {
631
+ kind: 'array',
632
+ element: resolveFieldType(type.typeArguments[0], typeIndex, depth),
633
+ };
634
+ }
635
+ // `readonly T[]` parses as TypeOperator(readonly) wrapping ArrayType.
636
+ if (ts.isTypeOperatorNode(type) && type.operator === ts.SyntaxKind.ReadonlyKeyword) {
637
+ return resolveFieldType(type.type, typeIndex, depth);
638
+ }
639
+ // Named type reference — chase it through the local index. This is
640
+ // the ONLY recursive step that consumes depth budget. Inline moves
641
+ // (array element, inline object/DU/union) traverse for free; the
642
+ // budget exists to break cycles in mutually-recursive named types
643
+ // (`Tree.children: Tree[]`), which can only re-enter resolution via
644
+ // a named lookup.
645
+ if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {
646
+ if (depth <= 0)
647
+ return 'unknown';
648
+ const target = typeIndex.get(type.typeName.text);
649
+ if (target) {
650
+ if (ts.isInterfaceDeclaration(target)) {
651
+ return {
652
+ kind: 'object',
653
+ shape: collectInterfaceShape(target, typeIndex, depth - 1),
654
+ };
655
+ }
656
+ // Type alias — recurse on its body. `type Foo = …` could resolve
657
+ // to anything (object literal, array, union, primitive); each
658
+ // already has its own branch above.
659
+ return resolveFieldType(target, typeIndex, depth - 1);
660
+ }
661
+ // Reference to a type the index doesn't know about — typically
662
+ // imported from another module. Cross-file resolution is the
663
+ // separate cross-file-resolver pipeline's job; leave this as
664
+ // unknown rather than fabricating a misleading shape.
665
+ return 'unknown';
666
+ }
667
+ return 'unknown';
668
+ }
669
+ /**
670
+ * Resolve a single property signature to a MsgField. Centralised
671
+ * here so every nested call site (interface members, inline-object
672
+ * members, DU variant fields) picks up the same `T | undefined` peel
673
+ * rules AND reads `@should` / `@validates` JSDoc. Before this helper
674
+ * existed, JSDoc was only honored on top-level Msg variant fields
675
+ * via `buildFieldDescriptor` — annotations on `interface
676
+ * Alternative.image` etc. were silently dropped, so domain types
677
+ * couldn't carry guidance for the agent.
678
+ *
679
+ * Source for the JSDoc reader is recovered from the member's own
680
+ * SourceFile, which means cross-file types (interfaces imported from
681
+ * another package) carry their JSDoc through transparently.
682
+ */
683
+ function resolveMember(member, typeIndex, depth) {
684
+ const peeled = member.type ? peelOptionalUnion(member.type) : null;
685
+ const innerType = peeled?.type ?? member.type;
686
+ const baseType = innerType
687
+ ? resolveFieldType(innerType, typeIndex, depth)
688
+ : 'unknown';
689
+ const optional = member.questionToken !== undefined || peeled?.isImplicitOptional === true;
690
+ const sourceText = member.getSourceFile().text;
691
+ const jsdoc = readMemberJSDoc(sourceText, member);
692
+ const hint = readShouldHint(jsdoc);
693
+ const validates = readValidatesTag(jsdoc);
694
+ if (!optional && hint === null && validates === null)
695
+ return baseType;
696
+ const rich = { type: baseType };
697
+ if (optional)
698
+ rich.optional = true;
699
+ if (hint !== null) {
700
+ rich.priority = 'should';
701
+ rich.hint = hint;
702
+ }
703
+ if (validates !== null)
704
+ rich.validates = validates;
705
+ return rich;
706
+ }
707
+ function collectInlineShape(lit, typeIndex, depth) {
708
+ const shape = {};
709
+ for (const member of lit.members) {
710
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
711
+ continue;
712
+ shape[member.name.text] = resolveMember(member, typeIndex, depth);
713
+ }
714
+ return shape;
715
+ }
716
+ function collectInterfaceShape(iface, typeIndex, depth) {
717
+ const shape = {};
718
+ for (const member of iface.members) {
719
+ if (!ts.isPropertySignature(member) || !member.name || !ts.isIdentifier(member.name))
720
+ continue;
721
+ shape[member.name.text] = resolveMember(member, typeIndex, depth);
722
+ }
723
+ return shape;
724
+ }
725
+ /**
726
+ * Read the leading JSDoc block immediately above `member`. The
727
+ * TypeScript parser doesn't attach JSDoc to interior property
728
+ * signatures, so we re-scan the source between the previous member's
729
+ * end (or the type-literal's `{`) and this member's start, and return
730
+ * the last `/** … *\/` block found there. Returns `''` when none.
731
+ */
732
+ function readMemberJSDoc(source, member) {
733
+ const ranges = ts.getLeadingCommentRanges(source, member.pos) ?? [];
734
+ // Walk in order, keeping only `/** */` blocks. Multiple back-to-back
735
+ // JSDocs concatenate (matches msg-annotations.ts's existing behavior).
736
+ const docs = ranges
737
+ .filter((r) => r.kind === ts.SyntaxKind.MultiLineCommentTrivia)
738
+ .map((r) => source.slice(r.pos, r.end))
739
+ .filter((txt) => txt.startsWith('/**'));
740
+ return docs.join('\n');
741
+ }
742
+ /**
743
+ * Match `@should("…")` (and curly-quote variant) anywhere in the
744
+ * JSDoc. Mirrors msg-annotations.ts's `@intent` parser — same grammar,
745
+ * same tolerance for either ASCII or curly quotes.
746
+ *
747
+ * Returns the unescaped string content, or null when the tag is
748
+ * absent or malformed.
749
+ */
750
+ function readShouldHint(comment) {
751
+ if (!comment)
752
+ return null;
753
+ const match = comment.match(/@should\s*\(\s*["“]([^"”]*)["”]\s*\)/);
754
+ return match?.[1] ?? null;
755
+ }
756
+ /**
757
+ * Match `@validates("predicate-expression")` (and curly-quote variant)
758
+ * anywhere in the JSDoc. Returns the verbatim predicate string —
759
+ * runtime validator compiles it with `new Function('v', 'return (' +
760
+ * src + ')')`. Quote characters inside the predicate must be escaped
761
+ * as the predicate runs through a regex match; for predicates that
762
+ * need embedded quotes, use a regex literal or a named character class.
763
+ */
764
+ function readValidatesTag(comment) {
765
+ if (!comment)
766
+ return null;
767
+ const match = comment.match(/@validates\s*\(\s*["“]([^"”]*)["”]\s*\)/);
768
+ return match?.[1] ?? null;
769
+ }
770
+ //# sourceMappingURL=msg-schema.js.map