@pattern-stack/codegen 0.15.1 → 0.15.2

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 (521) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/chunk-24CWKBK5.js +94 -0
  3. package/dist/chunk-24CWKBK5.js.map +1 -0
  4. package/dist/chunk-2E224ZSN.js +20 -0
  5. package/dist/chunk-2E224ZSN.js.map +1 -0
  6. package/dist/chunk-2FTZLDBP.js +179 -0
  7. package/dist/chunk-2FTZLDBP.js.map +1 -0
  8. package/dist/chunk-2N4UG4VD.js +20 -0
  9. package/dist/chunk-2N4UG4VD.js.map +1 -0
  10. package/dist/chunk-2TVVBC53.js +92 -0
  11. package/dist/chunk-2TVVBC53.js.map +1 -0
  12. package/dist/chunk-2VHZ7EKC.js +37 -0
  13. package/dist/chunk-2VHZ7EKC.js.map +1 -0
  14. package/dist/chunk-32BMMV4H.js +109 -0
  15. package/dist/chunk-32BMMV4H.js.map +1 -0
  16. package/dist/chunk-32DOFN3T.js +4042 -0
  17. package/dist/chunk-32DOFN3T.js.map +1 -0
  18. package/dist/chunk-36U5UGIO.js +107 -0
  19. package/dist/chunk-36U5UGIO.js.map +1 -0
  20. package/dist/chunk-3CJFPU6Q.js +14 -0
  21. package/dist/chunk-3CJFPU6Q.js.map +1 -0
  22. package/dist/chunk-3NMCDN7L.js +90 -0
  23. package/dist/chunk-3NMCDN7L.js.map +1 -0
  24. package/dist/chunk-3SZFUTXE.js +62 -0
  25. package/dist/chunk-3SZFUTXE.js.map +1 -0
  26. package/dist/chunk-4DOJBQTP.js +117 -0
  27. package/dist/chunk-4DOJBQTP.js.map +1 -0
  28. package/dist/chunk-4JLJYWJC.js +308 -0
  29. package/dist/chunk-4JLJYWJC.js.map +1 -0
  30. package/dist/chunk-4KNXX6TI.js +29 -0
  31. package/dist/chunk-4KNXX6TI.js.map +1 -0
  32. package/dist/chunk-4LH67P4U.js +17 -0
  33. package/dist/chunk-4LH67P4U.js.map +1 -0
  34. package/dist/chunk-4MVGAMUA.js +40 -0
  35. package/dist/chunk-4MVGAMUA.js.map +1 -0
  36. package/dist/chunk-4OMHBMZJ.js +75 -0
  37. package/dist/chunk-4OMHBMZJ.js.map +1 -0
  38. package/dist/chunk-4RFHUZXU.js +635 -0
  39. package/dist/chunk-4RFHUZXU.js.map +1 -0
  40. package/dist/chunk-5A432NZJ.js +7 -0
  41. package/dist/chunk-5A432NZJ.js.map +1 -0
  42. package/dist/chunk-5Y7W3XR6.js +356 -0
  43. package/dist/chunk-5Y7W3XR6.js.map +1 -0
  44. package/dist/chunk-6DWFJNIK.js +15 -0
  45. package/dist/chunk-6DWFJNIK.js.map +1 -0
  46. package/dist/chunk-6I7ULIN6.js +15 -0
  47. package/dist/chunk-6I7ULIN6.js.map +1 -0
  48. package/dist/chunk-6XY6ZMMD.js +25 -0
  49. package/dist/chunk-6XY6ZMMD.js.map +1 -0
  50. package/dist/chunk-7B3RYX45.js +63 -0
  51. package/dist/chunk-7B3RYX45.js.map +1 -0
  52. package/dist/chunk-7C3FOSDI.js +1 -0
  53. package/dist/chunk-7C3FOSDI.js.map +1 -0
  54. package/dist/chunk-7KOW6PU6.js +59 -0
  55. package/dist/chunk-7KOW6PU6.js.map +1 -0
  56. package/dist/chunk-7LKAMLV4.js +92 -0
  57. package/dist/chunk-7LKAMLV4.js.map +1 -0
  58. package/dist/chunk-7RELQJIN.js +22 -0
  59. package/dist/chunk-7RELQJIN.js.map +1 -0
  60. package/dist/chunk-AHV4GDYM.js +63 -0
  61. package/dist/chunk-AHV4GDYM.js.map +1 -0
  62. package/dist/chunk-AQFQ4BYM.js +81 -0
  63. package/dist/chunk-AQFQ4BYM.js.map +1 -0
  64. package/dist/chunk-AS3NAZB6.js +14 -0
  65. package/dist/chunk-AS3NAZB6.js.map +1 -0
  66. package/dist/chunk-BGULBWKJ.js +88 -0
  67. package/dist/chunk-BGULBWKJ.js.map +1 -0
  68. package/dist/chunk-BIO6F7YI.js +17 -0
  69. package/dist/chunk-BIO6F7YI.js.map +1 -0
  70. package/dist/chunk-BOPZWRJK.js +36 -0
  71. package/dist/chunk-BOPZWRJK.js.map +1 -0
  72. package/dist/chunk-BPARRK6F.js +14 -0
  73. package/dist/chunk-BPARRK6F.js.map +1 -0
  74. package/dist/chunk-CO6LUM72.js +59 -0
  75. package/dist/chunk-CO6LUM72.js.map +1 -0
  76. package/dist/chunk-COGHTKXY.js +84 -0
  77. package/dist/chunk-COGHTKXY.js.map +1 -0
  78. package/dist/chunk-DCCZB4UC.js +100 -0
  79. package/dist/chunk-DCCZB4UC.js.map +1 -0
  80. package/dist/chunk-DKKFTHHI.js +53 -0
  81. package/dist/chunk-DKKFTHHI.js.map +1 -0
  82. package/dist/chunk-DV4RV2DC.js +59 -0
  83. package/dist/chunk-DV4RV2DC.js.map +1 -0
  84. package/dist/chunk-EDKJU5BO.js +11 -0
  85. package/dist/chunk-EDKJU5BO.js.map +1 -0
  86. package/dist/chunk-EO2QPOKH.js +116 -0
  87. package/dist/chunk-EO2QPOKH.js.map +1 -0
  88. package/dist/chunk-EOLLMEAH.js +155 -0
  89. package/dist/chunk-EOLLMEAH.js.map +1 -0
  90. package/dist/chunk-EWYCWP4H.js +14 -0
  91. package/dist/chunk-EWYCWP4H.js.map +1 -0
  92. package/dist/chunk-EXVDJMIY.js +33 -0
  93. package/dist/chunk-EXVDJMIY.js.map +1 -0
  94. package/dist/chunk-FASRXRX5.js +19 -0
  95. package/dist/chunk-FASRXRX5.js.map +1 -0
  96. package/dist/chunk-FI34KYZ5.js +1 -0
  97. package/dist/chunk-FI34KYZ5.js.map +1 -0
  98. package/dist/chunk-FN2PYDPP.js +1 -0
  99. package/dist/chunk-FN2PYDPP.js.map +1 -0
  100. package/dist/chunk-GM3RMJIJ.js +92 -0
  101. package/dist/chunk-GM3RMJIJ.js.map +1 -0
  102. package/dist/chunk-GYGNEQSC.js +9 -0
  103. package/dist/chunk-GYGNEQSC.js.map +1 -0
  104. package/dist/chunk-H5NH7KPE.js +21 -0
  105. package/dist/chunk-H5NH7KPE.js.map +1 -0
  106. package/dist/chunk-HNWZFNKP.js +168 -0
  107. package/dist/chunk-HNWZFNKP.js.map +1 -0
  108. package/dist/chunk-HUH73XGI.js +1 -0
  109. package/dist/chunk-HUH73XGI.js.map +1 -0
  110. package/dist/chunk-I6MG4M3F.js +201 -0
  111. package/dist/chunk-I6MG4M3F.js.map +1 -0
  112. package/dist/chunk-I6MVCB5A.js +39 -0
  113. package/dist/chunk-I6MVCB5A.js.map +1 -0
  114. package/dist/chunk-IBGER4YK.js +12 -0
  115. package/dist/chunk-IBGER4YK.js.map +1 -0
  116. package/dist/chunk-IF5I3DAA.js +92 -0
  117. package/dist/chunk-IF5I3DAA.js.map +1 -0
  118. package/dist/chunk-IP4OO26U.js +54 -0
  119. package/dist/chunk-IP4OO26U.js.map +1 -0
  120. package/dist/chunk-IWAOY6KC.js +1 -0
  121. package/dist/chunk-IWAOY6KC.js.map +1 -0
  122. package/dist/chunk-J37YWU7Y.js +19 -0
  123. package/dist/chunk-J37YWU7Y.js.map +1 -0
  124. package/dist/chunk-J6KZS54B.js +269 -0
  125. package/dist/chunk-J6KZS54B.js.map +1 -0
  126. package/dist/chunk-J6MN42LG.js +19 -0
  127. package/dist/chunk-J6MN42LG.js.map +1 -0
  128. package/dist/chunk-JRQO2IOF.js +65 -0
  129. package/dist/chunk-JRQO2IOF.js.map +1 -0
  130. package/dist/chunk-JRVNVKN6.js +212 -0
  131. package/dist/chunk-JRVNVKN6.js.map +1 -0
  132. package/dist/chunk-JWNHNUYL.js +96 -0
  133. package/dist/chunk-JWNHNUYL.js.map +1 -0
  134. package/dist/chunk-K2I6XIK5.js +122 -0
  135. package/dist/chunk-K2I6XIK5.js.map +1 -0
  136. package/dist/chunk-KMZCQASO.js +111 -0
  137. package/dist/chunk-KMZCQASO.js.map +1 -0
  138. package/dist/chunk-KVOWSC5S.js +1 -0
  139. package/dist/chunk-KVOWSC5S.js.map +1 -0
  140. package/dist/chunk-KYR3B3OW.js +79 -0
  141. package/dist/chunk-KYR3B3OW.js.map +1 -0
  142. package/dist/chunk-L3LZWWSX.js +61 -0
  143. package/dist/chunk-L3LZWWSX.js.map +1 -0
  144. package/dist/chunk-L4SDDEEU.js +1 -0
  145. package/dist/chunk-L4SDDEEU.js.map +1 -0
  146. package/dist/chunk-L6FTY45T.js +13 -0
  147. package/dist/chunk-L6FTY45T.js.map +1 -0
  148. package/dist/chunk-L7BNNRGI.js +134 -0
  149. package/dist/chunk-L7BNNRGI.js.map +1 -0
  150. package/dist/chunk-LG57S2SC.js +150 -0
  151. package/dist/chunk-LG57S2SC.js.map +1 -0
  152. package/dist/chunk-M6QLSLPO.js +97 -0
  153. package/dist/chunk-M6QLSLPO.js.map +1 -0
  154. package/dist/chunk-MZ6GV4YF.js +21 -0
  155. package/dist/chunk-MZ6GV4YF.js.map +1 -0
  156. package/dist/chunk-N5OTOWTP.js +55 -0
  157. package/dist/chunk-N5OTOWTP.js.map +1 -0
  158. package/dist/chunk-NN7XZEGF.js +14 -0
  159. package/dist/chunk-NN7XZEGF.js.map +1 -0
  160. package/dist/chunk-NPFPZ2HO.js +13 -0
  161. package/dist/chunk-NPFPZ2HO.js.map +1 -0
  162. package/dist/chunk-NXXDZ6ZF.js +42 -0
  163. package/dist/chunk-NXXDZ6ZF.js.map +1 -0
  164. package/dist/chunk-NYBCQZC7.js +11 -0
  165. package/dist/chunk-NYBCQZC7.js.map +1 -0
  166. package/dist/chunk-OFRRBC7M.js +78 -0
  167. package/dist/chunk-OFRRBC7M.js.map +1 -0
  168. package/dist/chunk-OGIZXGPY.js +222 -0
  169. package/dist/chunk-OGIZXGPY.js.map +1 -0
  170. package/dist/chunk-OKXZ63IA.js +168 -0
  171. package/dist/chunk-OKXZ63IA.js.map +1 -0
  172. package/dist/chunk-OSQRXVG2.js +58 -0
  173. package/dist/chunk-OSQRXVG2.js.map +1 -0
  174. package/dist/chunk-OTDN3OUQ.js +215 -0
  175. package/dist/chunk-OTDN3OUQ.js.map +1 -0
  176. package/dist/chunk-OZZJDRGW.js +122 -0
  177. package/dist/chunk-OZZJDRGW.js.map +1 -0
  178. package/dist/chunk-PNZSGAB2.js +114 -0
  179. package/dist/chunk-PNZSGAB2.js.map +1 -0
  180. package/dist/chunk-PRWIX6UW.js +21 -0
  181. package/dist/chunk-PRWIX6UW.js.map +1 -0
  182. package/dist/chunk-PSXUNOVU.js +7 -0
  183. package/dist/chunk-PSXUNOVU.js.map +1 -0
  184. package/dist/chunk-QLTJSCE6.js +44 -0
  185. package/dist/chunk-QLTJSCE6.js.map +1 -0
  186. package/dist/chunk-RC23QROE.js +447 -0
  187. package/dist/chunk-RC23QROE.js.map +1 -0
  188. package/dist/chunk-RFH7N6EP.js +36 -0
  189. package/dist/chunk-RFH7N6EP.js.map +1 -0
  190. package/dist/chunk-RHVN6NA7.js +134 -0
  191. package/dist/chunk-RHVN6NA7.js.map +1 -0
  192. package/dist/chunk-S7C6TIIF.js +21 -0
  193. package/dist/chunk-S7C6TIIF.js.map +1 -0
  194. package/dist/chunk-SNQ3TOWP.js +20 -0
  195. package/dist/chunk-SNQ3TOWP.js.map +1 -0
  196. package/dist/chunk-SOVM2VEK.js +14 -0
  197. package/dist/chunk-SOVM2VEK.js.map +1 -0
  198. package/dist/chunk-SQDOBLBP.js +13 -0
  199. package/dist/chunk-SQDOBLBP.js.map +1 -0
  200. package/dist/chunk-SR7F3TJY.js +130 -0
  201. package/dist/chunk-SR7F3TJY.js.map +1 -0
  202. package/dist/chunk-SZVPIHWE.js +129 -0
  203. package/dist/chunk-SZVPIHWE.js.map +1 -0
  204. package/dist/chunk-T4BIIU5E.js +89 -0
  205. package/dist/chunk-T4BIIU5E.js.map +1 -0
  206. package/dist/chunk-T6C4LFLC.js +112 -0
  207. package/dist/chunk-T6C4LFLC.js.map +1 -0
  208. package/dist/chunk-TNXH7BJS.js +48 -0
  209. package/dist/chunk-TNXH7BJS.js.map +1 -0
  210. package/dist/chunk-U64T4YZE.js +9 -0
  211. package/dist/chunk-U64T4YZE.js.map +1 -0
  212. package/dist/chunk-UQ5EHOH2.js +39 -0
  213. package/dist/chunk-UQ5EHOH2.js.map +1 -0
  214. package/dist/chunk-UTN4GBPQ.js +1 -0
  215. package/dist/chunk-UTN4GBPQ.js.map +1 -0
  216. package/dist/chunk-V4AF6DI4.js +16 -0
  217. package/dist/chunk-V4AF6DI4.js.map +1 -0
  218. package/dist/chunk-W72PRNJY.js +126 -0
  219. package/dist/chunk-W72PRNJY.js.map +1 -0
  220. package/dist/chunk-WEVWJKOW.js +81 -0
  221. package/dist/chunk-WEVWJKOW.js.map +1 -0
  222. package/dist/chunk-WL67FZGF.js +21 -0
  223. package/dist/chunk-WL67FZGF.js.map +1 -0
  224. package/dist/chunk-WPXNN6QS.js +290 -0
  225. package/dist/chunk-WPXNN6QS.js.map +1 -0
  226. package/dist/chunk-WRUUSZDJ.js +29 -0
  227. package/dist/chunk-WRUUSZDJ.js.map +1 -0
  228. package/dist/chunk-X2GMTYPA.js +50 -0
  229. package/dist/chunk-X2GMTYPA.js.map +1 -0
  230. package/dist/chunk-XCEI7NUH.js +41 -0
  231. package/dist/chunk-XCEI7NUH.js.map +1 -0
  232. package/dist/chunk-Y7GDG744.js +88 -0
  233. package/dist/chunk-Y7GDG744.js.map +1 -0
  234. package/dist/chunk-Y7RRSEOC.js +9 -0
  235. package/dist/chunk-Y7RRSEOC.js.map +1 -0
  236. package/dist/chunk-YPWODKD5.js +184 -0
  237. package/dist/chunk-YPWODKD5.js.map +1 -0
  238. package/dist/chunk-YSLTTQLC.js +25 -0
  239. package/dist/chunk-YSLTTQLC.js.map +1 -0
  240. package/dist/chunk-YTN6BKWA.js +121 -0
  241. package/dist/chunk-YTN6BKWA.js.map +1 -0
  242. package/dist/chunk-Z7PQCAVK.js +200 -0
  243. package/dist/chunk-Z7PQCAVK.js.map +1 -0
  244. package/dist/chunk-ZUKFQL6E.js +47 -0
  245. package/dist/chunk-ZUKFQL6E.js.map +1 -0
  246. package/dist/chunk-ZUMULSEQ.js +1 -0
  247. package/dist/chunk-ZUMULSEQ.js.map +1 -0
  248. package/dist/runtime/analytics/index.js +8 -41
  249. package/dist/runtime/analytics/index.js.map +1 -1
  250. package/dist/runtime/analytics/types.js +8 -41
  251. package/dist/runtime/analytics/types.js.map +1 -1
  252. package/dist/runtime/base-classes/activity-entity-repository.js +6 -312
  253. package/dist/runtime/base-classes/activity-entity-repository.js.map +1 -1
  254. package/dist/runtime/base-classes/activity-entity-service.js +6 -212
  255. package/dist/runtime/base-classes/activity-entity-service.js.map +1 -1
  256. package/dist/runtime/base-classes/base-read-use-cases.js +5 -27
  257. package/dist/runtime/base-classes/base-read-use-cases.js.map +1 -1
  258. package/dist/runtime/base-classes/base-repository.js +5 -277
  259. package/dist/runtime/base-classes/base-repository.js.map +1 -1
  260. package/dist/runtime/base-classes/base-service.js +5 -184
  261. package/dist/runtime/base-classes/base-service.js.map +1 -1
  262. package/dist/runtime/base-classes/index.js +59 -1076
  263. package/dist/runtime/base-classes/index.js.map +1 -1
  264. package/dist/runtime/base-classes/integrated-entity-repository.js +6 -486
  265. package/dist/runtime/base-classes/integrated-entity-repository.js.map +1 -1
  266. package/dist/runtime/base-classes/integrated-entity-service.js +6 -213
  267. package/dist/runtime/base-classes/integrated-entity-service.js.map +1 -1
  268. package/dist/runtime/base-classes/junction-integration-repository.js +8 -448
  269. package/dist/runtime/base-classes/junction-integration-repository.js.map +1 -1
  270. package/dist/runtime/base-classes/knowledge-entity-repository.js +6 -283
  271. package/dist/runtime/base-classes/knowledge-entity-repository.js.map +1 -1
  272. package/dist/runtime/base-classes/knowledge-entity-service.js +6 -190
  273. package/dist/runtime/base-classes/knowledge-entity-service.js.map +1 -1
  274. package/dist/runtime/base-classes/lifecycle-events.js +8 -70
  275. package/dist/runtime/base-classes/lifecycle-events.js.map +1 -1
  276. package/dist/runtime/base-classes/metadata-entity-repository.js +6 -330
  277. package/dist/runtime/base-classes/metadata-entity-repository.js.map +1 -1
  278. package/dist/runtime/base-classes/metadata-entity-service.js +6 -212
  279. package/dist/runtime/base-classes/metadata-entity-service.js.map +1 -1
  280. package/dist/runtime/base-classes/tenant-context.js +10 -36
  281. package/dist/runtime/base-classes/tenant-context.js.map +1 -1
  282. package/dist/runtime/base-classes/with-analytics.js +4 -7
  283. package/dist/runtime/base-classes/with-analytics.js.map +1 -1
  284. package/dist/runtime/constants/tokens.js +5 -3
  285. package/dist/runtime/constants/tokens.js.map +1 -1
  286. package/dist/runtime/eav-helpers.js +2 -0
  287. package/dist/runtime/eav-helpers.js.map +1 -1
  288. package/dist/runtime/pipes/zod-validation.pipe.js +3 -10
  289. package/dist/runtime/pipes/zod-validation.pipe.js.map +1 -1
  290. package/dist/runtime/shared/openapi/error-response.dto.js +5 -8
  291. package/dist/runtime/shared/openapi/error-response.dto.js.map +1 -1
  292. package/dist/runtime/shared/openapi/errors.js +5 -19
  293. package/dist/runtime/shared/openapi/errors.js.map +1 -1
  294. package/dist/runtime/shared/openapi/index.js +15 -106
  295. package/dist/runtime/shared/openapi/index.js.map +1 -1
  296. package/dist/runtime/shared/openapi/registry.js +6 -103
  297. package/dist/runtime/shared/openapi/registry.js.map +1 -1
  298. package/dist/runtime/shared/openapi/registry.tokens.js +4 -2
  299. package/dist/runtime/shared/openapi/registry.tokens.js.map +1 -1
  300. package/dist/runtime/subsystems/analytics/analytics.module.js +8 -117
  301. package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -1
  302. package/dist/runtime/subsystems/analytics/analytics.tokens.js +7 -8
  303. package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -1
  304. package/dist/runtime/subsystems/analytics/cube-backend.js +6 -71
  305. package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -1
  306. package/dist/runtime/subsystems/analytics/index.js +16 -117
  307. package/dist/runtime/subsystems/analytics/index.js.map +1 -1
  308. package/dist/runtime/subsystems/analytics/noop-backend.js +4 -21
  309. package/dist/runtime/subsystems/analytics/noop-backend.js.map +1 -1
  310. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js +4 -8
  311. package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js.map +1 -1
  312. package/dist/runtime/subsystems/auth/auth.module.js +12 -359
  313. package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
  314. package/dist/runtime/subsystems/auth/auth.tokens.js +12 -13
  315. package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
  316. package/dist/runtime/subsystems/auth/backends/encryption-key/env.js +4 -49
  317. package/dist/runtime/subsystems/auth/backends/encryption-key/env.js.map +1 -1
  318. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js +6 -64
  319. package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js.map +1 -1
  320. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js +5 -47
  321. package/dist/runtime/subsystems/auth/backends/state-store.memory-backend.js.map +1 -1
  322. package/dist/runtime/subsystems/auth/controllers/auth.controller.js +5 -139
  323. package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
  324. package/dist/runtime/subsystems/auth/index.js +53 -542
  325. package/dist/runtime/subsystems/auth/index.js.map +1 -1
  326. package/dist/runtime/subsystems/auth/middleware/requester-context.js +9 -65
  327. package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
  328. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js +4 -9
  329. package/dist/runtime/subsystems/auth/protocols/oauth-state-store.js.map +1 -1
  330. package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js +4 -15
  331. package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js.map +1 -1
  332. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js +5 -104
  333. package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js.map +1 -1
  334. package/dist/runtime/subsystems/auth/runtime/session-expired.error.js +5 -16
  335. package/dist/runtime/subsystems/auth/runtime/session-expired.error.js.map +1 -1
  336. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js +5 -29
  337. package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js.map +1 -1
  338. package/dist/runtime/subsystems/bridge/assert-tenant-id.js +5 -18
  339. package/dist/runtime/subsystems/bridge/assert-tenant-id.js.map +1 -1
  340. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js +12 -184
  341. package/dist/runtime/subsystems/bridge/bridge-delivery-handler.js.map +1 -1
  342. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js +10 -448
  343. package/dist/runtime/subsystems/bridge/bridge-delivery.drizzle-backend.js.map +1 -1
  344. package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js +5 -126
  345. package/dist/runtime/subsystems/bridge/bridge-delivery.memory-backend.js.map +1 -1
  346. package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js +6 -308
  347. package/dist/runtime/subsystems/bridge/bridge-delivery.schema.js.map +1 -1
  348. package/dist/runtime/subsystems/bridge/bridge-errors.js +6 -35
  349. package/dist/runtime/subsystems/bridge/bridge-errors.js.map +1 -1
  350. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js +14 -606
  351. package/dist/runtime/subsystems/bridge/bridge-outbox-drain-hook.js.map +1 -1
  352. package/dist/runtime/subsystems/bridge/bridge.module.js +35 -3476
  353. package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
  354. package/dist/runtime/subsystems/bridge/bridge.tokens.js +9 -7
  355. package/dist/runtime/subsystems/bridge/bridge.tokens.js.map +1 -1
  356. package/dist/runtime/subsystems/bridge/event-flow.service.js +11 -137
  357. package/dist/runtime/subsystems/bridge/event-flow.service.js.map +1 -1
  358. package/dist/runtime/subsystems/bridge/generated/registry.js +4 -2
  359. package/dist/runtime/subsystems/bridge/generated/registry.js.map +1 -1
  360. package/dist/runtime/subsystems/bridge/index.js +60 -3470
  361. package/dist/runtime/subsystems/bridge/index.js.map +1 -1
  362. package/dist/runtime/subsystems/bridge/reserved-pools.js +4 -6
  363. package/dist/runtime/subsystems/bridge/reserved-pools.js.map +1 -1
  364. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +10 -133
  365. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -1
  366. package/dist/runtime/subsystems/cache/cache.memory-backend.js +6 -101
  367. package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -1
  368. package/dist/runtime/subsystems/cache/cache.module.js +10 -278
  369. package/dist/runtime/subsystems/cache/cache.module.js.map +1 -1
  370. package/dist/runtime/subsystems/cache/cache.schema.js +4 -14
  371. package/dist/runtime/subsystems/cache/cache.schema.js.map +1 -1
  372. package/dist/runtime/subsystems/cache/cache.tokens.js +6 -7
  373. package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -1
  374. package/dist/runtime/subsystems/cache/index.js +20 -278
  375. package/dist/runtime/subsystems/cache/index.js.map +1 -1
  376. package/dist/runtime/subsystems/events/domain-events.schema.js +3 -72
  377. package/dist/runtime/subsystems/events/domain-events.schema.js.map +1 -1
  378. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +9 -413
  379. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
  380. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +7 -235
  381. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -1
  382. package/dist/runtime/subsystems/events/event-bus.redis-backend.js +8 -20
  383. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -1
  384. package/dist/runtime/subsystems/events/event-keyset-cursor.js +8 -30
  385. package/dist/runtime/subsystems/events/event-keyset-cursor.js.map +1 -1
  386. package/dist/runtime/subsystems/events/event-read.protocol.js +2 -0
  387. package/dist/runtime/subsystems/events/event-read.protocol.js.map +1 -1
  388. package/dist/runtime/subsystems/events/events-errors.js +4 -11
  389. package/dist/runtime/subsystems/events/events-errors.js.map +1 -1
  390. package/dist/runtime/subsystems/events/events.module.js +15 -949
  391. package/dist/runtime/subsystems/events/events.module.js.map +1 -1
  392. package/dist/runtime/subsystems/events/events.tokens.js +10 -11
  393. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -1
  394. package/dist/runtime/subsystems/events/generated/bus.js +9 -240
  395. package/dist/runtime/subsystems/events/generated/bus.js.map +1 -1
  396. package/dist/runtime/subsystems/events/generated/index.js +23 -240
  397. package/dist/runtime/subsystems/events/generated/index.js.map +1 -1
  398. package/dist/runtime/subsystems/events/generated/registry.js +5 -82
  399. package/dist/runtime/subsystems/events/generated/registry.js.map +1 -1
  400. package/dist/runtime/subsystems/events/generated/schemas.js +12 -52
  401. package/dist/runtime/subsystems/events/generated/schemas.js.map +1 -1
  402. package/dist/runtime/subsystems/events/generated/types.js +1 -0
  403. package/dist/runtime/subsystems/events/index.js +32 -949
  404. package/dist/runtime/subsystems/events/index.js.map +1 -1
  405. package/dist/runtime/subsystems/index.js +171 -5912
  406. package/dist/runtime/subsystems/index.js.map +1 -1
  407. package/dist/runtime/subsystems/integration/build-change-source.js +6 -178
  408. package/dist/runtime/subsystems/integration/build-change-source.js.map +1 -1
  409. package/dist/runtime/subsystems/integration/deep-equal.differ.js +4 -109
  410. package/dist/runtime/subsystems/integration/deep-equal.differ.js.map +1 -1
  411. package/dist/runtime/subsystems/integration/detection-config.schema.js +11 -78
  412. package/dist/runtime/subsystems/integration/detection-config.schema.js.map +1 -1
  413. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js +5 -30
  414. package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js.map +1 -1
  415. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js +4 -9
  416. package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js.map +1 -1
  417. package/dist/runtime/subsystems/integration/execute-integration.use-case.js +6 -239
  418. package/dist/runtime/subsystems/integration/execute-integration.use-case.js.map +1 -1
  419. package/dist/runtime/subsystems/integration/incremental-read.js +5 -144
  420. package/dist/runtime/subsystems/integration/incremental-read.js.map +1 -1
  421. package/dist/runtime/subsystems/integration/index.js +83 -1352
  422. package/dist/runtime/subsystems/integration/index.js.map +1 -1
  423. package/dist/runtime/subsystems/integration/integration-audit.schema.js +10 -155
  424. package/dist/runtime/subsystems/integration/integration-audit.schema.js.map +1 -1
  425. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js +7 -270
  426. package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js.map +1 -1
  427. package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js +4 -65
  428. package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js.map +1 -1
  429. package/dist/runtime/subsystems/integration/integration-errors.js +5 -15
  430. package/dist/runtime/subsystems/integration/integration-errors.js.map +1 -1
  431. package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js +5 -7
  432. package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js.map +1 -1
  433. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js +8 -303
  434. package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js.map +1 -1
  435. package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js +5 -125
  436. package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js.map +1 -1
  437. package/dist/runtime/subsystems/integration/integration.module.js +13 -700
  438. package/dist/runtime/subsystems/integration/integration.module.js.map +1 -1
  439. package/dist/runtime/subsystems/integration/integration.tokens.js +11 -9
  440. package/dist/runtime/subsystems/integration/integration.tokens.js.map +1 -1
  441. package/dist/runtime/subsystems/integration/loopback.middleware.js +4 -16
  442. package/dist/runtime/subsystems/integration/loopback.middleware.js.map +1 -1
  443. package/dist/runtime/subsystems/integration/poll-change-source.js +4 -89
  444. package/dist/runtime/subsystems/integration/poll-change-source.js.map +1 -1
  445. package/dist/runtime/subsystems/integration/webhook-change-source.js +4 -70
  446. package/dist/runtime/subsystems/integration/webhook-change-source.js.map +1 -1
  447. package/dist/runtime/subsystems/jobs/bullmq.config.js +9 -140
  448. package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
  449. package/dist/runtime/subsystems/jobs/index.js +88 -2691
  450. package/dist/runtime/subsystems/jobs/index.js.map +1 -1
  451. package/dist/runtime/subsystems/jobs/job-handler.base.js +10 -49
  452. package/dist/runtime/subsystems/jobs/job-handler.base.js.map +1 -1
  453. package/dist/runtime/subsystems/jobs/job-orchestration.schema.js +13 -152
  454. package/dist/runtime/subsystems/jobs/job-orchestration.schema.js.map +1 -1
  455. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js +36 -699
  456. package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
  457. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js +10 -564
  458. package/dist/runtime/subsystems/jobs/job-orchestrator.drizzle-backend.js.map +1 -1
  459. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js +10 -824
  460. package/dist/runtime/subsystems/jobs/job-orchestrator.memory-backend.js.map +1 -1
  461. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js +9 -51
  462. package/dist/runtime/subsystems/jobs/job-run-keyset-cursor.js.map +1 -1
  463. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js +9 -416
  464. package/dist/runtime/subsystems/jobs/job-run-service.drizzle-backend.js.map +1 -1
  465. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js +9 -290
  466. package/dist/runtime/subsystems/jobs/job-run-service.memory-backend.js.map +1 -1
  467. package/dist/runtime/subsystems/jobs/job-step-service.drizzle-backend.js +5 -213
  468. package/dist/runtime/subsystems/jobs/job-step-service.drizzle-backend.js.map +1 -1
  469. package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js +5 -131
  470. package/dist/runtime/subsystems/jobs/job-step-service.memory-backend.js.map +1 -1
  471. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +9 -175
  472. package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
  473. package/dist/runtime/subsystems/jobs/job-worker.js +14 -613
  474. package/dist/runtime/subsystems/jobs/job-worker.js.map +1 -1
  475. package/dist/runtime/subsystems/jobs/job-worker.module.js +23 -2647
  476. package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
  477. package/dist/runtime/subsystems/jobs/jobs-domain.module.js +19 -1897
  478. package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +1 -1
  479. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js +8 -9
  480. package/dist/runtime/subsystems/jobs/jobs-domain.tokens.js.map +1 -1
  481. package/dist/runtime/subsystems/jobs/jobs-errors.js +10 -78
  482. package/dist/runtime/subsystems/jobs/jobs-errors.js.map +1 -1
  483. package/dist/runtime/subsystems/jobs/memory-job-store.js +4 -15
  484. package/dist/runtime/subsystems/jobs/memory-job-store.js.map +1 -1
  485. package/dist/runtime/subsystems/jobs/pool-config.loader.js +9 -124
  486. package/dist/runtime/subsystems/jobs/pool-config.loader.js.map +1 -1
  487. package/dist/runtime/subsystems/observability/index.js +21 -310
  488. package/dist/runtime/subsystems/observability/index.js.map +1 -1
  489. package/dist/runtime/subsystems/observability/observability-errors.js +4 -9
  490. package/dist/runtime/subsystems/observability/observability-errors.js.map +1 -1
  491. package/dist/runtime/subsystems/observability/observability.module.js +11 -300
  492. package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
  493. package/dist/runtime/subsystems/observability/observability.service.js +9 -197
  494. package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
  495. package/dist/runtime/subsystems/observability/observability.tokens.js +5 -3
  496. package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -1
  497. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js +4 -84
  498. package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -1
  499. package/dist/runtime/subsystems/observability/reporters/index.js +5 -84
  500. package/dist/runtime/subsystems/observability/reporters/index.js.map +1 -1
  501. package/dist/runtime/subsystems/storage/index.js +15 -200
  502. package/dist/runtime/subsystems/storage/index.js.map +1 -1
  503. package/dist/runtime/subsystems/storage/storage.local-backend.js +4 -103
  504. package/dist/runtime/subsystems/storage/storage.local-backend.js.map +1 -1
  505. package/dist/runtime/subsystems/storage/storage.memory-backend.js +5 -68
  506. package/dist/runtime/subsystems/storage/storage.memory-backend.js.map +1 -1
  507. package/dist/runtime/subsystems/storage/storage.module.js +8 -200
  508. package/dist/runtime/subsystems/storage/storage.module.js.map +1 -1
  509. package/dist/runtime/subsystems/storage/storage.tokens.js +5 -6
  510. package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -1
  511. package/dist/runtime/subsystems/storage/storage.utils.js +4 -14
  512. package/dist/runtime/subsystems/storage/storage.utils.js.map +1 -1
  513. package/dist/runtime/subsystems/token-key.js +5 -3
  514. package/dist/runtime/subsystems/token-key.js.map +1 -1
  515. package/dist/src/cli/index.js +637 -5454
  516. package/dist/src/cli/index.js.map +1 -1
  517. package/dist/src/index.js +68 -4170
  518. package/dist/src/index.js.map +1 -1
  519. package/package.json +1 -1
  520. package/runtime/subsystems/bridge/bridge-outbox-drain-hook.ts +44 -21
  521. package/runtime/subsystems/jobs/job-worker.ts +17 -11
@@ -0,0 +1,4042 @@
1
+ import {
2
+ DetectionConfigSchema
3
+ } from "./chunk-3NMCDN7L.js";
4
+
5
+ // src/parser/load-entities.ts
6
+ import { resolve as resolve2 } from "path";
7
+
8
+ // src/utils/find-yaml-files.ts
9
+ import { readdirSync } from "fs";
10
+ import { join, resolve } from "path";
11
+ function isYaml(name) {
12
+ return name.endsWith(".yaml") || name.endsWith(".yml");
13
+ }
14
+ function findYamlFiles(dir, opts) {
15
+ const root = resolve(dir);
16
+ const out = [];
17
+ const excluded = new Set((opts?.excludeDirs ?? []).map((d) => resolve(d)));
18
+ const walk = (current) => {
19
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
20
+ if (entry.isDirectory()) {
21
+ if (entry.name.startsWith(".")) continue;
22
+ const child = join(current, entry.name);
23
+ if (excluded.has(resolve(child))) continue;
24
+ walk(child);
25
+ } else if (isYaml(entry.name)) {
26
+ out.push(join(current, entry.name));
27
+ }
28
+ }
29
+ };
30
+ walk(root);
31
+ return out.sort();
32
+ }
33
+
34
+ // src/utils/yaml-loader.ts
35
+ import { readFileSync, existsSync } from "fs";
36
+ import { parse as parseYaml } from "yaml";
37
+
38
+ // src/schema/entity-definition.schema.ts
39
+ import { z } from "zod";
40
+ var FieldTypeSchema = z.enum([
41
+ "string",
42
+ "integer",
43
+ "decimal",
44
+ "boolean",
45
+ "uuid",
46
+ "date",
47
+ "datetime",
48
+ "json",
49
+ "entity_ref",
50
+ // Polymorphic reference: generates {field}EntityType + {field}EntityId columns
51
+ "string_array",
52
+ // Array of strings: generates text[] column
53
+ "enum"
54
+ // Enum type with choices or choices_from
55
+ ]);
56
+ var UiTypeSchema = z.enum([
57
+ "text",
58
+ "textarea",
59
+ "number",
60
+ "money",
61
+ "percentage",
62
+ "email",
63
+ "url",
64
+ "date",
65
+ "datetime",
66
+ "boolean",
67
+ "enum",
68
+ "reference",
69
+ "json",
70
+ "badge",
71
+ "password"
72
+ ]);
73
+ var UiImportanceSchema = z.enum(["primary", "secondary", "tertiary"]);
74
+ var AnalyticsAggregationSchema = z.enum([
75
+ "sum",
76
+ "min",
77
+ "max",
78
+ "count",
79
+ "count_distinct",
80
+ "average",
81
+ "median",
82
+ "percentile",
83
+ "sum_boolean"
84
+ ]);
85
+ var AnalyticsDimensionTypeSchema = z.enum(["categorical", "time"]);
86
+ var AnalyticsEntityTypeSchema = z.enum(["primary", "unique", "foreign", "natural"]);
87
+ var AnalyticsTimeGranularitySchema = z.enum(["day", "week", "month", "quarter", "year"]);
88
+ var AnalyticsVisibilitySchema = z.enum(["internal", "agent", "public"]);
89
+ var NonAdditiveDimensionSchema = z.union([
90
+ z.string(),
91
+ z.object({
92
+ name: z.string(),
93
+ window_choice: z.string().optional(),
94
+ window_groupings: z.array(z.string()).optional()
95
+ })
96
+ ]);
97
+ var SemanticMetadataSchema = z.object({
98
+ measure: z.boolean().optional(),
99
+ analytics_aggregation: AnalyticsAggregationSchema.optional(),
100
+ agg_time_dimension: z.string().optional(),
101
+ non_additive_dimension: NonAdditiveDimensionSchema.optional(),
102
+ dimension: z.boolean().optional(),
103
+ dimension_type: AnalyticsDimensionTypeSchema.optional(),
104
+ time_granularity: AnalyticsTimeGranularitySchema.optional(),
105
+ is_partition: z.boolean().optional(),
106
+ entity: z.boolean().optional(),
107
+ entity_type: AnalyticsEntityTypeSchema.optional(),
108
+ entity_role: z.string().optional(),
109
+ analytics_visibility: AnalyticsVisibilitySchema.optional(),
110
+ semantic_expr: z.string().optional(),
111
+ semantic_label: z.string().optional()
112
+ });
113
+ var UiMetadataSchema = z.object({
114
+ ui_label: z.string().optional(),
115
+ ui_type: UiTypeSchema.optional(),
116
+ ui_importance: UiImportanceSchema.optional(),
117
+ ui_group: z.string().optional(),
118
+ ui_sortable: z.boolean().optional(),
119
+ ui_filterable: z.boolean().optional(),
120
+ ui_visible: z.boolean().optional(),
121
+ ui_placeholder: z.string().optional(),
122
+ ui_help: z.string().optional(),
123
+ ui_format: z.record(z.unknown()).optional()
124
+ });
125
+ var BaseFieldSchema = z.object({
126
+ type: FieldTypeSchema,
127
+ required: z.boolean().optional().default(false),
128
+ nullable: z.boolean().optional().default(false),
129
+ // String constraints
130
+ max_length: z.number().int().positive().optional(),
131
+ min_length: z.number().int().nonnegative().optional(),
132
+ // Numeric constraints
133
+ min: z.number().optional(),
134
+ max: z.number().optional(),
135
+ // Enum/choices (inline definition)
136
+ choices: z.array(z.string()).optional(),
137
+ // Enum/choices from external file (e.g., "relationship_types.yaml")
138
+ // Mutually exclusive with choices - parser loads file and extracts keys
139
+ choices_from: z.string().optional(),
140
+ // Entity reference: allowed entity types for polymorphic refs
141
+ // Required when type is 'entity_ref'
142
+ allowed_types: z.array(z.string()).optional(),
143
+ // Default value
144
+ default: z.unknown().optional(),
145
+ // Indexing
146
+ index: z.boolean().optional(),
147
+ unique: z.boolean().optional(),
148
+ // Foreign key reference (e.g., "accounts.id")
149
+ foreign_key: z.string().optional()
150
+ });
151
+ var FieldDefinitionSchema = BaseFieldSchema.merge(UiMetadataSchema).merge(SemanticMetadataSchema).refine((data) => !(data.required === true && data.nullable === true), {
152
+ message: "'required: true' and 'nullable: true' cannot both be set. A required field cannot be null.",
153
+ path: ["required"]
154
+ }).refine(
155
+ (data) => {
156
+ if (data.min_length !== void 0 && data.type !== "string") {
157
+ return false;
158
+ }
159
+ return true;
160
+ },
161
+ {
162
+ message: "'min_length' can only be used with type 'string'",
163
+ path: ["min_length"]
164
+ }
165
+ ).refine(
166
+ (data) => {
167
+ if (data.max_length !== void 0 && data.type !== "string") {
168
+ return false;
169
+ }
170
+ return true;
171
+ },
172
+ {
173
+ message: "'max_length' can only be used with type 'string'",
174
+ path: ["max_length"]
175
+ }
176
+ ).refine(
177
+ (data) => {
178
+ if (data.min !== void 0 && !["integer", "decimal"].includes(data.type)) {
179
+ return false;
180
+ }
181
+ return true;
182
+ },
183
+ {
184
+ message: "'min' can only be used with numeric types",
185
+ path: ["min"]
186
+ }
187
+ ).refine(
188
+ (data) => {
189
+ if (data.max !== void 0 && !["integer", "decimal"].includes(data.type)) {
190
+ return false;
191
+ }
192
+ return true;
193
+ },
194
+ {
195
+ message: "'max' can only be used with numeric types",
196
+ path: ["max"]
197
+ }
198
+ ).refine(
199
+ (data) => {
200
+ if (data.type === "entity_ref" && !data.allowed_types?.length) {
201
+ return false;
202
+ }
203
+ return true;
204
+ },
205
+ {
206
+ message: "'entity_ref' type requires 'allowed_types' to be specified",
207
+ path: ["allowed_types"]
208
+ }
209
+ ).refine(
210
+ (data) => {
211
+ if (data.allowed_types !== void 0 && data.type !== "entity_ref") {
212
+ return false;
213
+ }
214
+ return true;
215
+ },
216
+ {
217
+ message: "'allowed_types' can only be used with type 'entity_ref'",
218
+ path: ["allowed_types"]
219
+ }
220
+ ).refine(
221
+ (data) => {
222
+ if (data.choices !== void 0 && data.choices_from !== void 0) {
223
+ return false;
224
+ }
225
+ return true;
226
+ },
227
+ {
228
+ message: "'choices' and 'choices_from' cannot both be specified",
229
+ path: ["choices_from"]
230
+ }
231
+ ).refine(
232
+ (data) => {
233
+ if (data.type === "enum" && !data.choices?.length && !data.choices_from) {
234
+ return false;
235
+ }
236
+ return true;
237
+ },
238
+ {
239
+ message: "'enum' type requires either 'choices' or 'choices_from'",
240
+ path: ["choices"]
241
+ }
242
+ ).refine(
243
+ (data) => {
244
+ if (data.measure === true && !data.analytics_aggregation) {
245
+ return false;
246
+ }
247
+ return true;
248
+ },
249
+ {
250
+ message: "When 'measure' is true, 'analytics_aggregation' must be specified",
251
+ path: ["analytics_aggregation"]
252
+ }
253
+ );
254
+ var RelationshipTypeSchema = z.enum(["belongs_to", "has_many", "has_one"]);
255
+ var OnDeleteSchema = z.enum(["restrict", "cascade", "set_null", "no_action"]);
256
+ var RelationshipSchema = z.object({
257
+ type: RelationshipTypeSchema,
258
+ target: z.string(),
259
+ // Target entity name (e.g., "account")
260
+ foreign_key: z.string(),
261
+ // FK field name (e.g., "account_id")
262
+ through: z.string().optional(),
263
+ // For transitive: "owned_opportunities.updates"
264
+ inverse: z.string().optional(),
265
+ // Name of inverse relationship on target entity
266
+ nullable: z.boolean().optional(),
267
+ // Whether the FK column allows NULL
268
+ on_delete: OnDeleteSchema.optional().default("restrict")
269
+ // FK cascade action (belongs_to only; hard-delete only)
270
+ }).strict().refine(
271
+ (data) => {
272
+ if (data.on_delete === "set_null" && data.nullable !== true) {
273
+ return false;
274
+ }
275
+ return true;
276
+ },
277
+ {
278
+ message: "'on_delete: set_null' requires 'nullable: true' \u2014 Postgres cannot null a NOT NULL FK column.",
279
+ path: ["on_delete"]
280
+ }
281
+ );
282
+ var BehaviorConfigSchema = z.union([
283
+ z.string(),
284
+ z.object({
285
+ name: z.string(),
286
+ options: z.record(z.unknown()).optional()
287
+ })
288
+ ]);
289
+ var BehaviorStrategySchema = z.enum(["base_class", "inline"]);
290
+ var FolderStructureSchema = z.enum(["nested", "flat"]).default("nested");
291
+ var FileGroupingSchema = z.enum(["separate", "grouped"]).default("separate");
292
+ var ExposeLayerSchema = z.enum(["repository", "rest", "trpc", "electric"]);
293
+ var EntityConfigSchema = z.object({
294
+ name: z.string().regex(
295
+ /^[a-z][a-z0-9_]*$/,
296
+ "Entity name must be lowercase with underscores (e.g., 'opportunity')"
297
+ ),
298
+ plural: z.string().regex(/^[a-z][a-z0-9_]*$/, "Plural must be lowercase"),
299
+ table: z.string().regex(/^[a-z][a-z0-9_]*$/, "Table must be lowercase"),
300
+ // Layout options (orthogonal concerns)
301
+ // folder_structure: controls directory nesting
302
+ // file_grouping: controls file organization
303
+ folder_structure: FolderStructureSchema.optional(),
304
+ file_grouping: FileGroupingSchema.optional(),
305
+ // Per-entity behavior strategy override (overrides codegen.config.yaml)
306
+ behavior_strategy: BehaviorStrategySchema.optional(),
307
+ // Which layers to generate (default: all)
308
+ expose: z.array(ExposeLayerSchema).optional().default(["repository", "rest", "trpc"]),
309
+ // App-defined patterns (ADR-031, supersedes ADR-005 family:).
310
+ // `pattern:` and `patterns:` are mutually exclusive — use `pattern:`
311
+ // for a single pattern and `patterns:` for multi-pattern composition.
312
+ // Pattern names resolve against the registry at codegen time.
313
+ pattern: z.string().optional(),
314
+ patterns: z.array(z.string()).optional(),
315
+ // Per-pattern config, keyed by pattern name. Each value is validated
316
+ // against the pattern's `configSchema` in composition validation
317
+ // (src/patterns/validate-composition.ts — PATTERN-4).
318
+ config: z.record(z.string(), z.unknown()).optional(),
319
+ // JOB-7: marks this entity as a valid scope target for job scoping.
320
+ // Drives the generated ScopeEntityType union in
321
+ // runtime/subsystems/jobs/generated/scope-entity-type.ts.
322
+ scopeable: z.boolean().optional(),
323
+ // RFC-0001 §1/§8: the integration *surface* this entity belongs to
324
+ // (e.g. 'calendar', 'mail', 'crm'). Surfaces span provider contexts
325
+ // (ADR-0006) — one Google OAuth feeds calendar+mail+transcript. The union
326
+ // of `surface:` values across all entity YAML is the closed set that a
327
+ // provider's `surfaces:` must be a subset of (cross-checked in
328
+ // src/parser/validate-providers.ts). Optional: entities without an
329
+ // integration surface omit it. The surface-package *emission* convention
330
+ // is Track C (#329); this field is only the declarative input both tracks
331
+ // read. Lives inside the `entity:` block (next to `pattern:`/`name:`/`table:`).
332
+ surface: z.string().optional(),
333
+ // Bounded-context declaration (ADR-0004) — "which bounded context this
334
+ // entity belongs to". This is the DURABLE decision; it is a plain
335
+ // bounded-context slug, NOT a folder knob. Different features consume it:
336
+ //
337
+ // - #403 (the FIRST consumer): drives the generated code's
338
+ // module output folder. clean-lite-ps nests the entity's module under
339
+ // `<modules>/<context>/<entity>/` so same-context entities group
340
+ // together; untagged entities stay flat (`<modules>/<entity>/`).
341
+ // - ADR-0004 (deferred): a later `naming: prefix | schema` knob reads
342
+ // this SAME field to drive the Postgres physical layout —
343
+ // `prefix` → `pgTable('<context>__<table>')`, then the flip to
344
+ // `schema` → `pgSchema('<context>').table('<table>')`. NOT wired here.
345
+ //
346
+ // Sibling to `surface:` and orthogonal to it (ADR-0006): context = model
347
+ // cohesion (which domain), surface = vendor composition (which integration).
348
+ // Lives inside the `entity:` block (next to `pattern:`/`name:`/`table:`).
349
+ context: z.string().regex(
350
+ /^[a-z][a-z0-9_]*$/,
351
+ "context must be lowercase snake_case (e.g. 'integration')"
352
+ ).optional()
353
+ }).strict().refine((d) => !(d.pattern && d.patterns), {
354
+ message: "'pattern' and 'patterns' are mutually exclusive"
355
+ });
356
+ var QueryDeclarationSchema = z.object({
357
+ by: z.array(z.string()).min(1),
358
+ unique: z.boolean().optional(),
359
+ select: z.array(z.string()).optional(),
360
+ order: z.string().optional(),
361
+ limit: z.boolean().optional(),
362
+ via: z.string().optional()
363
+ });
364
+ var SearchQueryDeclarationSchema = z.object({
365
+ name: z.literal("search"),
366
+ filters: z.array(z.string()).min(1),
367
+ search: z.string().optional(),
368
+ paginate: z.boolean().optional().default(true),
369
+ order: z.string().optional()
370
+ });
371
+ var AnyQueryDeclarationSchema = z.union([
372
+ SearchQueryDeclarationSchema,
373
+ QueryDeclarationSchema
374
+ ]);
375
+ var IntegrationDirectionSchema = z.enum([
376
+ "inbound",
377
+ "outbound",
378
+ "bidirectional"
379
+ ]);
380
+ var ProviderIntegrationSchema = z.object({
381
+ remote_entity: z.string(),
382
+ direction: IntegrationDirectionSchema,
383
+ cdc: z.boolean().optional().default(false),
384
+ field_mapping: z.record(z.string(), z.string()).optional(),
385
+ read_only_fields: z.array(z.string()).optional()
386
+ });
387
+ var IntegrationConfigSchema = z.object({
388
+ electric: z.boolean().optional().default(false),
389
+ providers: z.record(z.string(), ProviderIntegrationSchema).optional()
390
+ });
391
+ var EventDeclarationSchema = z.object({
392
+ name: z.string().regex(/^[a-z][a-z0-9_]*$/, "Event name must be snake_case"),
393
+ queue: z.string(),
394
+ body: z.record(z.string(), z.string()),
395
+ generate_handler: z.boolean().optional().default(false)
396
+ });
397
+ var SimpleMetricSchema = z.object({
398
+ type: z.literal("simple"),
399
+ measure: z.string(),
400
+ agg: AnalyticsAggregationSchema.optional(),
401
+ filter: z.string().optional(),
402
+ description: z.string().optional(),
403
+ label: z.string().optional()
404
+ });
405
+ var DerivedMetricSchema = z.object({
406
+ type: z.literal("derived"),
407
+ expr: z.string(),
408
+ metrics: z.array(z.string()),
409
+ description: z.string().optional(),
410
+ label: z.string().optional()
411
+ });
412
+ var RatioMetricSchema = z.object({
413
+ type: z.literal("ratio"),
414
+ numerator: z.union([z.string(), SimpleMetricSchema]),
415
+ denominator: z.union([z.string(), SimpleMetricSchema]),
416
+ filter: z.string().optional(),
417
+ description: z.string().optional(),
418
+ label: z.string().optional()
419
+ });
420
+ var CumulativeMetricSchema = z.object({
421
+ type: z.literal("cumulative"),
422
+ measure: z.string(),
423
+ window: z.string().optional(),
424
+ grain_to_date: AnalyticsTimeGranularitySchema.optional(),
425
+ description: z.string().optional(),
426
+ label: z.string().optional()
427
+ });
428
+ var MetricDefinitionSchema = z.discriminatedUnion("type", [
429
+ SimpleMetricSchema,
430
+ DerivedMetricSchema,
431
+ RatioMetricSchema,
432
+ CumulativeMetricSchema
433
+ ]);
434
+ var AnalyticsBlockSchema = z.object({
435
+ measure_packs: z.array(z.string()).optional(),
436
+ cube_name: z.string().optional(),
437
+ metrics: z.record(z.string(), MetricDefinitionSchema).optional()
438
+ });
439
+ var GenerateConfigSchema = z.object({
440
+ writes: z.boolean().optional().default(true)
441
+ }).strict();
442
+ var EntityDefinitionSchema = z.object({
443
+ entity: EntityConfigSchema,
444
+ fields: z.record(z.string(), FieldDefinitionSchema),
445
+ relationships: z.record(z.string(), RelationshipSchema).optional(),
446
+ // Behaviors add cross-cutting concerns (timestamps, soft_delete, user_tracking, etc.)
447
+ behaviors: z.array(BehaviorConfigSchema).optional().default([]),
448
+ // Per-entity generation toggles (e.g. disable write-side emission)
449
+ generate: GenerateConfigSchema.optional(),
450
+ // EAV (entity-attribute-value) dual-write + paired reads (ADR-13).
451
+ // When `true`, codegen emits:
452
+ // - FindXWithFieldsUseCase + ListXWithFieldsUseCase (paired reads)
453
+ // - CreateX / UpdateX use cases in transactional compound-write shape
454
+ // (composes entity service + FieldValueService in one db.transaction,
455
+ // splits `{ fields, ...core }` from the DTO)
456
+ // - GET /:id/with-fields + GET /with-fields controller routes
457
+ // - Service with injected FieldValueRepository + findByIdWithFields /
458
+ // listWithFields paired read methods
459
+ //
460
+ // Consumer contract (must be in place before regen):
461
+ // - BaseService.create/update/delete accept optional `tx` parameter
462
+ // - `@shared/eav-helpers` exports `toEavRows(entityId, entityType, fields)`
463
+ // and `mergeEavRows(rows)`
464
+ // - FieldValueService exposes `upsertMany(rows, tx?)` (inherited from
465
+ // MetadataEntityService)
466
+ // - DRIZZLE_DB injection token available via `@shared/constants/tokens`
467
+ //
468
+ // Defaults to `false` — opt in per entity that needs dynamic/custom fields.
469
+ eav: z.boolean().optional().default(false),
470
+ // Declare this entity IS an EAV value table. When `true`, codegen emits
471
+ // the compound EAV methods (upsertFieldsTransactional, findMergedByEntity)
472
+ // on the service and the upsertCurrentValues method on the repository —
473
+ // no hand extension required. Companion flag `eav_definition_table`
474
+ // identifies the sibling entity that stores the field-key ↔ id lookup.
475
+ //
476
+ // Mutually exclusive with `eav: true` in practice — a value table holds
477
+ // OTHER entities' dynamic fields, it isn't itself an EAV-opt-in entity.
478
+ //
479
+ // Assumption (v1): value tables have a `user_id` column. Future
480
+ // `eav_user_scoped: false` flag will relax this for audit/system EAV.
481
+ eav_value_table: z.boolean().optional().default(false),
482
+ // Singular entity name of the field-definitions entity that pairs with
483
+ // this value table (matches the `target:` convention in relationship
484
+ // YAMLs). Required when `eav_value_table: true`; ignored otherwise.
485
+ eav_definition_table: z.string().optional(),
486
+ // v2: Declarative query generation (ADR-005)
487
+ // Generates repository + service + use case methods from declarations
488
+ queries: z.array(AnyQueryDeclarationSchema).optional(),
489
+ // v2: Integration integration configuration (CODEGEN-EVOLUTION-PLAN Phase 2)
490
+ // Electric SQL + provider integration (Salesforce, HubSpot, etc.)
491
+ integration: IntegrationConfigSchema.optional(),
492
+ // ADR-033.1: Provider-keyed change-source detection.
493
+ //
494
+ // Map of provider name → DetectionConfig. Single-provider entities use
495
+ // a one-key map; multi-provider entities list each provider as a
496
+ // separate key. Each value is an independent `DetectionConfig` (its
497
+ // own mode/cursor/mapping/filters) sourced from the canonical schema
498
+ // in `runtime/subsystems/integration` so this validator and the runtime
499
+ // parser stay in lockstep.
500
+ //
501
+ // Within-file cross-check (ADR-033.1 §6): every key here must also
502
+ // appear in `integration.providers` — see the superRefine on
503
+ // `EntityDefinitionSchema` below.
504
+ detection: z.record(z.string(), DetectionConfigSchema).optional(),
505
+ // NOTE: `surface:` and `context:` moved INTO EntityConfigSchema (the
506
+ // `entity:` block) in 0.12.2 — consumers write them next to
507
+ // `pattern:`/`name:`/`table:`, which is the natural place. They are
508
+ // read via `entity.surface` / `entity.context`. Clean break: no
509
+ // root-level placement is accepted.
510
+ // v2: Domain event declarations (CODEGEN-EVOLUTION-PLAN Phase 2)
511
+ // Generates typed event classes, handlers, and queue registration
512
+ events: z.array(EventDeclarationSchema).optional(),
513
+ // EVT-7: Opt-in typed event emission. Each entry names an `EventDefinition`
514
+ // (top-level `events/<type>.yaml` or an inline `events:` block entry) that
515
+ // the generated use-cases should publish via `TypedEventBus.publish(...)`
516
+ // inside a Drizzle transaction.
517
+ //
518
+ // emits: [contact_created, contact_updated]
519
+ //
520
+ // Cross-validated in `validateEntityEmits()` against the merged event
521
+ // registry. `undefined` ⇒ fallback to untyped lifecycle-events + warning;
522
+ // `[]` ⇒ explicit opt-out, no warning, no typed emission.
523
+ emits: z.array(
524
+ z.string().regex(/^[a-z][a-z0-9_]*$/, "emits entries must be snake_case event type names")
525
+ ).optional(),
526
+ // v2: Analytics / semantic layer configuration
527
+ // Cube.js measure packs, custom cube name, and metric definitions
528
+ analytics: AnalyticsBlockSchema.optional(),
529
+ // Composite (multi-column) unique indexes (#356). Single-column uniqueness
530
+ // is `unique: true` on the field itself; this declares constraints that
531
+ // span 2+ columns, e.g. UNIQUE (conversation_id, sequence). Emitted as a
532
+ // `uniqueIndex(...).on(...)` entry in the generated entity's pgTable
533
+ // extra-config callback. `name` defaults to <table>_<col1>_<col2>_uniq.
534
+ unique_indexes: z.array(
535
+ z.object({
536
+ fields: z.array(z.string()).min(2, "unique_indexes entries span 2+ columns \u2014 use `unique: true` on the field for single-column uniqueness"),
537
+ name: z.string().optional()
538
+ }).strict()
539
+ ).optional()
540
+ }).strict().refine(
541
+ (data) => !data.eav_value_table || typeof data.eav_definition_table === "string",
542
+ {
543
+ message: "`eav_definition_table` is required when `eav_value_table: true` \u2014 declare the singular entity name of the paired field-definitions entity (e.g. `eav_definition_table: 'field_definition'`).",
544
+ path: ["eav_definition_table"]
545
+ }
546
+ ).superRefine((entity, ctx) => {
547
+ if (!entity.detection) return;
548
+ const declared = new Set(Object.keys(entity.integration?.providers ?? {}));
549
+ for (const provider of Object.keys(entity.detection)) {
550
+ if (!declared.has(provider)) {
551
+ ctx.addIssue({
552
+ code: "custom",
553
+ path: ["detection", provider],
554
+ message: `Provider '${provider}' used in detection: but not declared in integration.providers. Known providers: ${[...declared].join(", ")}`
555
+ });
556
+ }
557
+ }
558
+ });
559
+
560
+ // src/schema/event-definition.schema.ts
561
+ import { z as z2 } from "zod";
562
+ var EVENT_DIRECTIONS = ["inbound", "change", "outbound"];
563
+ var EVENT_TIERS = ["domain", "audit"];
564
+ var EVENT_FIELD_TYPES = [
565
+ "uuid",
566
+ "string",
567
+ "number",
568
+ "boolean",
569
+ "date",
570
+ "json",
571
+ "array"
572
+ ];
573
+ var EVENT_ARRAY_ITEM_TYPES = [
574
+ "uuid",
575
+ "string",
576
+ "number",
577
+ "boolean",
578
+ "date"
579
+ ];
580
+ var RESERVED_EVENT_POOLS = [
581
+ "events_inbound",
582
+ "events_change",
583
+ "events_outbound"
584
+ ];
585
+ var EVENT_BACKOFF_STRATEGIES = ["linear", "exponential"];
586
+ var DIRECTION_TO_POOL = {
587
+ inbound: "events_inbound",
588
+ change: "events_change",
589
+ outbound: "events_outbound"
590
+ };
591
+ var EventDirectionSchema = z2.enum(EVENT_DIRECTIONS);
592
+ var EventTierSchema = z2.enum(EVENT_TIERS);
593
+ var EventFieldTypeSchema = z2.enum(EVENT_FIELD_TYPES);
594
+ var EventArrayItemTypeSchema = z2.enum(EVENT_ARRAY_ITEM_TYPES);
595
+ var EventPoolSchema = z2.enum(RESERVED_EVENT_POOLS);
596
+ var EventPayloadFieldSchema = z2.object({
597
+ type: EventFieldTypeSchema,
598
+ items: EventArrayItemTypeSchema.optional(),
599
+ nullable: z2.boolean().optional().default(false),
600
+ description: z2.string().optional()
601
+ }).strict().superRefine((data, ctx) => {
602
+ if (data.type === "array" && data.items === void 0) {
603
+ ctx.addIssue({
604
+ code: z2.ZodIssueCode.custom,
605
+ message: "'items' is required when type is 'array'",
606
+ path: ["items"]
607
+ });
608
+ }
609
+ if (data.type !== "array" && data.items !== void 0) {
610
+ ctx.addIssue({
611
+ code: z2.ZodIssueCode.custom,
612
+ message: `'items' is only valid when type is 'array' (got '${data.type}')`,
613
+ path: ["items"]
614
+ });
615
+ }
616
+ });
617
+ var RetrySchema = z2.object({
618
+ attempts: z2.number().int().min(0).max(20),
619
+ backoff: z2.enum(EVENT_BACKOFF_STRATEGIES)
620
+ }).strict();
621
+ var SNAKE_CASE_RE = /^[a-z][a-z0-9_]*$/;
622
+ var EventDefinitionSchemaCore = z2.object({
623
+ type: z2.string().regex(
624
+ SNAKE_CASE_RE,
625
+ "Event type must be snake_case starting with a letter"
626
+ ),
627
+ tier: EventTierSchema.optional().default("domain"),
628
+ direction: EventDirectionSchema.optional(),
629
+ pool: EventPoolSchema.optional(),
630
+ aggregate: z2.string().regex(SNAKE_CASE_RE).optional(),
631
+ source: z2.string().min(1).optional(),
632
+ destination: z2.string().min(1).optional(),
633
+ payload: z2.record(
634
+ z2.string().regex(SNAKE_CASE_RE, "Payload keys must be snake_case"),
635
+ EventPayloadFieldSchema
636
+ ).default({}),
637
+ retry: RetrySchema.optional().default({
638
+ attempts: 3,
639
+ backoff: "exponential"
640
+ }),
641
+ version: z2.number().int().min(1).optional().default(1),
642
+ description: z2.string().optional()
643
+ }).strict();
644
+ var EventDefinitionSchemaRefined = EventDefinitionSchemaCore.superRefine(
645
+ (data, ctx) => {
646
+ if (data.tier === "audit") {
647
+ if (data.pool !== void 0) {
648
+ ctx.addIssue({
649
+ code: z2.ZodIssueCode.custom,
650
+ message: `Event '${data.type}' is tier:audit; pool MUST be omitted (got '${data.pool}'). Audit events have no pool. See ai-docs/specs/issue-242/plan.md \xA7AUDIT-2.`,
651
+ path: ["pool"]
652
+ });
653
+ }
654
+ if (data.direction !== void 0) {
655
+ ctx.addIssue({
656
+ code: z2.ZodIssueCode.custom,
657
+ message: `Event '${data.type}' is tier:audit; direction MUST be omitted (got '${data.direction}'). Audit events have no direction. See ai-docs/specs/issue-242/plan.md \xA7AUDIT-2.`,
658
+ path: ["direction"]
659
+ });
660
+ }
661
+ return;
662
+ }
663
+ if (data.direction === void 0) {
664
+ ctx.addIssue({
665
+ code: z2.ZodIssueCode.custom,
666
+ message: "'direction' is required when tier is 'domain'",
667
+ path: ["direction"]
668
+ });
669
+ return;
670
+ }
671
+ if (data.direction === "change" && !data.aggregate) {
672
+ ctx.addIssue({
673
+ code: z2.ZodIssueCode.custom,
674
+ message: "'aggregate' is required when direction is 'change'",
675
+ path: ["aggregate"]
676
+ });
677
+ }
678
+ if (data.source !== void 0 && data.direction !== "inbound") {
679
+ ctx.addIssue({
680
+ code: z2.ZodIssueCode.custom,
681
+ message: `'source' is only valid when direction is 'inbound' (got '${data.direction}')`,
682
+ path: ["source"]
683
+ });
684
+ }
685
+ if (data.destination !== void 0 && data.direction !== "outbound") {
686
+ ctx.addIssue({
687
+ code: z2.ZodIssueCode.custom,
688
+ message: `'destination' is only valid when direction is 'outbound' (got '${data.direction}')`,
689
+ path: ["destination"]
690
+ });
691
+ }
692
+ if (data.pool !== void 0) {
693
+ const expected = DIRECTION_TO_POOL[data.direction];
694
+ if (data.pool !== expected) {
695
+ ctx.addIssue({
696
+ code: z2.ZodIssueCode.custom,
697
+ message: `pool '${data.pool}' is inconsistent with direction '${data.direction}' (expected '${expected}')`,
698
+ path: ["pool"]
699
+ });
700
+ }
701
+ }
702
+ }
703
+ );
704
+ var EventDefinitionSchema = EventDefinitionSchemaRefined.transform(
705
+ (parsed) => {
706
+ if (parsed.tier === "audit") {
707
+ return parsed;
708
+ }
709
+ const direction = parsed.direction;
710
+ return {
711
+ ...parsed,
712
+ pool: parsed.pool ?? DIRECTION_TO_POOL[direction]
713
+ };
714
+ }
715
+ );
716
+
717
+ // src/schema/relationship-definition.schema.ts
718
+ import { z as z3 } from "zod";
719
+ var TypeDirectionSchema = z3.object({
720
+ /** Name of the inverse type when viewed from the other direction */
721
+ inverse: z3.string().optional(),
722
+ /** Both directions are equivalent — queries should check both FK columns */
723
+ bidirectional: z3.boolean().optional(),
724
+ /** Explicitly directed, no named inverse (default behavior) */
725
+ directed: z3.boolean().optional()
726
+ }).refine(
727
+ (data) => {
728
+ const set = [data.inverse, data.bidirectional, data.directed].filter(
729
+ (v) => v !== void 0
730
+ );
731
+ return set.length === 1;
732
+ },
733
+ {
734
+ message: "Exactly one of inverse, bidirectional, or directed must be specified"
735
+ }
736
+ );
737
+ var RelationshipTypesSchema = z3.union([
738
+ // Simple list: all types are directed from→to
739
+ z3.array(z3.string().regex(/^[a-z][a-z0-9_]*$/, "Type must be snake_case")),
740
+ // Object map: each type has direction metadata
741
+ z3.record(
742
+ z3.string().regex(/^[a-z][a-z0-9_]*$/, "Type key must be snake_case"),
743
+ TypeDirectionSchema
744
+ )
745
+ ]);
746
+ var OnDeleteActionSchema = z3.enum(["restrict", "cascade", "set_null", "no_action"]).default("restrict");
747
+ var RelationshipConfigSchema = z3.object({
748
+ /** Relationship name (snake_case). Used for class/file naming. */
749
+ name: z3.string().regex(
750
+ /^[a-z][a-z0-9_]*$/,
751
+ "Relationship name must be snake_case"
752
+ ),
753
+ /** Database table name. Defaults to {name}s if not specified. */
754
+ table: z3.string().regex(/^[a-z][a-z0-9_]*$/, "Table must be snake_case").optional(),
755
+ /** The "from" entity — generates {entity}_id FK column (subject). */
756
+ from: z3.string().regex(/^[a-z][a-z0-9_]*$/, "Entity name must be snake_case"),
757
+ /** The "to" entity — generates {entity}_id FK column (object). */
758
+ to: z3.string().regex(/^[a-z][a-z0-9_]*$/, "Entity name must be snake_case"),
759
+ /**
760
+ * Relationship subtypes. Optional — omit for untyped junctions.
761
+ * When present, generates a `type` enum column on the junction table.
762
+ *
763
+ * Simple list: all types are directed (from→to). Use for cross-type
764
+ * relationships where entity asymmetry makes direction obvious.
765
+ *
766
+ * Object map: each type declares its own direction metadata.
767
+ * Required for self-referential relationships (from === to).
768
+ */
769
+ types: RelationshipTypesSchema.optional(),
770
+ /**
771
+ * Generate temporal validity fields: valid_from (date), valid_to (date?),
772
+ * is_current (boolean, denormalized for query performance).
773
+ * Default: true
774
+ */
775
+ temporal: z3.boolean().default(true),
776
+ /**
777
+ * Generate source tracking fields: source (enum), confidence (decimal 0-1).
778
+ * Default: true
779
+ */
780
+ sourced: z3.boolean().default(true),
781
+ /** on_delete action for the "from" endpoint FK. Default: restrict */
782
+ on_delete_from: OnDeleteActionSchema.optional(),
783
+ /** on_delete action for the "to" endpoint FK. Default: restrict */
784
+ on_delete_to: OnDeleteActionSchema.optional(),
785
+ /**
786
+ * Override the default unique constraint columns.
787
+ *
788
+ * Defaults:
789
+ * - Typed: [from_id, to_id, type]
790
+ * - Typed + temporal: [from_id, to_id, type, valid_from]
791
+ * - Untyped: [from_id, to_id]
792
+ *
793
+ * Use this when the default doesn't fit — e.g., allowing multiple
794
+ * relationships of the same type between the same entities at different times.
795
+ */
796
+ unique_on: z3.array(z3.string()).optional()
797
+ }).strict();
798
+ var RelationshipQuerySchema = z3.object({
799
+ by: z3.array(z3.string()).min(1),
800
+ unique: z3.boolean().optional(),
801
+ select: z3.array(z3.string()).optional(),
802
+ order: z3.string().optional(),
803
+ limit: z3.boolean().optional()
804
+ });
805
+ var RelationshipDefinitionSchema = z3.object({
806
+ /** Relationship configuration block */
807
+ relationship: RelationshipConfigSchema,
808
+ /**
809
+ * Additional fields beyond auto-generated ones.
810
+ * These describe the relationship, not either endpoint entity.
811
+ * Uses the same field definition schema as entity fields.
812
+ */
813
+ fields: z3.record(z3.string(), z3.any()).optional(),
814
+ /** Declarative queries — same syntax as entity queries. */
815
+ queries: z3.array(RelationshipQuerySchema).optional()
816
+ }).strict().refine(
817
+ (data) => {
818
+ if (data.relationship.from === data.relationship.to && data.relationship.types) {
819
+ return !Array.isArray(data.relationship.types);
820
+ }
821
+ return true;
822
+ },
823
+ {
824
+ message: "Self-referential relationships must use the object map form for types (with inverse/bidirectional/directed metadata), not a simple list",
825
+ path: ["relationship", "types"]
826
+ }
827
+ ).refine(
828
+ (data) => {
829
+ if (!data.fields) return true;
830
+ const reserved = getReservedColumnNames(data.relationship);
831
+ const collisions = Object.keys(data.fields).filter(
832
+ (key) => reserved.has(key)
833
+ );
834
+ return collisions.length === 0;
835
+ },
836
+ {
837
+ message: "fields: contains keys that collide with auto-generated columns. Reserved names depend on config (type, valid_from, valid_to, is_current, source, confidence, id, created_at, updated_at, and FK columns).",
838
+ path: ["fields"]
839
+ }
840
+ );
841
+ function getReservedColumnNames(config) {
842
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
843
+ const reserved = /* @__PURE__ */ new Set([
844
+ "id",
845
+ "created_at",
846
+ "updated_at",
847
+ fromColumn,
848
+ toColumn
849
+ ]);
850
+ if (config.types) {
851
+ reserved.add("type");
852
+ }
853
+ if (config.temporal) {
854
+ reserved.add("valid_from");
855
+ reserved.add("valid_to");
856
+ reserved.add("is_current");
857
+ }
858
+ if (config.sourced) {
859
+ reserved.add("source");
860
+ reserved.add("confidence");
861
+ }
862
+ return reserved;
863
+ }
864
+ function deriveRelationshipFKColumns(config) {
865
+ if (config.from === config.to) {
866
+ return {
867
+ fromColumn: `from_${config.from}_id`,
868
+ toColumn: `to_${config.to}_id`
869
+ };
870
+ }
871
+ return {
872
+ fromColumn: `${config.from}_id`,
873
+ toColumn: `${config.to}_id`
874
+ };
875
+ }
876
+ function deriveTableName(config) {
877
+ return config.table ?? `${config.name}s`;
878
+ }
879
+ function deriveUniqueConstraint(config) {
880
+ if (config.unique_on) return config.unique_on;
881
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
882
+ const columns = [fromColumn, toColumn];
883
+ if (config.types) {
884
+ columns.push("type");
885
+ }
886
+ if (config.temporal && config.types) {
887
+ columns.push("valid_from");
888
+ }
889
+ return columns;
890
+ }
891
+
892
+ // src/schema/junction-definition.schema.ts
893
+ import { z as z4 } from "zod";
894
+
895
+ // src/patterns/library/base-junction-fields.ts
896
+ var BaseJunctionFields = [
897
+ { name: "is_primary", type: "boolean" },
898
+ { name: "started_at", type: "timestamp" },
899
+ { name: "ended_at", type: "timestamp" },
900
+ { name: "sourced_from", type: "text" },
901
+ { name: "confidence", type: "numeric(5,4)" },
902
+ { name: "matched_at", type: "timestamp" }
903
+ ];
904
+ var BASE_JUNCTION_FIELD_NAMES = new Set(
905
+ BaseJunctionFields.map((c) => c.name)
906
+ );
907
+
908
+ // src/schema/junction-definition.schema.ts
909
+ var EntityNameSchema = z4.string().regex(/^[a-z][a-z0-9_]*$/, "Entity reference must be snake_case");
910
+ var JunctionDefinitionSchema = z4.object({
911
+ /** Discriminator literal — `pattern: Junction`. */
912
+ pattern: z4.literal("Junction"),
913
+ /**
914
+ * Exactly two endpoint entity names. Both intra- and cross-domain
915
+ * pairings are accepted; entity existence is validated by the
916
+ * analyzer in a later leaf.
917
+ */
918
+ between: z4.tuple([EntityNameSchema, EntityNameSchema]),
919
+ /**
920
+ * Emit BaseJunctionFields temporal columns (`started_at`, `ended_at`,
921
+ * `matched_at`). Default true. Matches Relationship's `temporal` toggle.
922
+ */
923
+ temporal: z4.boolean().optional().default(true),
924
+ /**
925
+ * Emit BaseJunctionFields sourcing columns (`sourced_from`,
926
+ * `confidence`). Default true. Matches Relationship's `sourced` toggle.
927
+ */
928
+ sourced: z4.boolean().optional().default(true),
929
+ /**
930
+ * Junction-specific fields beyond `BaseJunctionFields`. Includes the
931
+ * per-pairing role enum (declared inline; never shared across
932
+ * pairings). Shape is validated downstream by the codegen layer
933
+ * using the existing entity FieldDefinitionSchema.
934
+ */
935
+ fields: z4.record(z4.string(), z4.any()).optional(),
936
+ /**
937
+ * Declarative queries — same syntax as entity queries. Shape is
938
+ * validated downstream by the codegen layer.
939
+ */
940
+ queries: z4.array(z4.any()).optional(),
941
+ /**
942
+ * Per-side opt-out for parent-service fan-out (CGP-60). When a side
943
+ * is `false`, the `_inject-parent-service-*` templates emit nothing
944
+ * on that side (and the corresponding module wiring is skipped).
945
+ * The junction service body is always emitted regardless. Defaults
946
+ * to `{ left: true, right: true }`.
947
+ */
948
+ expose_on_parent: z4.object({
949
+ left: z4.boolean().optional().default(true),
950
+ right: z4.boolean().optional().default(true)
951
+ }).optional().default({ left: true, right: true })
952
+ }).strict().refine((d) => d.between[0] !== d.between[1], {
953
+ message: "`between` endpoints must be distinct",
954
+ path: ["between"]
955
+ }).refine(
956
+ (d) => {
957
+ const fieldNames = Object.keys(d.fields ?? {});
958
+ return !fieldNames.some((n) => BASE_JUNCTION_FIELD_NAMES.has(n));
959
+ },
960
+ {
961
+ message: "`fields:` block redeclares a reserved BaseJunctionFields column (is_primary, started_at, ended_at, sourced_from, confidence, matched_at)",
962
+ path: ["fields"]
963
+ }
964
+ );
965
+ function validateJunctionDefinition(data) {
966
+ return JunctionDefinitionSchema.parse(data);
967
+ }
968
+ function safeValidateJunctionDefinition(data) {
969
+ const result = JunctionDefinitionSchema.safeParse(data);
970
+ if (result.success) {
971
+ return { success: true, data: result.data };
972
+ }
973
+ return { success: false, error: result.error };
974
+ }
975
+
976
+ // src/schema/provider-definition.schema.ts
977
+ import { z as z5 } from "zod";
978
+ var IMPORT_REF_RE = /^[^#\s]+#[A-Za-z_$][A-Za-z0-9_$]*$/;
979
+ var ImportRefSchema = z5.string().regex(
980
+ IMPORT_REF_RE,
981
+ "must be an 'import-path#Export' reference (e.g. '@app/foo/bar.strategy#BarStrategy')"
982
+ );
983
+ function parseImportRef(ref) {
984
+ const hash = ref.indexOf("#");
985
+ return { path: ref.slice(0, hash), exportName: ref.slice(hash + 1) };
986
+ }
987
+ var AuthTypeSchema = z5.enum(["oauth2", "api-key", "app-password"]);
988
+ var AuthSchema = z5.object({
989
+ type: AuthTypeSchema,
990
+ // Class implementing the auth subsystem's strategy contract (ADR-031).
991
+ // Pre-flight verified against a real export at codegen time.
992
+ strategy: ImportRefSchema,
993
+ // Required (and non-empty) iff type === 'oauth2'; see refine below.
994
+ scopes: z5.array(z5.string()).optional()
995
+ }).strict().refine(
996
+ (a) => a.type !== "oauth2" || a.scopes !== void 0 && a.scopes.length > 0,
997
+ {
998
+ message: "auth.scopes is required and must be non-empty when auth.type is 'oauth2'",
999
+ path: ["scopes"]
1000
+ }
1001
+ );
1002
+ var ClientSchema = z5.object({
1003
+ // API client class. Pre-flight verified against a real export.
1004
+ class: ImportRefSchema,
1005
+ base_url: z5.string().url("client.base_url must be an absolute URL")
1006
+ }).strict();
1007
+ var ProviderDefinitionSchema = z5.object({
1008
+ // Provider id — the canonical string used as detection: keys, audit rows,
1009
+ // subscription rows. kebab/lower; unique across definitions/providers/
1010
+ // (uniqueness is a cross-file check in validate-providers.ts).
1011
+ slug: z5.string().regex(
1012
+ /^[a-z][a-z0-9-]*$/,
1013
+ "slug must be kebab-case lower (e.g. 'google', 'hubspot')"
1014
+ ),
1015
+ display_name: z5.string().optional(),
1016
+ auth: AuthSchema,
1017
+ client: ClientSchema,
1018
+ // Surfaces this provider serves (ADR-0006: surfaces span contexts — one
1019
+ // Google OAuth feeds calendar+mail+transcript). Each must reference a real
1020
+ // `surface:` declared on some entity; that cross-check is in
1021
+ // validate-providers.ts. Non-empty enforced here.
1022
+ surfaces: z5.array(z5.string()).min(1, "surfaces must list at least one surface"),
1023
+ // Optional auth lifecycle hints consumed by provider-module emission (D2).
1024
+ // `refresh_behavior` is left as a free string in D1 — its domain firms up
1025
+ // when D2 consumes it; carrying it now keeps the YAML lossless.
1026
+ token_lifetime: z5.number().int().positive().optional(),
1027
+ refresh_behavior: z5.string().optional()
1028
+ }).strict();
1029
+
1030
+ // src/utils/yaml-loader.ts
1031
+ function loadEntityFromYaml(filePath) {
1032
+ if (!existsSync(filePath)) {
1033
+ return {
1034
+ success: false,
1035
+ error: `File not found: ${filePath}`,
1036
+ filePath
1037
+ };
1038
+ }
1039
+ let content;
1040
+ try {
1041
+ content = readFileSync(filePath, "utf-8");
1042
+ } catch (err) {
1043
+ return {
1044
+ success: false,
1045
+ error: `Failed to read file: ${filePath}`,
1046
+ details: [err instanceof Error ? err.message : String(err)],
1047
+ filePath
1048
+ };
1049
+ }
1050
+ let parsed;
1051
+ try {
1052
+ parsed = parseYaml(content);
1053
+ } catch (err) {
1054
+ return {
1055
+ success: false,
1056
+ error: `Invalid YAML syntax in ${filePath}`,
1057
+ details: [err instanceof Error ? err.message : String(err)],
1058
+ filePath
1059
+ };
1060
+ }
1061
+ const result = EntityDefinitionSchema.safeParse(parsed);
1062
+ if (!result.success) {
1063
+ return {
1064
+ success: false,
1065
+ error: `Validation failed for ${filePath}`,
1066
+ details: formatZodErrors(result.error),
1067
+ filePath
1068
+ };
1069
+ }
1070
+ return {
1071
+ success: true,
1072
+ definition: result.data,
1073
+ filePath
1074
+ };
1075
+ }
1076
+ function formatZodErrors(error) {
1077
+ return error.errors.map((err) => {
1078
+ const path2 = err.path.join(".");
1079
+ const location = path2 ? `at '${path2}'` : "at root";
1080
+ return `${err.message} ${location}`;
1081
+ });
1082
+ }
1083
+ function loadEntitiesFromYaml(filePaths) {
1084
+ const successes = [];
1085
+ const failures = [];
1086
+ for (const filePath of filePaths) {
1087
+ const result = loadEntityFromYaml(filePath);
1088
+ if (result.success) {
1089
+ successes.push(result);
1090
+ } else {
1091
+ failures.push(result);
1092
+ }
1093
+ }
1094
+ return { successes, failures };
1095
+ }
1096
+ function loadRelationshipFromYaml(filePath) {
1097
+ if (!existsSync(filePath)) {
1098
+ return {
1099
+ success: false,
1100
+ error: `File not found: ${filePath}`,
1101
+ filePath
1102
+ };
1103
+ }
1104
+ let content;
1105
+ try {
1106
+ content = readFileSync(filePath, "utf-8");
1107
+ } catch (err) {
1108
+ return {
1109
+ success: false,
1110
+ error: `Failed to read file: ${filePath}`,
1111
+ details: [err instanceof Error ? err.message : String(err)],
1112
+ filePath
1113
+ };
1114
+ }
1115
+ let parsed;
1116
+ try {
1117
+ parsed = parseYaml(content);
1118
+ } catch (err) {
1119
+ return {
1120
+ success: false,
1121
+ error: `Invalid YAML syntax in ${filePath}`,
1122
+ details: [err instanceof Error ? err.message : String(err)],
1123
+ filePath
1124
+ };
1125
+ }
1126
+ const result = RelationshipDefinitionSchema.safeParse(parsed);
1127
+ if (!result.success) {
1128
+ return {
1129
+ success: false,
1130
+ error: `Validation failed for ${filePath}`,
1131
+ details: formatZodErrors(result.error),
1132
+ filePath
1133
+ };
1134
+ }
1135
+ return {
1136
+ success: true,
1137
+ definition: result.data,
1138
+ filePath
1139
+ };
1140
+ }
1141
+ function loadEventFromYaml(filePath) {
1142
+ if (!existsSync(filePath)) {
1143
+ return {
1144
+ success: false,
1145
+ error: `File not found: ${filePath}`,
1146
+ filePath
1147
+ };
1148
+ }
1149
+ let content;
1150
+ try {
1151
+ content = readFileSync(filePath, "utf-8");
1152
+ } catch (err) {
1153
+ return {
1154
+ success: false,
1155
+ error: `Failed to read file: ${filePath}`,
1156
+ details: [err instanceof Error ? err.message : String(err)],
1157
+ filePath
1158
+ };
1159
+ }
1160
+ let parsed;
1161
+ try {
1162
+ parsed = parseYaml(content);
1163
+ } catch (err) {
1164
+ return {
1165
+ success: false,
1166
+ error: `Invalid YAML syntax in ${filePath}`,
1167
+ details: [err instanceof Error ? err.message : String(err)],
1168
+ filePath
1169
+ };
1170
+ }
1171
+ const result = EventDefinitionSchema.safeParse(parsed);
1172
+ if (!result.success) {
1173
+ return {
1174
+ success: false,
1175
+ error: `Validation failed for ${filePath}`,
1176
+ details: formatZodErrors(result.error),
1177
+ filePath
1178
+ };
1179
+ }
1180
+ return {
1181
+ success: true,
1182
+ definition: result.data,
1183
+ filePath
1184
+ };
1185
+ }
1186
+ function loadJunctionFromYaml(filePath) {
1187
+ if (!existsSync(filePath)) {
1188
+ return {
1189
+ success: false,
1190
+ error: `File not found: ${filePath}`,
1191
+ filePath
1192
+ };
1193
+ }
1194
+ let content;
1195
+ try {
1196
+ content = readFileSync(filePath, "utf-8");
1197
+ } catch (err) {
1198
+ return {
1199
+ success: false,
1200
+ error: `Failed to read file: ${filePath}`,
1201
+ details: [err instanceof Error ? err.message : String(err)],
1202
+ filePath
1203
+ };
1204
+ }
1205
+ let parsed;
1206
+ try {
1207
+ parsed = parseYaml(content);
1208
+ } catch (err) {
1209
+ return {
1210
+ success: false,
1211
+ error: `Invalid YAML syntax in ${filePath}`,
1212
+ details: [err instanceof Error ? err.message : String(err)],
1213
+ filePath
1214
+ };
1215
+ }
1216
+ const result = JunctionDefinitionSchema.safeParse(parsed);
1217
+ if (!result.success) {
1218
+ return {
1219
+ success: false,
1220
+ error: `Validation failed for ${filePath}`,
1221
+ details: formatZodErrors(result.error),
1222
+ filePath
1223
+ };
1224
+ }
1225
+ return {
1226
+ success: true,
1227
+ definition: result.data,
1228
+ filePath
1229
+ };
1230
+ }
1231
+ function loadProviderFromYaml(filePath) {
1232
+ if (!existsSync(filePath)) {
1233
+ return {
1234
+ success: false,
1235
+ error: `File not found: ${filePath}`,
1236
+ filePath
1237
+ };
1238
+ }
1239
+ let content;
1240
+ try {
1241
+ content = readFileSync(filePath, "utf-8");
1242
+ } catch (err) {
1243
+ return {
1244
+ success: false,
1245
+ error: `Failed to read file: ${filePath}`,
1246
+ details: [err instanceof Error ? err.message : String(err)],
1247
+ filePath
1248
+ };
1249
+ }
1250
+ let parsed;
1251
+ try {
1252
+ parsed = parseYaml(content);
1253
+ } catch (err) {
1254
+ return {
1255
+ success: false,
1256
+ error: `Invalid YAML syntax in ${filePath}`,
1257
+ details: [err instanceof Error ? err.message : String(err)],
1258
+ filePath
1259
+ };
1260
+ }
1261
+ const result = ProviderDefinitionSchema.safeParse(parsed);
1262
+ if (!result.success) {
1263
+ return {
1264
+ success: false,
1265
+ error: `Validation failed for ${filePath}`,
1266
+ details: formatZodErrors(result.error),
1267
+ filePath
1268
+ };
1269
+ }
1270
+ return {
1271
+ success: true,
1272
+ definition: result.data,
1273
+ filePath
1274
+ };
1275
+ }
1276
+ function loadProvidersFromYaml(filePaths) {
1277
+ const successes = [];
1278
+ const failures = [];
1279
+ for (const filePath of filePaths) {
1280
+ const result = loadProviderFromYaml(filePath);
1281
+ if (result.success) {
1282
+ successes.push(result);
1283
+ } else {
1284
+ failures.push(result);
1285
+ }
1286
+ }
1287
+ return { successes, failures };
1288
+ }
1289
+ function detectYamlType(filePath) {
1290
+ if (!existsSync(filePath)) return "unknown";
1291
+ try {
1292
+ const content = readFileSync(filePath, "utf-8");
1293
+ const parsed = parseYaml(content);
1294
+ if (parsed && typeof parsed === "object") {
1295
+ if (parsed.pattern === "Junction") return "junction";
1296
+ if ("entity" in parsed) return "entity";
1297
+ if ("relationship" in parsed) return "relationship";
1298
+ }
1299
+ } catch {
1300
+ }
1301
+ return "unknown";
1302
+ }
1303
+
1304
+ // src/parser/load-entities.ts
1305
+ function transformToEntity(result) {
1306
+ const { definition, filePath } = result;
1307
+ const queries = definition.queries?.filter((q) => "by" in q).map((q) => ({
1308
+ by: q.by,
1309
+ unique: q.unique,
1310
+ select: q.select,
1311
+ order: q.order,
1312
+ limit: q.limit,
1313
+ via: q.via
1314
+ }));
1315
+ const entity = {
1316
+ name: definition.entity.name,
1317
+ plural: definition.entity.plural,
1318
+ table: definition.entity.table,
1319
+ pattern: definition.entity.pattern,
1320
+ patterns: definition.entity.patterns,
1321
+ patternConfig: definition.entity.config,
1322
+ scopeable: definition.entity.scopeable ?? false,
1323
+ folderStructure: definition.entity.folder_structure ?? "nested",
1324
+ fields: /* @__PURE__ */ new Map(),
1325
+ relationships: /* @__PURE__ */ new Map(),
1326
+ behaviors: definition.behaviors.map((b) => typeof b === "string" ? b : b.name),
1327
+ queries,
1328
+ sourcePath: filePath
1329
+ };
1330
+ for (const [name, fieldDef] of Object.entries(definition.fields)) {
1331
+ const field = {
1332
+ name,
1333
+ type: fieldDef.type,
1334
+ required: fieldDef.required ?? false,
1335
+ nullable: fieldDef.nullable ?? false,
1336
+ unique: fieldDef.unique ?? false,
1337
+ index: fieldDef.index ?? false,
1338
+ foreignKey: fieldDef.foreign_key ? parseForeignKey(fieldDef.foreign_key) : void 0,
1339
+ choices: fieldDef.choices,
1340
+ constraints: {
1341
+ minLength: fieldDef.min_length,
1342
+ maxLength: fieldDef.max_length,
1343
+ min: fieldDef.min,
1344
+ max: fieldDef.max
1345
+ },
1346
+ ui: {
1347
+ label: fieldDef.ui_label,
1348
+ type: fieldDef.ui_type,
1349
+ importance: fieldDef.ui_importance,
1350
+ group: fieldDef.ui_group,
1351
+ sortable: fieldDef.ui_sortable,
1352
+ filterable: fieldDef.ui_filterable,
1353
+ visible: fieldDef.ui_visible
1354
+ }
1355
+ };
1356
+ entity.fields.set(name, field);
1357
+ }
1358
+ if (definition.relationships) {
1359
+ for (const [name, relDef] of Object.entries(definition.relationships)) {
1360
+ const relationship = {
1361
+ name,
1362
+ type: relDef.type,
1363
+ target: relDef.target,
1364
+ foreignKey: relDef.foreign_key,
1365
+ inverse: relDef.inverse,
1366
+ through: relDef.through,
1367
+ resolved: false
1368
+ };
1369
+ entity.relationships.set(name, relationship);
1370
+ }
1371
+ }
1372
+ if (definition.integration) {
1373
+ const integrationDef = definition.integration;
1374
+ const parsedIntegration = {
1375
+ electric: integrationDef.electric ?? false
1376
+ };
1377
+ if (integrationDef.providers) {
1378
+ parsedIntegration.providers = {};
1379
+ for (const [providerName, providerDef] of Object.entries(integrationDef.providers)) {
1380
+ const parsedProvider = {
1381
+ remoteEntity: providerDef.remote_entity,
1382
+ direction: providerDef.direction,
1383
+ cdc: providerDef.cdc ?? false
1384
+ };
1385
+ if (providerDef.field_mapping) {
1386
+ parsedProvider.fieldMapping = providerDef.field_mapping;
1387
+ }
1388
+ if (providerDef.read_only_fields) {
1389
+ parsedProvider.readOnlyFields = providerDef.read_only_fields;
1390
+ }
1391
+ parsedIntegration.providers[providerName] = parsedProvider;
1392
+ }
1393
+ }
1394
+ entity.integration = parsedIntegration;
1395
+ }
1396
+ if (definition.events) {
1397
+ entity.events = definition.events.map((ev) => ({
1398
+ name: ev.name,
1399
+ queue: ev.queue,
1400
+ body: ev.body,
1401
+ generateHandler: ev.generate_handler
1402
+ }));
1403
+ }
1404
+ if (definition.emits !== void 0) {
1405
+ entity.emits = definition.emits;
1406
+ }
1407
+ return entity;
1408
+ }
1409
+ function parseForeignKey(fk) {
1410
+ const [table, column] = fk.split(".");
1411
+ return { table, column: column ?? "id" };
1412
+ }
1413
+ function loadErrorToIssue(error) {
1414
+ const issues = [];
1415
+ issues.push({
1416
+ severity: "error",
1417
+ type: "parse_error",
1418
+ message: error.error,
1419
+ path: error.filePath
1420
+ });
1421
+ if (error.details) {
1422
+ for (const detail of error.details) {
1423
+ issues.push({
1424
+ severity: "error",
1425
+ type: "schema_error",
1426
+ message: detail,
1427
+ path: error.filePath
1428
+ });
1429
+ }
1430
+ }
1431
+ return issues;
1432
+ }
1433
+ function loadEntities(entitiesDir, opts) {
1434
+ const entities = [];
1435
+ const issues = [];
1436
+ const resolvedDir = resolve2(entitiesDir);
1437
+ let files;
1438
+ try {
1439
+ files = findYamlFiles(resolvedDir, { excludeDirs: opts?.excludeDirs });
1440
+ } catch (err) {
1441
+ issues.push({
1442
+ severity: "error",
1443
+ type: "parse_error",
1444
+ message: `Failed to read directory: ${resolvedDir}`,
1445
+ path: resolvedDir
1446
+ });
1447
+ return { entities, issues };
1448
+ }
1449
+ if (files.length === 0) {
1450
+ issues.push({
1451
+ severity: "warning",
1452
+ type: "no_files",
1453
+ message: `No YAML files found in directory: ${resolvedDir}`,
1454
+ path: resolvedDir
1455
+ });
1456
+ return { entities, issues };
1457
+ }
1458
+ for (const filePath of files) {
1459
+ const result = loadEntityFromYaml(filePath);
1460
+ if (result.success) {
1461
+ entities.push(transformToEntity(result));
1462
+ } else {
1463
+ issues.push(...loadErrorToIssue(result));
1464
+ }
1465
+ }
1466
+ return { entities, issues };
1467
+ }
1468
+ function resolveReferences(entities) {
1469
+ const issues = [];
1470
+ const entityMap = /* @__PURE__ */ new Map();
1471
+ for (const entity of entities) {
1472
+ if (entityMap.has(entity.name)) {
1473
+ issues.push({
1474
+ severity: "error",
1475
+ type: "duplicate_entity",
1476
+ entity: entity.name,
1477
+ message: `Duplicate entity name: ${entity.name}`,
1478
+ path: entity.sourcePath
1479
+ });
1480
+ }
1481
+ entityMap.set(entity.name, entity);
1482
+ }
1483
+ for (const entity of entities) {
1484
+ for (const [relName, rel] of entity.relationships) {
1485
+ const targetEntity = entityMap.get(rel.target);
1486
+ if (targetEntity) {
1487
+ rel.resolved = true;
1488
+ } else {
1489
+ issues.push({
1490
+ severity: "error",
1491
+ type: "missing_target",
1492
+ entity: entity.name,
1493
+ field: relName,
1494
+ message: `Relationship '${relName}' references unknown entity '${rel.target}'`,
1495
+ path: entity.sourcePath,
1496
+ suggestion: `Define entity '${rel.target}' or fix the target name`
1497
+ });
1498
+ }
1499
+ }
1500
+ for (const [fieldName, field] of entity.fields) {
1501
+ if (field.foreignKey) {
1502
+ const targetTable = field.foreignKey.table;
1503
+ const targetEntity = Array.from(entityMap.values()).find(
1504
+ (e) => e.table === targetTable
1505
+ );
1506
+ if (!targetEntity) {
1507
+ issues.push({
1508
+ severity: "warning",
1509
+ type: "missing_fk_target",
1510
+ entity: entity.name,
1511
+ field: fieldName,
1512
+ message: `Foreign key references unknown table '${targetTable}'`,
1513
+ path: entity.sourcePath,
1514
+ suggestion: `Define entity with table '${targetTable}' or fix the foreign_key reference`
1515
+ });
1516
+ }
1517
+ }
1518
+ }
1519
+ }
1520
+ return issues;
1521
+ }
1522
+ function transformToRelationshipDefinition(result) {
1523
+ const { definition, filePath } = result;
1524
+ const config = definition.relationship;
1525
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
1526
+ const table = deriveTableName(config);
1527
+ const uniqueOn = deriveUniqueConstraint(config);
1528
+ const types = resolveTypeDirections(config.types);
1529
+ const fields = /* @__PURE__ */ new Map();
1530
+ if (definition.fields) {
1531
+ for (const [name, fieldDef] of Object.entries(definition.fields)) {
1532
+ const field = {
1533
+ name,
1534
+ type: fieldDef.type,
1535
+ required: fieldDef.required ?? false,
1536
+ nullable: fieldDef.nullable ?? false,
1537
+ unique: fieldDef.unique ?? false,
1538
+ index: fieldDef.index ?? false,
1539
+ foreignKey: fieldDef.foreign_key ? parseForeignKey(fieldDef.foreign_key) : void 0,
1540
+ choices: fieldDef.choices,
1541
+ constraints: {
1542
+ minLength: fieldDef.min_length,
1543
+ maxLength: fieldDef.max_length,
1544
+ min: fieldDef.min,
1545
+ max: fieldDef.max
1546
+ },
1547
+ ui: {
1548
+ label: fieldDef.ui_label,
1549
+ type: fieldDef.ui_type,
1550
+ importance: fieldDef.ui_importance,
1551
+ group: fieldDef.ui_group,
1552
+ sortable: fieldDef.ui_sortable,
1553
+ filterable: fieldDef.ui_filterable,
1554
+ visible: fieldDef.ui_visible
1555
+ }
1556
+ };
1557
+ fields.set(name, field);
1558
+ }
1559
+ }
1560
+ const queries = definition.queries?.filter((q) => "by" in q).map((q) => ({
1561
+ by: q.by,
1562
+ unique: q.unique,
1563
+ select: q.select,
1564
+ order: q.order,
1565
+ limit: q.limit
1566
+ }));
1567
+ return {
1568
+ name: config.name,
1569
+ table,
1570
+ from: config.from,
1571
+ to: config.to,
1572
+ selfReferential: config.from === config.to,
1573
+ fromColumn,
1574
+ toColumn,
1575
+ types,
1576
+ hasTypes: types.length > 0,
1577
+ temporal: config.temporal,
1578
+ sourced: config.sourced,
1579
+ onDeleteFrom: config.on_delete_from ?? "restrict",
1580
+ onDeleteTo: config.on_delete_to ?? "restrict",
1581
+ uniqueOn,
1582
+ fields,
1583
+ queries,
1584
+ sourcePath: filePath
1585
+ };
1586
+ }
1587
+ function resolveTypeDirections(types) {
1588
+ if (!types) return [];
1589
+ if (Array.isArray(types)) {
1590
+ return types.map((name) => ({
1591
+ name,
1592
+ bidirectional: false,
1593
+ directed: true
1594
+ }));
1595
+ }
1596
+ return Object.entries(types).map(([name, dir]) => {
1597
+ const direction = dir;
1598
+ return {
1599
+ name,
1600
+ inverse: direction.inverse,
1601
+ bidirectional: direction.bidirectional ?? false,
1602
+ directed: direction.directed ?? (!direction.bidirectional && !direction.inverse)
1603
+ };
1604
+ });
1605
+ }
1606
+ function loadRelationships(relationshipsDir) {
1607
+ const relationships = [];
1608
+ const issues = [];
1609
+ const resolvedDir = resolve2(relationshipsDir);
1610
+ let files;
1611
+ try {
1612
+ files = findYamlFiles(resolvedDir);
1613
+ } catch {
1614
+ return { relationships, issues };
1615
+ }
1616
+ if (files.length === 0) {
1617
+ return { relationships, issues };
1618
+ }
1619
+ for (const filePath of files) {
1620
+ const result = loadRelationshipFromYaml(filePath);
1621
+ if (result.success) {
1622
+ relationships.push(transformToRelationshipDefinition(result));
1623
+ } else {
1624
+ issues.push(...loadErrorToIssue(result));
1625
+ }
1626
+ }
1627
+ return { relationships, issues };
1628
+ }
1629
+ function resolveRelationshipReferences(relationshipDefs, entities) {
1630
+ const issues = [];
1631
+ const entityNames = new Set(entities.map((e) => e.name));
1632
+ for (const relDef of relationshipDefs) {
1633
+ if (!entityNames.has(relDef.from)) {
1634
+ issues.push({
1635
+ severity: "warning",
1636
+ type: "missing_relationship_endpoint",
1637
+ entity: relDef.name,
1638
+ message: `Relationship '${relDef.name}' references unknown 'from' entity '${relDef.from}'`,
1639
+ path: relDef.sourcePath,
1640
+ suggestion: `Define entity '${relDef.from}' or fix the 'from' value`
1641
+ });
1642
+ }
1643
+ if (!entityNames.has(relDef.to)) {
1644
+ issues.push({
1645
+ severity: "warning",
1646
+ type: "missing_relationship_endpoint",
1647
+ entity: relDef.name,
1648
+ message: `Relationship '${relDef.name}' references unknown 'to' entity '${relDef.to}'`,
1649
+ path: relDef.sourcePath,
1650
+ suggestion: `Define entity '${relDef.to}' or fix the 'to' value`
1651
+ });
1652
+ }
1653
+ const dupes = relationshipDefs.filter((r) => r.name === relDef.name);
1654
+ if (dupes.length > 1) {
1655
+ issues.push({
1656
+ severity: "error",
1657
+ type: "duplicate_relationship",
1658
+ entity: relDef.name,
1659
+ message: `Duplicate relationship name: ${relDef.name}`,
1660
+ path: relDef.sourcePath
1661
+ });
1662
+ }
1663
+ }
1664
+ return issues;
1665
+ }
1666
+
1667
+ // src/parser/validate-providers.ts
1668
+ import { existsSync as existsSync2, readFileSync as readFileSync2, statSync } from "fs";
1669
+ import { isAbsolute, join as join2, resolve as resolve3 } from "path";
1670
+ import ts from "typescript";
1671
+ function collectEntitySurfaces(entities) {
1672
+ const surfaces = /* @__PURE__ */ new Set();
1673
+ for (const e of entities) {
1674
+ if (e.entity.surface) surfaces.add(e.entity.surface);
1675
+ }
1676
+ return surfaces;
1677
+ }
1678
+ function resolveImportRef(ref, opts) {
1679
+ const { path: path2, exportName } = parseImportRef(ref);
1680
+ const file = resolveModuleFile(path2, opts);
1681
+ if (!file) {
1682
+ return { status: "module-not-found", resolvedFrom: opts.sourceRoot };
1683
+ }
1684
+ const content = readFileSync2(file, "utf-8");
1685
+ const { names, hasWildcard } = collectExportedNames(file, content);
1686
+ if (names.has(exportName) || hasWildcard) {
1687
+ return { status: "ok", file };
1688
+ }
1689
+ return { status: "export-not-found", file };
1690
+ }
1691
+ function resolveModuleFile(importPath, opts) {
1692
+ let base = null;
1693
+ for (const [alias, target] of Object.entries(opts.aliases ?? {})) {
1694
+ if (importPath === alias || importPath.startsWith(`${alias}/`)) {
1695
+ const rest = importPath.slice(alias.length);
1696
+ base = join2(target, rest);
1697
+ break;
1698
+ }
1699
+ }
1700
+ if (base === null) {
1701
+ base = isAbsolute(importPath) ? importPath : resolve3(opts.sourceRoot, importPath);
1702
+ }
1703
+ const candidates = [
1704
+ base,
1705
+ `${base}.ts`,
1706
+ `${base}.tsx`,
1707
+ join2(base, "index.ts"),
1708
+ join2(base, "index.tsx")
1709
+ ];
1710
+ for (const c of candidates) {
1711
+ if (existsSync2(c) && statSync(c).isFile()) return c;
1712
+ }
1713
+ return null;
1714
+ }
1715
+ function collectExportedNames(fileName, content) {
1716
+ const sf = ts.createSourceFile(
1717
+ fileName,
1718
+ content,
1719
+ ts.ScriptTarget.Latest,
1720
+ /* setParentNodes */
1721
+ true
1722
+ );
1723
+ const names = /* @__PURE__ */ new Set();
1724
+ let hasWildcard = false;
1725
+ const hasExportModifier = (node) => ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false);
1726
+ sf.forEachChild((node) => {
1727
+ if (hasExportModifier(node)) {
1728
+ if ((ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) && node.name && ts.isIdentifier(node.name)) {
1729
+ names.add(node.name.text);
1730
+ } else if (ts.isVariableStatement(node)) {
1731
+ for (const decl of node.declarationList.declarations) {
1732
+ if (ts.isIdentifier(decl.name)) names.add(decl.name.text);
1733
+ }
1734
+ }
1735
+ }
1736
+ if (ts.isExportDeclaration(node)) {
1737
+ if (!node.exportClause) {
1738
+ hasWildcard = true;
1739
+ } else if (ts.isNamedExports(node.exportClause)) {
1740
+ for (const el of node.exportClause.elements) names.add(el.name.text);
1741
+ } else if (ts.isNamespaceExport(node.exportClause)) {
1742
+ names.add(node.exportClause.name.text);
1743
+ }
1744
+ }
1745
+ });
1746
+ return { names, hasWildcard };
1747
+ }
1748
+ function validateProviders(providers, opts) {
1749
+ const issues = [];
1750
+ const knownSurfaces = new Set(opts.entitySurfaces);
1751
+ const slugToFiles = /* @__PURE__ */ new Map();
1752
+ for (const p of providers) {
1753
+ const files = slugToFiles.get(p.definition.slug) ?? [];
1754
+ files.push(p.filePath);
1755
+ slugToFiles.set(p.definition.slug, files);
1756
+ }
1757
+ for (const [slug, files] of slugToFiles) {
1758
+ if (files.length > 1) {
1759
+ for (const file of files) {
1760
+ const others = files.filter((f) => f !== file);
1761
+ issues.push({
1762
+ severity: "error",
1763
+ type: "provider_duplicate_slug",
1764
+ message: `provider slug '${slug}' is declared more than once (also in: ${others.join(", ")})`,
1765
+ path: file
1766
+ });
1767
+ }
1768
+ }
1769
+ }
1770
+ for (const { definition, filePath } of providers) {
1771
+ const { slug } = definition;
1772
+ for (const surface of definition.surfaces) {
1773
+ if (!knownSurfaces.has(surface)) {
1774
+ const known = [...knownSurfaces].sort().join(", ") || "(none declared)";
1775
+ issues.push({
1776
+ severity: "error",
1777
+ type: "provider_unknown_surface",
1778
+ message: `provider ${slug}: surface '${surface}' is not declared by any entity (known surfaces: ${known})`,
1779
+ path: filePath
1780
+ });
1781
+ }
1782
+ }
1783
+ if (!opts.skipImportCheck) {
1784
+ if (!opts.sourceRoot) {
1785
+ throw new Error(
1786
+ "validateProviders: sourceRoot is required for the import pre-flight check (or set skipImportCheck: true)"
1787
+ );
1788
+ }
1789
+ const resolveOpts = {
1790
+ sourceRoot: opts.sourceRoot,
1791
+ aliases: opts.aliases
1792
+ };
1793
+ const refs = [
1794
+ { field: "auth.strategy", ref: definition.auth.strategy },
1795
+ { field: "client.class", ref: definition.client.class }
1796
+ ];
1797
+ for (const { field, ref } of refs) {
1798
+ const result = resolveImportRef(ref, resolveOpts);
1799
+ if (result.status === "module-not-found") {
1800
+ issues.push({
1801
+ severity: "error",
1802
+ type: "provider_import_unresolved",
1803
+ message: `provider ${slug}: ${field} '${ref}' not found \u2014 module could not be resolved from ${result.resolvedFrom}`,
1804
+ path: filePath
1805
+ });
1806
+ } else if (result.status === "export-not-found") {
1807
+ const { exportName } = parseImportRef(ref);
1808
+ issues.push({
1809
+ severity: "error",
1810
+ type: "provider_import_unresolved",
1811
+ message: `provider ${slug}: ${field} '${ref}' not found \u2014 export '${exportName}' is missing from ${result.file}`,
1812
+ path: filePath
1813
+ });
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1818
+ return issues;
1819
+ }
1820
+
1821
+ // src/analyzer/graph-builder.ts
1822
+ function inferCardinality(type) {
1823
+ switch (type) {
1824
+ case "belongs_to":
1825
+ return "N:1";
1826
+ case "has_many":
1827
+ return "1:N";
1828
+ case "has_one":
1829
+ return "1:1";
1830
+ default:
1831
+ return "1:N";
1832
+ }
1833
+ }
1834
+ function hasReverseEdge(edges, from, to) {
1835
+ return edges.find((e) => e.from === to && e.to === from);
1836
+ }
1837
+ function buildDomainGraph(entities, relationshipDefinitions = []) {
1838
+ const entityMap = /* @__PURE__ */ new Map();
1839
+ const relDefMap = /* @__PURE__ */ new Map();
1840
+ const edges = [];
1841
+ for (const entity of entities) {
1842
+ entityMap.set(entity.name, entity);
1843
+ }
1844
+ for (const relDef of relationshipDefinitions) {
1845
+ relDefMap.set(relDef.name, relDef);
1846
+ }
1847
+ for (const entity of entities) {
1848
+ for (const [relName, rel] of entity.relationships) {
1849
+ if (!rel.resolved) continue;
1850
+ const reverseEdge = hasReverseEdge(edges, entity.name, rel.target);
1851
+ const edge = {
1852
+ from: entity.name,
1853
+ to: rel.target,
1854
+ relationship: rel,
1855
+ cardinality: inferCardinality(rel.type),
1856
+ bidirectional: reverseEdge !== void 0
1857
+ };
1858
+ if (reverseEdge) {
1859
+ reverseEdge.bidirectional = true;
1860
+ }
1861
+ edges.push(edge);
1862
+ }
1863
+ }
1864
+ for (const relDef of relationshipDefinitions) {
1865
+ const fromExists = entityMap.has(relDef.from);
1866
+ const toExists = entityMap.has(relDef.to);
1867
+ if (fromExists && toExists) {
1868
+ const edge = {
1869
+ from: relDef.from,
1870
+ to: relDef.to,
1871
+ relationship: {
1872
+ name: relDef.name,
1873
+ type: "has_many",
1874
+ target: relDef.to,
1875
+ foreignKey: relDef.fromColumn,
1876
+ resolved: true
1877
+ },
1878
+ cardinality: "N:M",
1879
+ bidirectional: relDef.types.some((t) => t.bidirectional)
1880
+ };
1881
+ edges.push(edge);
1882
+ }
1883
+ }
1884
+ return { entities: entityMap, relationshipDefinitions: relDefMap, edges };
1885
+ }
1886
+ function getRelatedEntities(graph, entityName, depth = 1) {
1887
+ const related = /* @__PURE__ */ new Set();
1888
+ const visited = /* @__PURE__ */ new Set();
1889
+ const queue = [
1890
+ { name: entityName, currentDepth: 0 }
1891
+ ];
1892
+ while (queue.length > 0) {
1893
+ const item = queue.shift();
1894
+ if (!item) continue;
1895
+ const { name, currentDepth } = item;
1896
+ if (visited.has(name) || currentDepth > depth) continue;
1897
+ visited.add(name);
1898
+ for (const edge of graph.edges) {
1899
+ if (edge.from === name && !visited.has(edge.to)) {
1900
+ related.add(edge.to);
1901
+ queue.push({ name: edge.to, currentDepth: currentDepth + 1 });
1902
+ }
1903
+ if (edge.to === name && !visited.has(edge.from)) {
1904
+ related.add(edge.from);
1905
+ queue.push({ name: edge.from, currentDepth: currentDepth + 1 });
1906
+ }
1907
+ }
1908
+ }
1909
+ return related;
1910
+ }
1911
+ function findOrphanEntities(graph) {
1912
+ const orphans = [];
1913
+ for (const [name] of graph.entities) {
1914
+ const hasRelationship = graph.edges.some((e) => e.from === name || e.to === name);
1915
+ if (!hasRelationship) {
1916
+ orphans.push(name);
1917
+ }
1918
+ }
1919
+ return orphans;
1920
+ }
1921
+ function findCircularDependencies(graph) {
1922
+ const cycles = [];
1923
+ const visited = /* @__PURE__ */ new Set();
1924
+ const recursionStack = /* @__PURE__ */ new Set();
1925
+ function dfs(node, path2) {
1926
+ visited.add(node);
1927
+ recursionStack.add(node);
1928
+ const outgoingEdges = graph.edges.filter((e) => e.from === node);
1929
+ for (const edge of outgoingEdges) {
1930
+ if (!visited.has(edge.to)) {
1931
+ dfs(edge.to, [...path2, edge.to]);
1932
+ } else if (recursionStack.has(edge.to)) {
1933
+ const cycleStart = path2.indexOf(edge.to);
1934
+ if (cycleStart !== -1) {
1935
+ cycles.push([...path2.slice(cycleStart), edge.to]);
1936
+ } else {
1937
+ cycles.push([...path2, edge.to]);
1938
+ }
1939
+ }
1940
+ }
1941
+ recursionStack.delete(node);
1942
+ }
1943
+ for (const [name] of graph.entities) {
1944
+ if (!visited.has(name)) {
1945
+ dfs(name, [name]);
1946
+ }
1947
+ }
1948
+ const uniqueCycles = [];
1949
+ const seen = /* @__PURE__ */ new Set();
1950
+ for (const cycle of cycles) {
1951
+ const minIndex = cycle.indexOf(
1952
+ cycle.reduce((min, val) => val < min ? val : min, cycle[0])
1953
+ );
1954
+ const normalized = [...cycle.slice(minIndex), ...cycle.slice(0, minIndex)];
1955
+ const key = normalized.join("->");
1956
+ if (!seen.has(key)) {
1957
+ seen.add(key);
1958
+ uniqueCycles.push(cycle);
1959
+ }
1960
+ }
1961
+ return uniqueCycles;
1962
+ }
1963
+
1964
+ // src/behaviors/external-id-tracking.ts
1965
+ var externalIdTrackingBehavior = {
1966
+ name: "external_id_tracking",
1967
+ description: "Adds external_id, provider, and provider_metadata fields for external system sync tracking",
1968
+ fields: [
1969
+ {
1970
+ name: "external_id",
1971
+ camelName: "externalId",
1972
+ type: "string",
1973
+ tsType: "string | null",
1974
+ drizzleType: "varchar",
1975
+ drizzleImports: ["varchar", "index"],
1976
+ zodType: "z.string().nullable()",
1977
+ nullable: true,
1978
+ ui: {
1979
+ label: "External ID",
1980
+ type: "text",
1981
+ importance: "tertiary",
1982
+ group: "metadata",
1983
+ visible: false
1984
+ }
1985
+ },
1986
+ {
1987
+ name: "provider",
1988
+ camelName: "provider",
1989
+ type: "string",
1990
+ tsType: "string | null",
1991
+ drizzleType: "varchar",
1992
+ drizzleImports: ["varchar"],
1993
+ zodType: "z.string().nullable()",
1994
+ nullable: true,
1995
+ ui: {
1996
+ label: "Provider",
1997
+ type: "text",
1998
+ importance: "tertiary",
1999
+ group: "metadata",
2000
+ visible: false
2001
+ }
2002
+ },
2003
+ {
2004
+ name: "provider_metadata",
2005
+ camelName: "providerMetadata",
2006
+ type: "json",
2007
+ tsType: "unknown | null",
2008
+ drizzleType: "jsonb",
2009
+ drizzleImports: ["jsonb"],
2010
+ zodType: "z.unknown().nullable()",
2011
+ nullable: true,
2012
+ ui: {
2013
+ label: "Provider Metadata",
2014
+ type: "json",
2015
+ importance: "tertiary",
2016
+ group: "metadata",
2017
+ visible: false
2018
+ }
2019
+ }
2020
+ ],
2021
+ drizzleImports: ["varchar", "jsonb", "index"],
2022
+ configKey: "externalIdTracking"
2023
+ };
2024
+
2025
+ // src/behaviors/soft-delete.ts
2026
+ var softDeleteBehavior = {
2027
+ name: "soft_delete",
2028
+ description: "Adds deleted_at field for soft delete functionality",
2029
+ fields: [
2030
+ {
2031
+ name: "deleted_at",
2032
+ camelName: "deletedAt",
2033
+ type: "datetime",
2034
+ tsType: "Date | null",
2035
+ drizzleType: "timestamp",
2036
+ drizzleImports: ["timestamp"],
2037
+ zodType: "z.coerce.date().nullable()",
2038
+ nullable: true,
2039
+ ui: {
2040
+ label: "Deleted At",
2041
+ type: "datetime",
2042
+ importance: "tertiary",
2043
+ group: "metadata",
2044
+ visible: false
2045
+ }
2046
+ }
2047
+ ],
2048
+ drizzleImports: ["timestamp"],
2049
+ methods: [
2050
+ "softDelete",
2051
+ "restore",
2052
+ "findWithDeleted",
2053
+ "findOnlyDeleted",
2054
+ "baseQuery"
2055
+ // Modified to filter deleted records
2056
+ ],
2057
+ configKey: "softDelete"
2058
+ };
2059
+
2060
+ // src/behaviors/timestamps.ts
2061
+ var timestampsBehavior = {
2062
+ name: "timestamps",
2063
+ description: "Adds created_at and updated_at timestamp fields",
2064
+ fields: [
2065
+ {
2066
+ name: "created_at",
2067
+ camelName: "createdAt",
2068
+ type: "datetime",
2069
+ tsType: "Date",
2070
+ drizzleType: "timestamp",
2071
+ drizzleImports: ["timestamp"],
2072
+ zodType: "z.coerce.date()",
2073
+ nullable: false,
2074
+ default: "now()",
2075
+ ui: {
2076
+ label: "Created At",
2077
+ type: "datetime",
2078
+ importance: "tertiary",
2079
+ group: "metadata",
2080
+ visible: false
2081
+ }
2082
+ },
2083
+ {
2084
+ name: "updated_at",
2085
+ camelName: "updatedAt",
2086
+ type: "datetime",
2087
+ tsType: "Date",
2088
+ drizzleType: "timestamp",
2089
+ drizzleImports: ["timestamp"],
2090
+ zodType: "z.coerce.date()",
2091
+ nullable: false,
2092
+ default: "now()",
2093
+ ui: {
2094
+ label: "Updated At",
2095
+ type: "datetime",
2096
+ importance: "tertiary",
2097
+ group: "metadata",
2098
+ visible: false
2099
+ }
2100
+ }
2101
+ ],
2102
+ drizzleImports: ["timestamp"],
2103
+ methods: ["applyTimestampsOnCreate", "applyTimestampsOnUpdate"],
2104
+ configKey: "timestamps"
2105
+ };
2106
+
2107
+ // src/behaviors/user-tracking.ts
2108
+ var userTrackingBehavior = {
2109
+ name: "user_tracking",
2110
+ description: "Adds created_by and updated_by user reference fields",
2111
+ fields: [
2112
+ {
2113
+ name: "created_by",
2114
+ camelName: "createdBy",
2115
+ type: "uuid",
2116
+ tsType: "string | null",
2117
+ drizzleType: "uuid",
2118
+ drizzleImports: ["uuid"],
2119
+ zodType: "z.string().uuid().nullable()",
2120
+ nullable: true,
2121
+ foreignKey: "users.id",
2122
+ ui: {
2123
+ label: "Created By",
2124
+ type: "reference",
2125
+ importance: "tertiary",
2126
+ group: "metadata",
2127
+ visible: false
2128
+ }
2129
+ },
2130
+ {
2131
+ name: "updated_by",
2132
+ camelName: "updatedBy",
2133
+ type: "uuid",
2134
+ tsType: "string | null",
2135
+ drizzleType: "uuid",
2136
+ drizzleImports: ["uuid"],
2137
+ zodType: "z.string().uuid().nullable()",
2138
+ nullable: true,
2139
+ foreignKey: "users.id",
2140
+ ui: {
2141
+ label: "Updated By",
2142
+ type: "reference",
2143
+ importance: "tertiary",
2144
+ group: "metadata",
2145
+ visible: false
2146
+ }
2147
+ }
2148
+ ],
2149
+ drizzleImports: ["uuid"],
2150
+ methods: ["applyUserTrackingOnCreate", "applyUserTrackingOnUpdate"],
2151
+ configKey: "userTracking"
2152
+ };
2153
+
2154
+ // src/behaviors/index.ts
2155
+ var behaviorRegistry = /* @__PURE__ */ new Map([
2156
+ ["timestamps", timestampsBehavior],
2157
+ ["soft_delete", softDeleteBehavior],
2158
+ ["user_tracking", userTrackingBehavior],
2159
+ ["external_id_tracking", externalIdTrackingBehavior]
2160
+ ]);
2161
+ function getBehavior(name) {
2162
+ return behaviorRegistry.get(name);
2163
+ }
2164
+ function normalizeBehaviorConfig(config) {
2165
+ if (typeof config === "string") {
2166
+ return { name: config, options: {} };
2167
+ }
2168
+ return { name: config.name, options: config.options ?? {} };
2169
+ }
2170
+ function normalizeBehaviorConfigs(configs) {
2171
+ return configs.map(normalizeBehaviorConfig);
2172
+ }
2173
+ function resolveBehaviorFields(configs) {
2174
+ const normalized = normalizeBehaviorConfigs(configs);
2175
+ const fields = [];
2176
+ const addedFieldNames = /* @__PURE__ */ new Set();
2177
+ for (const config of normalized) {
2178
+ const behavior = getBehavior(config.name);
2179
+ if (!behavior) continue;
2180
+ for (const field of behavior.fields) {
2181
+ if (!addedFieldNames.has(field.name)) {
2182
+ fields.push(field);
2183
+ addedFieldNames.add(field.name);
2184
+ }
2185
+ }
2186
+ }
2187
+ return fields;
2188
+ }
2189
+
2190
+ // src/analyzer/consistency-checker.ts
2191
+ function checkConsistency(graph) {
2192
+ const issues = [];
2193
+ for (const [name, entity] of graph.entities) {
2194
+ issues.push(...checkEntityConsistency(entity));
2195
+ issues.push(...checkRelationshipConsistency(entity, graph));
2196
+ issues.push(...checkNamingConventions(entity));
2197
+ issues.push(...checkMissingIndexes(entity));
2198
+ issues.push(...checkUiMetadata(entity));
2199
+ if (entity.queries !== void 0) {
2200
+ issues.push(...checkQueryFieldReferences(entity));
2201
+ }
2202
+ if (entity.integration !== void 0) {
2203
+ issues.push(...checkIntegrationFieldMappingReferences(entity));
2204
+ issues.push(...checkExternalIdTrackingCollision(entity));
2205
+ }
2206
+ }
2207
+ issues.push(...checkOrphanEntities(graph));
2208
+ issues.push(...checkCircularReferences(graph));
2209
+ issues.push(...checkMissingInverses(graph));
2210
+ return issues;
2211
+ }
2212
+ function checkEntityConsistency(entity) {
2213
+ const issues = [];
2214
+ if (!entity.fields.has("id")) {
2215
+ issues.push({
2216
+ severity: "info",
2217
+ type: "missing_id",
2218
+ entity: entity.name,
2219
+ message: 'Entity missing standard "id" field',
2220
+ suggestion: 'Add an "id" field with type "uuid"'
2221
+ });
2222
+ }
2223
+ const hasCreatedAt = entity.fields.has("created_at");
2224
+ const hasTimestampsBehavior = entity.behaviors.includes("timestamps");
2225
+ if (!hasCreatedAt && !hasTimestampsBehavior) {
2226
+ issues.push({
2227
+ severity: "info",
2228
+ type: "missing_timestamps",
2229
+ entity: entity.name,
2230
+ message: 'Entity missing "created_at" field and "timestamps" behavior',
2231
+ suggestion: 'Add "timestamps" to behaviors or add created_at/updated_at fields'
2232
+ });
2233
+ }
2234
+ return issues;
2235
+ }
2236
+ function checkRelationshipConsistency(entity, graph) {
2237
+ const issues = [];
2238
+ for (const [relName, rel] of entity.relationships) {
2239
+ if (rel.type === "belongs_to") {
2240
+ const fkField = entity.fields.get(rel.foreignKey);
2241
+ if (!fkField) {
2242
+ issues.push({
2243
+ severity: "warning",
2244
+ type: "missing_fk_field",
2245
+ entity: entity.name,
2246
+ field: relName,
2247
+ message: `Relationship "${relName}" references foreign key "${rel.foreignKey}" but field doesn't exist`,
2248
+ suggestion: `Add field "${rel.foreignKey}" with foreign_key reference`
2249
+ });
2250
+ }
2251
+ }
2252
+ if (rel.type === "has_many" || rel.type === "has_one") {
2253
+ const targetEntity = graph.entities.get(rel.target);
2254
+ if (targetEntity) {
2255
+ const targetFkField = targetEntity.fields.get(rel.foreignKey);
2256
+ if (!targetFkField) {
2257
+ issues.push({
2258
+ severity: "warning",
2259
+ type: "missing_target_fk",
2260
+ entity: entity.name,
2261
+ field: relName,
2262
+ message: `Relationship "${relName}" expects foreign key "${rel.foreignKey}" on "${rel.target}" but field doesn't exist`,
2263
+ suggestion: `Add field "${rel.foreignKey}" to "${rel.target}" entity`
2264
+ });
2265
+ }
2266
+ }
2267
+ }
2268
+ }
2269
+ return issues;
2270
+ }
2271
+ function checkNamingConventions(entity) {
2272
+ const issues = [];
2273
+ if (entity.name !== entity.name.toLowerCase()) {
2274
+ issues.push({
2275
+ severity: "warning",
2276
+ type: "naming_convention",
2277
+ entity: entity.name,
2278
+ message: "Entity name should be lowercase",
2279
+ suggestion: `Use "${entity.name.toLowerCase()}"`
2280
+ });
2281
+ }
2282
+ for (const [fieldName] of entity.fields) {
2283
+ if (fieldName !== fieldName.toLowerCase()) {
2284
+ issues.push({
2285
+ severity: "warning",
2286
+ type: "naming_convention",
2287
+ entity: entity.name,
2288
+ field: fieldName,
2289
+ message: "Field name should be snake_case",
2290
+ suggestion: `Use "${toSnakeCase(fieldName)}"`
2291
+ });
2292
+ }
2293
+ }
2294
+ for (const [relName] of entity.relationships) {
2295
+ if (relName !== relName.toLowerCase()) {
2296
+ issues.push({
2297
+ severity: "warning",
2298
+ type: "naming_convention",
2299
+ entity: entity.name,
2300
+ field: relName,
2301
+ message: "Relationship name should be snake_case",
2302
+ suggestion: `Use "${toSnakeCase(relName)}"`
2303
+ });
2304
+ }
2305
+ }
2306
+ return issues;
2307
+ }
2308
+ function checkMissingIndexes(entity) {
2309
+ const issues = [];
2310
+ for (const [fieldName, field] of entity.fields) {
2311
+ if (field.ui.filterable && !field.index && !field.unique) {
2312
+ issues.push({
2313
+ severity: "warning",
2314
+ type: "missing_index",
2315
+ entity: entity.name,
2316
+ field: fieldName,
2317
+ message: `Field "${fieldName}" is filterable but has no index`,
2318
+ suggestion: 'Add "index: true" to improve query performance'
2319
+ });
2320
+ }
2321
+ if (field.foreignKey && !field.index && !field.unique) {
2322
+ issues.push({
2323
+ severity: "info",
2324
+ type: "missing_fk_index",
2325
+ entity: entity.name,
2326
+ field: fieldName,
2327
+ message: `Foreign key field "${fieldName}" has no index`,
2328
+ suggestion: 'Add "index: true" for better join performance'
2329
+ });
2330
+ }
2331
+ }
2332
+ return issues;
2333
+ }
2334
+ function checkUiMetadata(entity) {
2335
+ const issues = [];
2336
+ const systemFields = /* @__PURE__ */ new Set(["id", "created_at", "updated_at", "deleted_at", "tenant_id"]);
2337
+ for (const [fieldName, field] of entity.fields) {
2338
+ if (systemFields.has(fieldName)) continue;
2339
+ const hasAnyUiMeta = field.ui.label !== void 0 || field.ui.type !== void 0 || field.ui.group !== void 0;
2340
+ if (!hasAnyUiMeta) {
2341
+ issues.push({
2342
+ severity: "info",
2343
+ type: "missing_ui_metadata",
2344
+ entity: entity.name,
2345
+ field: fieldName,
2346
+ message: `Field "${fieldName}" has no UI metadata`,
2347
+ suggestion: "Add ui_label, ui_type, ui_group for better admin panel display"
2348
+ });
2349
+ }
2350
+ }
2351
+ return issues;
2352
+ }
2353
+ function checkOrphanEntities(graph) {
2354
+ const orphans = findOrphanEntities(graph);
2355
+ return orphans.map((name) => ({
2356
+ severity: "info",
2357
+ type: "orphan_entity",
2358
+ entity: name,
2359
+ message: `Entity "${name}" has no relationships to other entities`,
2360
+ suggestion: "Consider if this entity should be related to others"
2361
+ }));
2362
+ }
2363
+ function checkCircularReferences(graph) {
2364
+ const cycles = findCircularDependencies(graph);
2365
+ return cycles.map((cycle) => ({
2366
+ severity: "info",
2367
+ type: "circular_dependency",
2368
+ entity: cycle[0],
2369
+ message: `Circular reference detected: ${cycle.join(" -> ")}`,
2370
+ suggestion: "Verify this is intentional (e.g., self-referential hierarchy)"
2371
+ }));
2372
+ }
2373
+ function checkMissingInverses(graph) {
2374
+ const issues = [];
2375
+ for (const edge of graph.edges) {
2376
+ const { from, to, relationship } = edge;
2377
+ const targetEntity = graph.entities.get(to);
2378
+ if (!targetEntity) continue;
2379
+ const hasInverse = Array.from(targetEntity.relationships.values()).some(
2380
+ (rel) => rel.target === from
2381
+ );
2382
+ if (!hasInverse && relationship.type !== "belongs_to") {
2383
+ issues.push({
2384
+ severity: "info",
2385
+ type: "missing_inverse",
2386
+ entity: from,
2387
+ field: relationship.name,
2388
+ message: `Relationship "${relationship.name}" to "${to}" has no inverse defined on target`,
2389
+ suggestion: `Add inverse relationship on "${to}" pointing back to "${from}"`
2390
+ });
2391
+ }
2392
+ }
2393
+ return issues;
2394
+ }
2395
+ function getAvailableFieldNames(entity) {
2396
+ const entityFieldNames = Array.from(entity.fields.keys());
2397
+ const behaviorFields = resolveBehaviorFields(entity.behaviors);
2398
+ const behaviorFieldNames = behaviorFields.map((f) => f.name);
2399
+ const belongsToFkNames = [];
2400
+ for (const rel of entity.relationships.values()) {
2401
+ if (rel.type === "belongs_to" && rel.foreignKey) {
2402
+ belongsToFkNames.push(rel.foreignKey);
2403
+ }
2404
+ }
2405
+ return [.../* @__PURE__ */ new Set([...entityFieldNames, ...behaviorFieldNames, ...belongsToFkNames])];
2406
+ }
2407
+ function checkQueryFieldReferences(entity) {
2408
+ const issues = [];
2409
+ const availableFields = getAvailableFieldNames(entity);
2410
+ const availableSet = new Set(availableFields);
2411
+ for (const query of entity.queries ?? []) {
2412
+ if (!query.via) {
2413
+ for (const fieldName of query.by) {
2414
+ if (!availableSet.has(fieldName)) {
2415
+ issues.push({
2416
+ severity: "error",
2417
+ type: "unknown_query_field",
2418
+ entity: entity.name,
2419
+ field: fieldName,
2420
+ message: `Query references unknown field "${fieldName}" in entity "${entity.name}". Available fields: ${availableFields.join(", ")}`
2421
+ });
2422
+ }
2423
+ }
2424
+ }
2425
+ for (const fieldName of query.select ?? []) {
2426
+ if (!availableSet.has(fieldName)) {
2427
+ issues.push({
2428
+ severity: "error",
2429
+ type: "unknown_query_field",
2430
+ entity: entity.name,
2431
+ field: fieldName,
2432
+ message: `Query references unknown field "${fieldName}" in entity "${entity.name}". Available fields: ${availableFields.join(", ")}`
2433
+ });
2434
+ }
2435
+ }
2436
+ }
2437
+ return issues;
2438
+ }
2439
+ function checkIntegrationFieldMappingReferences(entity) {
2440
+ const issues = [];
2441
+ const availableFields = getAvailableFieldNames(entity);
2442
+ const availableSet = new Set(availableFields);
2443
+ for (const [providerName, provider] of Object.entries(entity.integration?.providers ?? {})) {
2444
+ for (const fieldName of Object.keys(provider.fieldMapping ?? {})) {
2445
+ if (!availableSet.has(fieldName)) {
2446
+ issues.push({
2447
+ severity: "warning",
2448
+ type: "unknown_integration_field_mapping",
2449
+ entity: entity.name,
2450
+ field: fieldName,
2451
+ message: `Integration field mapping references unknown field "${fieldName}" for provider "${providerName}" in entity "${entity.name}"`
2452
+ });
2453
+ }
2454
+ }
2455
+ for (const fieldName of provider.readOnlyFields ?? []) {
2456
+ if (!availableSet.has(fieldName)) {
2457
+ issues.push({
2458
+ severity: "warning",
2459
+ type: "unknown_integration_field_mapping",
2460
+ entity: entity.name,
2461
+ field: fieldName,
2462
+ message: `Integration field mapping references unknown field "${fieldName}" for provider "${providerName}" in entity "${entity.name}"`
2463
+ });
2464
+ }
2465
+ }
2466
+ }
2467
+ return issues;
2468
+ }
2469
+ function checkExternalIdTrackingCollision(entity) {
2470
+ const issues = [];
2471
+ const hasExternalIdTracking = entity.behaviors.includes("external_id_tracking");
2472
+ if (!hasExternalIdTracking) return issues;
2473
+ for (const [providerName, provider] of Object.entries(entity.integration?.providers ?? {})) {
2474
+ if (provider.fieldMapping && "external_id" in provider.fieldMapping) {
2475
+ issues.push({
2476
+ severity: "warning",
2477
+ type: "external_id_tracking_collision",
2478
+ entity: entity.name,
2479
+ field: "external_id",
2480
+ message: `Entity "${entity.name}" has external_id_tracking behavior and also maps "external_id" in integration field_mapping for provider "${providerName}". The behavior-added field may collide with the mapped field.`
2481
+ });
2482
+ }
2483
+ }
2484
+ return issues;
2485
+ }
2486
+ function toSnakeCase(str) {
2487
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
2488
+ }
2489
+
2490
+ // src/analyzer/statistics.ts
2491
+ function computeStatistics(graph) {
2492
+ const entities = Array.from(graph.entities.values());
2493
+ const fieldsByType = {};
2494
+ const relationshipsByType = {};
2495
+ let totalFields = 0;
2496
+ let totalRelationships = 0;
2497
+ let entitiesWithBehaviors = 0;
2498
+ for (const entity of entities) {
2499
+ totalFields += entity.fields.size;
2500
+ totalRelationships += entity.relationships.size;
2501
+ if (entity.behaviors.length > 0) {
2502
+ entitiesWithBehaviors++;
2503
+ }
2504
+ for (const field of entity.fields.values()) {
2505
+ fieldsByType[field.type] = (fieldsByType[field.type] ?? 0) + 1;
2506
+ }
2507
+ for (const rel of entity.relationships.values()) {
2508
+ relationshipsByType[rel.type] = (relationshipsByType[rel.type] ?? 0) + 1;
2509
+ }
2510
+ }
2511
+ return {
2512
+ totalEntities: entities.length,
2513
+ totalFields,
2514
+ totalRelationships,
2515
+ fieldsByType,
2516
+ relationshipsByType,
2517
+ entitiesWithBehaviors,
2518
+ averageFieldsPerEntity: entities.length > 0 ? totalFields / entities.length : 0
2519
+ };
2520
+ }
2521
+
2522
+ // src/analyzer/transitive-suggester.ts
2523
+ var DEFAULT_OPTIONS = {
2524
+ maxDepth: 3,
2525
+ excludeEntities: ["workspace", "tenant"],
2526
+ excludePatterns: [/_audit$/, /_log$/, /_history$/]
2527
+ };
2528
+ function suggestTransitiveRelationships(graph, options) {
2529
+ const opts = { ...DEFAULT_OPTIONS, ...options };
2530
+ const suggestions = [];
2531
+ for (const [entityName, entity] of graph.entities) {
2532
+ if (shouldExcludeEntity(entityName, opts)) continue;
2533
+ const paths = findTransitivePaths(graph, entityName, opts);
2534
+ for (const path2 of paths) {
2535
+ suggestions.push(createSuggestion(path2));
2536
+ }
2537
+ }
2538
+ return suggestions;
2539
+ }
2540
+ function shouldExcludeEntity(entityName, opts) {
2541
+ if (opts.excludeEntities.includes(entityName)) {
2542
+ return true;
2543
+ }
2544
+ for (const pattern of opts.excludePatterns) {
2545
+ if (pattern.test(entityName)) {
2546
+ return true;
2547
+ }
2548
+ }
2549
+ return false;
2550
+ }
2551
+ function findTransitivePaths(graph, sourceEntity, opts) {
2552
+ const paths = [];
2553
+ const sourceEntityData = graph.entities.get(sourceEntity);
2554
+ if (!sourceEntityData) return paths;
2555
+ const queue = [
2556
+ {
2557
+ entity: sourceEntity,
2558
+ depth: 0,
2559
+ path: [],
2560
+ visited: /* @__PURE__ */ new Set([sourceEntity])
2561
+ }
2562
+ ];
2563
+ while (queue.length > 0) {
2564
+ const current = queue.shift();
2565
+ if (!current) continue;
2566
+ const { entity, depth, path: path2, visited } = current;
2567
+ if (depth >= opts.maxDepth) continue;
2568
+ const currentEntity = graph.entities.get(entity);
2569
+ if (!currentEntity) continue;
2570
+ for (const [relName, rel] of currentEntity.relationships) {
2571
+ if (rel.through) continue;
2572
+ if (rel.type !== "has_many" && rel.type !== "has_one") continue;
2573
+ const target = rel.target;
2574
+ if (shouldExcludeEntity(target, opts)) continue;
2575
+ if (visited.has(target)) continue;
2576
+ const newPath = [
2577
+ ...path2,
2578
+ {
2579
+ via: entity,
2580
+ relationship: relName,
2581
+ foreignKey: rel.foreignKey
2582
+ }
2583
+ ];
2584
+ if (depth >= 1) {
2585
+ if (!hasDirectRelationship(sourceEntityData, target)) {
2586
+ const transitivePath = buildTransitivePath(
2587
+ sourceEntity,
2588
+ target,
2589
+ newPath
2590
+ );
2591
+ if (transitivePath) {
2592
+ paths.push(transitivePath);
2593
+ }
2594
+ }
2595
+ }
2596
+ if (depth + 1 < opts.maxDepth) {
2597
+ queue.push({
2598
+ entity: target,
2599
+ depth: depth + 1,
2600
+ path: newPath,
2601
+ visited: /* @__PURE__ */ new Set([...visited, target])
2602
+ });
2603
+ }
2604
+ }
2605
+ }
2606
+ return paths;
2607
+ }
2608
+ function hasDirectRelationship(sourceEntity, targetName) {
2609
+ for (const rel of sourceEntity.relationships.values()) {
2610
+ if (rel.target === targetName && !rel.through) {
2611
+ return true;
2612
+ }
2613
+ }
2614
+ return false;
2615
+ }
2616
+ function buildTransitivePath(source, target, hops) {
2617
+ if (hops.length === 0) return null;
2618
+ const throughPath = hops.map((hop) => hop.relationship).join(".");
2619
+ const suggestedName = generateSemanticName(source, target, hops);
2620
+ const yamlSnippet = generateYamlSnippet(suggestedName, target, throughPath);
2621
+ return {
2622
+ source,
2623
+ target,
2624
+ hops,
2625
+ suggestedName,
2626
+ throughPath,
2627
+ yamlSnippet
2628
+ };
2629
+ }
2630
+ function generateSemanticName(source, target, hops) {
2631
+ const firstHop = hops[0].relationship;
2632
+ if (hops.length === 2) {
2633
+ const prefix = firstHop.replace(/s$/, "");
2634
+ return `${prefix}_${target}`;
2635
+ }
2636
+ const parts = [firstHop.replace(/s$/, ""), target];
2637
+ return parts.join("_");
2638
+ }
2639
+ function generateYamlSnippet(name, target, throughPath) {
2640
+ return ` ${name}:
2641
+ type: has_many
2642
+ target: ${target}
2643
+ through: "${throughPath}"`;
2644
+ }
2645
+ function createSuggestion(path2) {
2646
+ const pathDescription = [path2.source, ...path2.hops.map((h) => h.via), path2.target].join(" -> ");
2647
+ return {
2648
+ severity: "info",
2649
+ type: "transitive_suggestion",
2650
+ entity: path2.source,
2651
+ message: `Potential transitive relationship: ${pathDescription}`,
2652
+ suggestion: `Add "${path2.suggestedName}" relationship via "${path2.throughPath}"`,
2653
+ path: path2
2654
+ };
2655
+ }
2656
+
2657
+ // src/analyzer/manifest.ts
2658
+ import { createHash } from "crypto";
2659
+ import { readFileSync as readFileSync3, writeFileSync, existsSync as existsSync3, mkdirSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
2660
+ import { join as join3 } from "path";
2661
+ var MANIFEST_FILE = "manifest.json";
2662
+ var MANIFEST_VERSION = 1;
2663
+ function getManifestDir() {
2664
+ return process.env.CODEGEN_MANIFEST_DIR || ".codegen";
2665
+ }
2666
+ function getManifestPaths(projectRoot) {
2667
+ const dir = join3(projectRoot, getManifestDir());
2668
+ const file = join3(dir, MANIFEST_FILE);
2669
+ return { dir, file };
2670
+ }
2671
+ async function computeEntityFilesHash(entitiesDir) {
2672
+ if (!existsSync3(entitiesDir)) {
2673
+ return createHash("sha256").update("").digest("hex");
2674
+ }
2675
+ const yamlFiles = [];
2676
+ function walkDir(dir) {
2677
+ const entries = readdirSync2(dir);
2678
+ for (const entry of entries) {
2679
+ const fullPath = join3(dir, entry);
2680
+ const stat = statSync2(fullPath);
2681
+ if (stat.isDirectory()) {
2682
+ walkDir(fullPath);
2683
+ } else if (stat.isFile() && (entry.endsWith(".yaml") || entry.endsWith(".yml"))) {
2684
+ yamlFiles.push(fullPath);
2685
+ }
2686
+ }
2687
+ }
2688
+ walkDir(entitiesDir);
2689
+ yamlFiles.sort();
2690
+ const hash = createHash("sha256");
2691
+ for (const file of yamlFiles) {
2692
+ const content = readFileSync3(file, "utf-8");
2693
+ hash.update(file);
2694
+ hash.update(content);
2695
+ }
2696
+ return hash.digest("hex");
2697
+ }
2698
+ function readManifest(projectRoot) {
2699
+ const { file } = getManifestPaths(projectRoot);
2700
+ if (!existsSync3(file)) {
2701
+ return null;
2702
+ }
2703
+ try {
2704
+ const content = readFileSync3(file, "utf-8");
2705
+ const manifest = JSON.parse(content);
2706
+ if (manifest.version !== MANIFEST_VERSION) {
2707
+ return null;
2708
+ }
2709
+ return manifest;
2710
+ } catch (error) {
2711
+ return null;
2712
+ }
2713
+ }
2714
+ function writeManifest(projectRoot, manifest) {
2715
+ const { dir, file } = getManifestPaths(projectRoot);
2716
+ if (!existsSync3(dir)) {
2717
+ mkdirSync(dir, { recursive: true });
2718
+ }
2719
+ const content = JSON.stringify(manifest, null, 2);
2720
+ writeFileSync(file, content, "utf-8");
2721
+ }
2722
+ async function isManifestStale(projectRoot, entitiesDir) {
2723
+ const manifest = readManifest(projectRoot);
2724
+ if (!manifest) {
2725
+ return true;
2726
+ }
2727
+ const currentHash = await computeEntityFilesHash(entitiesDir);
2728
+ return manifest.entityFilesHash !== currentHash;
2729
+ }
2730
+ function toManifestEntity(entity) {
2731
+ const fields = {};
2732
+ for (const [name, field] of entity.fields) {
2733
+ fields[name] = {
2734
+ name: field.name,
2735
+ type: field.type,
2736
+ required: field.required,
2737
+ nullable: field.nullable,
2738
+ unique: field.unique,
2739
+ index: field.index,
2740
+ foreignKey: field.foreignKey,
2741
+ choices: field.choices
2742
+ };
2743
+ }
2744
+ const relationships = {};
2745
+ for (const [name, rel] of entity.relationships) {
2746
+ relationships[name] = {
2747
+ type: rel.type,
2748
+ target: rel.target,
2749
+ foreignKey: rel.foreignKey,
2750
+ through: rel.through,
2751
+ inverse: rel.inverse
2752
+ };
2753
+ }
2754
+ return {
2755
+ sourcePath: entity.sourcePath,
2756
+ table: entity.table,
2757
+ plural: entity.plural,
2758
+ fields,
2759
+ relationships,
2760
+ behaviors: entity.behaviors
2761
+ };
2762
+ }
2763
+ function mergeSuggestions(newSuggestions, existingManifest) {
2764
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2765
+ const existingSuggestions = existingManifest?.suggestions.transitive || [];
2766
+ const existingMap = /* @__PURE__ */ new Map();
2767
+ for (const existing of existingSuggestions) {
2768
+ existingMap.set(existing.id, existing);
2769
+ }
2770
+ const merged = [];
2771
+ for (const suggestion of newSuggestions) {
2772
+ const id = `${suggestion.path.source}->${suggestion.path.target}`;
2773
+ const existing = existingMap.get(id);
2774
+ if (existing) {
2775
+ merged.push({
2776
+ id,
2777
+ source: suggestion.path.source,
2778
+ target: suggestion.path.target,
2779
+ throughPath: suggestion.path.throughPath,
2780
+ suggestedName: suggestion.path.suggestedName,
2781
+ yamlSnippet: suggestion.path.yamlSnippet,
2782
+ status: existing.status,
2783
+ // Preserve status
2784
+ detectedAt: existing.detectedAt,
2785
+ // Preserve original detection time
2786
+ resolvedAt: existing.resolvedAt
2787
+ });
2788
+ existingMap.delete(id);
2789
+ } else {
2790
+ merged.push({
2791
+ id,
2792
+ source: suggestion.path.source,
2793
+ target: suggestion.path.target,
2794
+ throughPath: suggestion.path.throughPath,
2795
+ suggestedName: suggestion.path.suggestedName,
2796
+ yamlSnippet: suggestion.path.yamlSnippet,
2797
+ status: "pending",
2798
+ detectedAt: now
2799
+ });
2800
+ }
2801
+ }
2802
+ for (const [id, existing] of existingMap) {
2803
+ if (existing.status !== "pending") {
2804
+ merged.push(existing);
2805
+ }
2806
+ }
2807
+ return merged;
2808
+ }
2809
+ async function buildManifest(analysis, transitiveSuggestions, entitiesDir, existingManifest) {
2810
+ const entities = {};
2811
+ for (const entity of analysis.entities) {
2812
+ entities[entity.name] = toManifestEntity(entity);
2813
+ }
2814
+ const orphans = findOrphanEntities(analysis.graph);
2815
+ const cycles = findCircularDependencies(analysis.graph);
2816
+ const mergedSuggestions = mergeSuggestions(transitiveSuggestions, existingManifest);
2817
+ const entityFilesHash = await computeEntityFilesHash(entitiesDir);
2818
+ const manifest = {
2819
+ version: MANIFEST_VERSION,
2820
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2821
+ entityFilesHash,
2822
+ entities,
2823
+ graph: {
2824
+ edges: analysis.graph.edges.map((edge) => ({
2825
+ from: edge.from,
2826
+ to: edge.to,
2827
+ relationship: edge.relationship.name,
2828
+ cardinality: edge.cardinality === "N:M" ? "1:N" : edge.cardinality,
2829
+ bidirectional: edge.bidirectional
2830
+ })),
2831
+ orphans,
2832
+ cycles
2833
+ },
2834
+ suggestions: {
2835
+ transitive: mergedSuggestions
2836
+ },
2837
+ statistics: {
2838
+ totalEntities: analysis.statistics.totalEntities,
2839
+ totalFields: analysis.statistics.totalFields,
2840
+ totalRelationships: analysis.statistics.totalRelationships,
2841
+ transitivePathsDetected: transitiveSuggestions.length
2842
+ }
2843
+ };
2844
+ return manifest;
2845
+ }
2846
+ function updateSuggestionStatus(projectRoot, suggestionId, status) {
2847
+ const manifest = readManifest(projectRoot);
2848
+ if (!manifest) {
2849
+ return false;
2850
+ }
2851
+ const suggestion = manifest.suggestions.transitive.find((s) => s.id === suggestionId);
2852
+ if (!suggestion) {
2853
+ return false;
2854
+ }
2855
+ suggestion.status = status;
2856
+ suggestion.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
2857
+ writeManifest(projectRoot, manifest);
2858
+ return true;
2859
+ }
2860
+ function updateAllSuggestionStatus(projectRoot, status) {
2861
+ const manifest = readManifest(projectRoot);
2862
+ if (!manifest) {
2863
+ return 0;
2864
+ }
2865
+ let count = 0;
2866
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2867
+ for (const suggestion of manifest.suggestions.transitive) {
2868
+ if (suggestion.status === "pending") {
2869
+ suggestion.status = status;
2870
+ suggestion.resolvedAt = now;
2871
+ count++;
2872
+ }
2873
+ }
2874
+ if (count > 0) {
2875
+ writeManifest(projectRoot, manifest);
2876
+ }
2877
+ return count;
2878
+ }
2879
+ function getPendingSuggestions(projectRoot) {
2880
+ const manifest = readManifest(projectRoot);
2881
+ if (!manifest) {
2882
+ return [];
2883
+ }
2884
+ return manifest.suggestions.transitive.filter((s) => s.status === "pending");
2885
+ }
2886
+
2887
+ // src/analyzer/serialize-graph.ts
2888
+ function serializeFields(fields) {
2889
+ const result = {};
2890
+ for (const [key, f] of fields) {
2891
+ result[key] = {
2892
+ name: f.name,
2893
+ type: f.type,
2894
+ required: f.required,
2895
+ nullable: f.nullable,
2896
+ unique: f.unique,
2897
+ index: f.index,
2898
+ foreignKey: f.foreignKey,
2899
+ choices: f.choices
2900
+ };
2901
+ }
2902
+ return result;
2903
+ }
2904
+ function serializeRelationships(rels) {
2905
+ const result = {};
2906
+ for (const [key, r] of rels) {
2907
+ result[key] = {
2908
+ name: r.name,
2909
+ type: r.type,
2910
+ target: r.target,
2911
+ foreignKey: r.foreignKey,
2912
+ inverse: r.inverse,
2913
+ through: r.through,
2914
+ resolved: r.resolved
2915
+ };
2916
+ }
2917
+ return result;
2918
+ }
2919
+ function serializeDomainGraph(graph) {
2920
+ const entities = {};
2921
+ for (const [key, entity] of graph.entities) {
2922
+ entities[key] = {
2923
+ name: entity.name,
2924
+ plural: entity.plural,
2925
+ table: entity.table,
2926
+ pattern: entity.pattern,
2927
+ patterns: entity.patterns,
2928
+ patternConfig: entity.patternConfig,
2929
+ fields: serializeFields(entity.fields),
2930
+ relationships: serializeRelationships(entity.relationships),
2931
+ behaviors: entity.behaviors,
2932
+ queries: entity.queries?.map((q) => ({
2933
+ by: q.by,
2934
+ unique: q.unique,
2935
+ order: q.order
2936
+ })),
2937
+ sourcePath: entity.sourcePath
2938
+ };
2939
+ }
2940
+ const relationshipDefinitions = {};
2941
+ for (const [key, relDef] of graph.relationshipDefinitions) {
2942
+ relationshipDefinitions[key] = {
2943
+ name: relDef.name,
2944
+ table: relDef.table,
2945
+ from: relDef.from,
2946
+ to: relDef.to,
2947
+ selfReferential: relDef.selfReferential,
2948
+ fromColumn: relDef.fromColumn,
2949
+ toColumn: relDef.toColumn,
2950
+ types: relDef.types,
2951
+ hasTypes: relDef.hasTypes,
2952
+ temporal: relDef.temporal,
2953
+ sourced: relDef.sourced,
2954
+ onDeleteFrom: relDef.onDeleteFrom,
2955
+ onDeleteTo: relDef.onDeleteTo,
2956
+ uniqueOn: relDef.uniqueOn,
2957
+ fields: serializeFields(relDef.fields),
2958
+ queries: relDef.queries?.map((q) => ({
2959
+ by: q.by,
2960
+ unique: q.unique,
2961
+ order: q.order
2962
+ })),
2963
+ sourcePath: relDef.sourcePath
2964
+ };
2965
+ }
2966
+ return {
2967
+ entities,
2968
+ relationshipDefinitions,
2969
+ edges: graph.edges.map((e) => ({
2970
+ from: e.from,
2971
+ to: e.to,
2972
+ relationship: {
2973
+ name: e.relationship.name,
2974
+ type: e.relationship.type,
2975
+ target: e.relationship.target,
2976
+ foreignKey: e.relationship.foreignKey,
2977
+ inverse: e.relationship.inverse,
2978
+ through: e.relationship.through,
2979
+ resolved: e.relationship.resolved
2980
+ },
2981
+ cardinality: e.cardinality,
2982
+ bidirectional: e.bidirectional
2983
+ }))
2984
+ };
2985
+ }
2986
+
2987
+ // src/patterns/registry.ts
2988
+ import { glob } from "glob";
2989
+ import path from "path";
2990
+ import { pathToFileURL } from "url";
2991
+
2992
+ // src/patterns/pattern-definition.ts
2993
+ function definePattern(def) {
2994
+ return def;
2995
+ }
2996
+ function isPatternDefinition(val) {
2997
+ return typeof val === "object" && val !== null && "name" in val && typeof val.name === "string";
2998
+ }
2999
+ function isOrchestrationPattern(def) {
3000
+ return def.kind === "orchestration";
3001
+ }
3002
+ function isDomainPattern(def) {
3003
+ return !isOrchestrationPattern(def);
3004
+ }
3005
+ function defineOrchestrationPattern(def) {
3006
+ return def;
3007
+ }
3008
+
3009
+ // src/patterns/registry.ts
3010
+ var LIBRARY_PATTERNS = /* @__PURE__ */ new Map();
3011
+ var APP_PATTERNS = /* @__PURE__ */ new Map();
3012
+ var ORCHESTRATION_APP_PATTERNS = /* @__PURE__ */ new Map();
3013
+ function assertHasContribution(def) {
3014
+ const hasColumns = Array.isArray(def.columns) && def.columns.length > 0;
3015
+ const hasRepo = typeof def.repositoryClass === "string" && def.repositoryClass.length > 0;
3016
+ const hasService = typeof def.serviceClass === "string" && def.serviceClass.length > 0;
3017
+ if (!hasColumns && !hasRepo && !hasService) {
3018
+ throw new Error(
3019
+ `Pattern '${def.name}' contributes nothing \u2014 at least one of \`columns\`, \`repositoryClass\`, or \`serviceClass\` is required.`
3020
+ );
3021
+ }
3022
+ }
3023
+ function assertOrchestrationContribution(def) {
3024
+ if (!def.registry || typeof def.registry !== "object") {
3025
+ throw new Error(
3026
+ `Orchestration pattern '${def.name}' is missing a 'registry' field.`
3027
+ );
3028
+ }
3029
+ if (typeof def.registry.keyType !== "string" || def.registry.keyType.length === 0) {
3030
+ throw new Error(
3031
+ `Orchestration pattern '${def.name}' registry.keyType must be a non-empty string.`
3032
+ );
3033
+ }
3034
+ if (typeof def.registry.valueType !== "string" || def.registry.valueType.length === 0) {
3035
+ throw new Error(
3036
+ `Orchestration pattern '${def.name}' registry.valueType must be a non-empty string.`
3037
+ );
3038
+ }
3039
+ if (!Array.isArray(def.registry.entries) || def.registry.entries.length === 0) {
3040
+ throw new Error(
3041
+ `Orchestration pattern '${def.name}' registry.entries must contain at least one entry.`
3042
+ );
3043
+ }
3044
+ }
3045
+ function registerLibraryPattern(def) {
3046
+ assertHasContribution(def);
3047
+ LIBRARY_PATTERNS.set(def.name, def);
3048
+ }
3049
+ function getPattern(name) {
3050
+ return APP_PATTERNS.get(name) ?? LIBRARY_PATTERNS.get(name);
3051
+ }
3052
+ function getAllPatternNames() {
3053
+ const set = /* @__PURE__ */ new Set([
3054
+ ...LIBRARY_PATTERNS.keys(),
3055
+ ...APP_PATTERNS.keys()
3056
+ ]);
3057
+ return [...set].sort();
3058
+ }
3059
+ function getLibraryPatternNames() {
3060
+ return [...LIBRARY_PATTERNS.keys()].sort();
3061
+ }
3062
+ function getAppPatternNames() {
3063
+ return [...APP_PATTERNS.keys()].sort();
3064
+ }
3065
+ function getOrchestrationPattern(name) {
3066
+ return ORCHESTRATION_APP_PATTERNS.get(name);
3067
+ }
3068
+ function getOrchestrationPatternNames() {
3069
+ return [...ORCHESTRATION_APP_PATTERNS.keys()].sort();
3070
+ }
3071
+ function getAllOrchestrationPatterns() {
3072
+ return getOrchestrationPatternNames().map(
3073
+ (n) => ORCHESTRATION_APP_PATTERNS.get(n)
3074
+ );
3075
+ }
3076
+ async function loadAppPatterns(manifestPaths, cwd) {
3077
+ const loaded = /* @__PURE__ */ new Set();
3078
+ const errors = [];
3079
+ const files = /* @__PURE__ */ new Set();
3080
+ for (const raw of manifestPaths) {
3081
+ try {
3082
+ const expanded = await glob(raw, { cwd, absolute: true, nodir: true });
3083
+ for (const filePath of expanded) {
3084
+ files.add(filePath);
3085
+ }
3086
+ } catch (err) {
3087
+ errors.push(
3088
+ `Failed to expand pattern glob '${raw}': ${stringifyError(err)}`
3089
+ );
3090
+ }
3091
+ }
3092
+ const sortedFiles = [...files].sort();
3093
+ for (const filePath of sortedFiles) {
3094
+ try {
3095
+ const mod = await import(pathToFileURL(filePath).href);
3096
+ for (const [key, val] of Object.entries(mod)) {
3097
+ if (!key.endsWith("Pattern")) continue;
3098
+ if (!isPatternDefinition(val)) continue;
3099
+ if (isOrchestrationPattern(val)) {
3100
+ const orch = val;
3101
+ try {
3102
+ assertOrchestrationContribution(orch);
3103
+ } catch (assertErr) {
3104
+ errors.push(
3105
+ `Orchestration pattern '${orch.name}' in ${relPath(filePath, cwd)} is invalid: ${stringifyError(assertErr)}`
3106
+ );
3107
+ continue;
3108
+ }
3109
+ const existingOrch = ORCHESTRATION_APP_PATTERNS.get(orch.name);
3110
+ if (existingOrch && existingOrch !== orch) {
3111
+ errors.push(
3112
+ `Orchestration pattern '${orch.name}' in ${relPath(filePath, cwd)} duplicates a previously loaded orchestration pattern. Pattern names must be unique.`
3113
+ );
3114
+ continue;
3115
+ }
3116
+ ORCHESTRATION_APP_PATTERNS.set(orch.name, orch);
3117
+ loaded.add(orch.name);
3118
+ } else {
3119
+ try {
3120
+ assertHasContribution(val);
3121
+ } catch (assertErr) {
3122
+ errors.push(
3123
+ `Pattern '${val.name}' in ${relPath(filePath, cwd)} is invalid: ${stringifyError(assertErr)}`
3124
+ );
3125
+ continue;
3126
+ }
3127
+ const existingDom = APP_PATTERNS.get(val.name);
3128
+ if (existingDom && existingDom !== val) {
3129
+ errors.push(
3130
+ `Pattern '${val.name}' in ${relPath(filePath, cwd)} duplicates a previously loaded app pattern. Pattern names must be unique.`
3131
+ );
3132
+ continue;
3133
+ }
3134
+ APP_PATTERNS.set(val.name, val);
3135
+ loaded.add(val.name);
3136
+ }
3137
+ }
3138
+ } catch (err) {
3139
+ errors.push(
3140
+ `Failed to load pattern file '${relPath(filePath, cwd)}': ${stringifyError(err)}`
3141
+ );
3142
+ }
3143
+ }
3144
+ return {
3145
+ loaded: [...loaded].sort(),
3146
+ errors
3147
+ };
3148
+ }
3149
+ function _resetRegistryForTests(opts = {}) {
3150
+ APP_PATTERNS.clear();
3151
+ ORCHESTRATION_APP_PATTERNS.clear();
3152
+ if (opts.includeLibrary) {
3153
+ LIBRARY_PATTERNS.clear();
3154
+ }
3155
+ }
3156
+ function stringifyError(err) {
3157
+ if (err instanceof Error) return err.message;
3158
+ return String(err);
3159
+ }
3160
+ function relPath(abs, cwd) {
3161
+ try {
3162
+ return path.relative(cwd, abs) || abs;
3163
+ } catch {
3164
+ return abs;
3165
+ }
3166
+ }
3167
+
3168
+ // src/patterns/validate-composition.ts
3169
+ function validatePatternComposition(entity) {
3170
+ const issues = [];
3171
+ const patternNames = entity.patterns ?? (entity.pattern ? [entity.pattern] : []);
3172
+ if (patternNames.length === 0) return issues;
3173
+ const columnSources = /* @__PURE__ */ new Map();
3174
+ for (const [name] of entity.fields) {
3175
+ columnSources.set(name, `entity field '${name}'`);
3176
+ }
3177
+ const behaviorFields = resolveBehaviorFields(entity.behaviors);
3178
+ for (const bf of behaviorFields) {
3179
+ const existing = columnSources.get(bf.name);
3180
+ if (existing) {
3181
+ issues.push({
3182
+ severity: "error",
3183
+ type: "pattern_column_conflict",
3184
+ entity: entity.name,
3185
+ message: `Behavior-contributed field '${bf.name}' conflicts with ${existing}.`
3186
+ });
3187
+ continue;
3188
+ }
3189
+ columnSources.set(bf.name, `behavior field '${bf.name}'`);
3190
+ }
3191
+ const impliedBehaviors = new Set(entity.behaviors);
3192
+ for (const patternName of patternNames) {
3193
+ const def = getPattern(patternName);
3194
+ if (!def) {
3195
+ issues.push({
3196
+ severity: "error",
3197
+ type: "pattern_unknown",
3198
+ entity: entity.name,
3199
+ message: `Unknown pattern '${patternName}'. Library patterns are pre-registered; app patterns are loaded from globs in codegen.config.yaml 'patterns:' (default 'src/patterns/*.pattern.ts').`
3200
+ });
3201
+ continue;
3202
+ }
3203
+ if (def.configSchema) {
3204
+ const rawConfig = entity.patternConfig?.[patternName];
3205
+ const result = def.configSchema.safeParse(rawConfig ?? {});
3206
+ if (!result.success) {
3207
+ const detail = result.error.issues.map((i) => `${i.path.join(".") || "<root>"}: ${i.message}`).join(", ");
3208
+ issues.push({
3209
+ severity: "error",
3210
+ type: "pattern_config_invalid",
3211
+ entity: entity.name,
3212
+ message: `Pattern '${patternName}' config failed validation: ${detail}`
3213
+ });
3214
+ }
3215
+ }
3216
+ for (const col of def.columns ?? []) {
3217
+ const existing = columnSources.get(col.name);
3218
+ if (existing) {
3219
+ issues.push({
3220
+ severity: "error",
3221
+ type: "pattern_column_conflict",
3222
+ entity: entity.name,
3223
+ message: `Pattern '${patternName}' contributes column '${col.name}' which conflicts with ${existing}.`
3224
+ });
3225
+ continue;
3226
+ }
3227
+ columnSources.set(col.name, `pattern '${patternName}'`);
3228
+ }
3229
+ for (const b of def.impliedBehaviors ?? []) {
3230
+ impliedBehaviors.add(b);
3231
+ }
3232
+ }
3233
+ if (entity.patternConfig) {
3234
+ const declared = new Set(patternNames);
3235
+ for (const key of Object.keys(entity.patternConfig)) {
3236
+ if (!declared.has(key)) {
3237
+ issues.push({
3238
+ severity: "warning",
3239
+ type: "pattern_config_unused",
3240
+ entity: entity.name,
3241
+ message: `Config block has key '${key}' but pattern '${key}' is not declared in 'pattern:' or 'patterns:'. Remove the entry or add the pattern.`
3242
+ });
3243
+ }
3244
+ }
3245
+ }
3246
+ return issues;
3247
+ }
3248
+ function validatePatternProject(ctx) {
3249
+ const issues = [];
3250
+ if (ctx.architecture === "clean") {
3251
+ const withPatterns = ctx.entities.filter(
3252
+ (e) => e.patterns && e.patterns.length > 0 || !!e.pattern
3253
+ );
3254
+ for (const e of withPatterns) {
3255
+ issues.push({
3256
+ severity: "warning",
3257
+ type: "pattern_clean_pipeline_noop",
3258
+ entity: e.name,
3259
+ message: `'pattern:' is declared but 'generate.architecture: clean' does not yet consume patterns. This declaration is a no-op. Patterns are consumed by 'clean-lite-ps' today; 'clean' integration is Phase 3+ additive work (ADR-031).`
3260
+ });
3261
+ }
3262
+ }
3263
+ return issues;
3264
+ }
3265
+
3266
+ // src/patterns/validate-orchestration.ts
3267
+ var PROJECT_SENTINEL = "<project>";
3268
+ function validateOrchestrationProject(ctx) {
3269
+ const issues = [];
3270
+ const domainNameSet = new Set(ctx.domainPatternNames);
3271
+ for (const orch of ctx.orchestrationPatterns) {
3272
+ if (domainNameSet.has(orch.name)) {
3273
+ issues.push({
3274
+ severity: "error",
3275
+ type: "pattern_name_collision",
3276
+ entity: PROJECT_SENTINEL,
3277
+ message: `Orchestration pattern '${orch.name}' shares a name with a domain pattern. Pattern names are globally unique across kinds (ADR-032 \xA7Composition rules).`
3278
+ });
3279
+ }
3280
+ }
3281
+ for (const orch of ctx.orchestrationPatterns) {
3282
+ const allRegistries = [
3283
+ orch.registry,
3284
+ ...orch.coKeyedRegistries ?? []
3285
+ ];
3286
+ for (const reg of allRegistries) {
3287
+ if (!Array.isArray(reg.entries) || reg.entries.length === 0) {
3288
+ issues.push({
3289
+ severity: "error",
3290
+ type: "pattern_entries_empty",
3291
+ entity: PROJECT_SENTINEL,
3292
+ message: `Orchestration pattern '${orch.name}' declares a registry with no entries. Provide at least one { key, provider } pair.`
3293
+ });
3294
+ continue;
3295
+ }
3296
+ const seen = /* @__PURE__ */ new Set();
3297
+ for (const entry of reg.entries) {
3298
+ if (typeof entry.key !== "string" || entry.key.length === 0) {
3299
+ issues.push({
3300
+ severity: "error",
3301
+ type: "pattern_entry_malformed",
3302
+ entity: PROJECT_SENTINEL,
3303
+ message: `Orchestration pattern '${orch.name}' has an entry with a missing or non-string 'key'.`
3304
+ });
3305
+ continue;
3306
+ }
3307
+ if (typeof entry.provider !== "string" || entry.provider.length === 0) {
3308
+ issues.push({
3309
+ severity: "error",
3310
+ type: "pattern_entry_malformed",
3311
+ entity: PROJECT_SENTINEL,
3312
+ message: `Orchestration pattern '${orch.name}' entry '${entry.key}' has a missing or non-string 'provider'.`
3313
+ });
3314
+ continue;
3315
+ }
3316
+ if (seen.has(entry.key)) {
3317
+ issues.push({
3318
+ severity: "error",
3319
+ type: "pattern_entry_key_duplicate",
3320
+ entity: PROJECT_SENTINEL,
3321
+ message: `Orchestration pattern '${orch.name}' has duplicate entry key '${entry.key}'. Keys must be unique within a registry.`
3322
+ });
3323
+ continue;
3324
+ }
3325
+ seen.add(entry.key);
3326
+ }
3327
+ }
3328
+ if (orch.coKeyedRegistries && orch.coKeyedRegistries.length > 0) {
3329
+ const primaryKeyType = orch.registry.keyType;
3330
+ for (const reg of orch.coKeyedRegistries) {
3331
+ if (reg.keyType !== primaryKeyType) {
3332
+ issues.push({
3333
+ severity: "error",
3334
+ type: "pattern_cokeyed_keytype_mismatch",
3335
+ entity: PROJECT_SENTINEL,
3336
+ message: `Orchestration pattern '${orch.name}' co-keyed registry has keyType '${reg.keyType}', expected '${primaryKeyType}'. Co-keyed registries must share the primary registry's key space (ADR-032 Decision 2).`
3337
+ });
3338
+ }
3339
+ }
3340
+ }
3341
+ }
3342
+ return issues;
3343
+ }
3344
+
3345
+ // src/formatters/console-formatter.ts
3346
+ var colors = {
3347
+ reset: "\x1B[0m",
3348
+ bold: "\x1B[1m",
3349
+ dim: "\x1B[2m",
3350
+ red: "\x1B[31m",
3351
+ green: "\x1B[32m",
3352
+ yellow: "\x1B[33m",
3353
+ blue: "\x1B[34m",
3354
+ magenta: "\x1B[35m",
3355
+ cyan: "\x1B[36m",
3356
+ white: "\x1B[37m",
3357
+ bgRed: "\x1B[41m",
3358
+ bgGreen: "\x1B[42m",
3359
+ bgYellow: "\x1B[43m"
3360
+ };
3361
+ function color(text, ...styles) {
3362
+ return `${styles.join("")}${text}${colors.reset}`;
3363
+ }
3364
+ function severityColor(severity) {
3365
+ switch (severity) {
3366
+ case "error":
3367
+ return colors.red;
3368
+ case "warning":
3369
+ return colors.yellow;
3370
+ case "info":
3371
+ return colors.cyan;
3372
+ }
3373
+ }
3374
+ function severityIcon(severity) {
3375
+ switch (severity) {
3376
+ case "error":
3377
+ return "X";
3378
+ case "warning":
3379
+ return "!";
3380
+ case "info":
3381
+ return "i";
3382
+ }
3383
+ }
3384
+ function formatConsole(result) {
3385
+ const lines = [];
3386
+ lines.push("");
3387
+ lines.push(color("=".repeat(60), colors.dim));
3388
+ lines.push(color(" Domain Analysis Report", colors.bold, colors.cyan));
3389
+ lines.push(color("=".repeat(60), colors.dim));
3390
+ lines.push("");
3391
+ lines.push(...formatStatistics(result));
3392
+ lines.push(...formatEntities(result));
3393
+ lines.push(...formatRelationships(result));
3394
+ if (result.issues.length > 0) {
3395
+ lines.push(...formatIssues(result.issues));
3396
+ }
3397
+ lines.push("");
3398
+ lines.push(color("-".repeat(60), colors.dim));
3399
+ const errors = result.issues.filter((i) => i.severity === "error");
3400
+ const warnings = result.issues.filter((i) => i.severity === "warning");
3401
+ const infos = result.issues.filter((i) => i.severity === "info");
3402
+ if (result.isValid) {
3403
+ lines.push(
3404
+ color(
3405
+ `[OK] Domain is valid (${warnings.length} warnings, ${infos.length} info)`,
3406
+ colors.green
3407
+ )
3408
+ );
3409
+ } else {
3410
+ lines.push(color(`[FAIL] Domain has ${errors.length} errors`, colors.red));
3411
+ }
3412
+ lines.push(color("-".repeat(60), colors.dim));
3413
+ lines.push("");
3414
+ return lines.join("\n");
3415
+ }
3416
+ function formatStatistics(result) {
3417
+ const lines = [];
3418
+ const stats = result.statistics;
3419
+ lines.push(color("Statistics:", colors.bold));
3420
+ lines.push("");
3421
+ lines.push(` Entities: ${stats.totalEntities}`);
3422
+ lines.push(
3423
+ ` Fields: ${stats.totalFields} (avg ${stats.averageFieldsPerEntity.toFixed(1)}/entity)`
3424
+ );
3425
+ lines.push(` Relationships: ${stats.totalRelationships}`);
3426
+ lines.push(` With behaviors: ${stats.entitiesWithBehaviors}`);
3427
+ lines.push("");
3428
+ lines.push(" Field types:");
3429
+ const sortedTypes = Object.entries(stats.fieldsByType).sort((a, b) => b[1] - a[1]);
3430
+ for (const [type, count] of sortedTypes) {
3431
+ const bar = color("|".repeat(Math.min(count, 20)), colors.blue);
3432
+ lines.push(` ${type.padEnd(12)} ${bar} ${count}`);
3433
+ }
3434
+ lines.push("");
3435
+ if (stats.totalRelationships > 0) {
3436
+ lines.push(" Relationship types:");
3437
+ const sortedRels = Object.entries(stats.relationshipsByType).sort((a, b) => b[1] - a[1]);
3438
+ for (const [type, count] of sortedRels) {
3439
+ const bar = color("|".repeat(Math.min(count, 20)), colors.magenta);
3440
+ lines.push(` ${type.padEnd(12)} ${bar} ${count}`);
3441
+ }
3442
+ lines.push("");
3443
+ }
3444
+ return lines;
3445
+ }
3446
+ function formatEntities(result) {
3447
+ const lines = [];
3448
+ lines.push(color("Entities:", colors.bold));
3449
+ lines.push("");
3450
+ for (const entity of result.entities) {
3451
+ const fieldCount = entity.fields.size;
3452
+ const relCount = entity.relationships.size;
3453
+ lines.push(
3454
+ ` ${color(entity.name, colors.cyan)} (${fieldCount} fields, ${relCount} relationships)`
3455
+ );
3456
+ if (entity.behaviors.length > 0) {
3457
+ lines.push(color(` behaviors: ${entity.behaviors.join(", ")}`, colors.dim));
3458
+ }
3459
+ }
3460
+ lines.push("");
3461
+ return lines;
3462
+ }
3463
+ function formatRelationships(result) {
3464
+ const lines = [];
3465
+ if (result.graph.edges.length === 0) {
3466
+ return lines;
3467
+ }
3468
+ lines.push(color("Relationships:", colors.bold));
3469
+ lines.push("");
3470
+ for (const edge of result.graph.edges) {
3471
+ const arrow = getCardinalityArrow(edge.cardinality);
3472
+ const bidir = edge.bidirectional ? color(" (bidirectional)", colors.dim) : "";
3473
+ lines.push(
3474
+ ` ${edge.from.padEnd(20)} ${arrow} ${edge.to} ${color(`(${edge.relationship.type})`, colors.dim)}${bidir}`
3475
+ );
3476
+ }
3477
+ lines.push("");
3478
+ return lines;
3479
+ }
3480
+ function getCardinalityArrow(cardinality) {
3481
+ switch (cardinality) {
3482
+ case "1:N":
3483
+ return color("--<", colors.magenta);
3484
+ case "N:1":
3485
+ return color(">--", colors.magenta);
3486
+ case "1:1":
3487
+ return color("---", colors.magenta);
3488
+ case "N:M":
3489
+ return color(">-<", colors.magenta);
3490
+ default:
3491
+ return color("-->", colors.magenta);
3492
+ }
3493
+ }
3494
+ function formatIssues(issues) {
3495
+ const lines = [];
3496
+ const errors = issues.filter((i) => i.severity === "error");
3497
+ const warnings = issues.filter((i) => i.severity === "warning");
3498
+ const infos = issues.filter((i) => i.severity === "info");
3499
+ if (errors.length > 0) {
3500
+ lines.push(color(`Errors (${errors.length}):`, colors.bold, colors.red));
3501
+ lines.push("");
3502
+ lines.push(...formatIssueList(errors, "error"));
3503
+ lines.push("");
3504
+ }
3505
+ if (warnings.length > 0) {
3506
+ lines.push(color(`Warnings (${warnings.length}):`, colors.bold, colors.yellow));
3507
+ lines.push("");
3508
+ lines.push(...formatIssueList(warnings, "warning"));
3509
+ lines.push("");
3510
+ }
3511
+ if (infos.length > 0) {
3512
+ lines.push(color(`Info (${infos.length}):`, colors.bold, colors.cyan));
3513
+ lines.push("");
3514
+ lines.push(...formatIssueList(infos, "info", 5));
3515
+ lines.push("");
3516
+ }
3517
+ return lines;
3518
+ }
3519
+ function formatIssueList(issues, severity, limit) {
3520
+ const lines = [];
3521
+ const displayIssues = limit ? issues.slice(0, limit) : issues;
3522
+ const byType = /* @__PURE__ */ new Map();
3523
+ for (const issue of displayIssues) {
3524
+ const list = byType.get(issue.type) ?? [];
3525
+ list.push(issue);
3526
+ byType.set(issue.type, list);
3527
+ }
3528
+ for (const [type, typeIssues] of byType) {
3529
+ lines.push(color(` ${type} (${typeIssues.length}):`, colors.dim));
3530
+ for (const issue of typeIssues.slice(0, 5)) {
3531
+ const location = issue.entity ? `${issue.entity}${issue.field ? "." + issue.field : ""}` : issue.path ?? "unknown";
3532
+ const icon = color(`[${severityIcon(severity)}]`, severityColor(severity));
3533
+ lines.push(` ${icon} ${color(location, colors.bold)}: ${issue.message}`);
3534
+ if (issue.suggestion) {
3535
+ lines.push(color(` -> ${issue.suggestion}`, colors.dim));
3536
+ }
3537
+ }
3538
+ if (typeIssues.length > 5) {
3539
+ lines.push(color(` ... and ${typeIssues.length - 5} more`, colors.dim));
3540
+ }
3541
+ }
3542
+ if (limit && issues.length > limit) {
3543
+ lines.push(color(` ... and ${issues.length - limit} more info messages`, colors.dim));
3544
+ }
3545
+ return lines;
3546
+ }
3547
+
3548
+ // src/formatters/json-formatter.ts
3549
+ function mapToObject(map) {
3550
+ const obj = {};
3551
+ for (const [key, value] of map) {
3552
+ obj[key] = value;
3553
+ }
3554
+ return obj;
3555
+ }
3556
+ function serializeEntity(entity) {
3557
+ return {
3558
+ name: entity.name,
3559
+ plural: entity.plural,
3560
+ table: entity.table,
3561
+ folderStructure: entity.folderStructure,
3562
+ fields: mapToObject(entity.fields),
3563
+ relationships: mapToObject(entity.relationships),
3564
+ behaviors: entity.behaviors,
3565
+ sourcePath: entity.sourcePath
3566
+ };
3567
+ }
3568
+ function serializeGraph(graph) {
3569
+ const entities = {};
3570
+ for (const [name, entity] of graph.entities) {
3571
+ entities[name] = serializeEntity(entity);
3572
+ }
3573
+ return {
3574
+ entities,
3575
+ edges: graph.edges
3576
+ };
3577
+ }
3578
+ function formatJson(result, pretty = true) {
3579
+ const output = {
3580
+ isValid: result.isValid,
3581
+ summary: {
3582
+ entities: result.statistics.totalEntities,
3583
+ fields: result.statistics.totalFields,
3584
+ relationships: result.statistics.totalRelationships,
3585
+ errors: result.issues.filter((i) => i.severity === "error").length,
3586
+ warnings: result.issues.filter((i) => i.severity === "warning").length,
3587
+ info: result.issues.filter((i) => i.severity === "info").length
3588
+ },
3589
+ entities: result.entities.map(serializeEntity),
3590
+ graph: serializeGraph(result.graph),
3591
+ issues: result.issues,
3592
+ statistics: result.statistics,
3593
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3594
+ };
3595
+ return pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
3596
+ }
3597
+ function formatStatsJson(result, pretty = true) {
3598
+ const output = {
3599
+ statistics: result.statistics,
3600
+ isValid: result.isValid,
3601
+ issueCount: {
3602
+ errors: result.issues.filter((i) => i.severity === "error").length,
3603
+ warnings: result.issues.filter((i) => i.severity === "warning").length,
3604
+ info: result.issues.filter((i) => i.severity === "info").length
3605
+ },
3606
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3607
+ };
3608
+ return pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
3609
+ }
3610
+
3611
+ // src/formatters/markdown-formatter.ts
3612
+ function formatMarkdown(result) {
3613
+ const lines = [];
3614
+ lines.push("# Domain Model Documentation");
3615
+ lines.push("");
3616
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
3617
+ lines.push("");
3618
+ lines.push("## Overview");
3619
+ lines.push("");
3620
+ lines.push("| Metric | Value |");
3621
+ lines.push("|--------|-------|");
3622
+ lines.push(`| Entities | ${result.statistics.totalEntities} |`);
3623
+ lines.push(`| Total Fields | ${result.statistics.totalFields} |`);
3624
+ lines.push(`| Total Relationships | ${result.statistics.totalRelationships} |`);
3625
+ lines.push(
3626
+ `| Avg Fields/Entity | ${result.statistics.averageFieldsPerEntity.toFixed(1)} |`
3627
+ );
3628
+ lines.push("");
3629
+ lines.push("### Field Type Distribution");
3630
+ lines.push("");
3631
+ lines.push("| Type | Count |");
3632
+ lines.push("|------|-------|");
3633
+ const sortedTypes = Object.entries(result.statistics.fieldsByType).sort(
3634
+ (a, b) => b[1] - a[1]
3635
+ );
3636
+ for (const [type, count] of sortedTypes) {
3637
+ lines.push(`| ${type} | ${count} |`);
3638
+ }
3639
+ lines.push("");
3640
+ if (result.statistics.totalRelationships > 0) {
3641
+ lines.push("### Relationship Type Distribution");
3642
+ lines.push("");
3643
+ lines.push("| Type | Count |");
3644
+ lines.push("|------|-------|");
3645
+ const sortedRels = Object.entries(result.statistics.relationshipsByType).sort(
3646
+ (a, b) => b[1] - a[1]
3647
+ );
3648
+ for (const [type, count] of sortedRels) {
3649
+ lines.push(`| ${type} | ${count} |`);
3650
+ }
3651
+ lines.push("");
3652
+ }
3653
+ lines.push("## Entity Relationship Diagram");
3654
+ lines.push("");
3655
+ lines.push("```mermaid");
3656
+ lines.push(...generateMermaidErDiagram(result));
3657
+ lines.push("```");
3658
+ lines.push("");
3659
+ lines.push("## Entities");
3660
+ lines.push("");
3661
+ for (const entity of result.entities) {
3662
+ lines.push(...formatEntitySection(entity));
3663
+ }
3664
+ const errors = result.issues.filter((i) => i.severity === "error");
3665
+ const warnings = result.issues.filter((i) => i.severity === "warning");
3666
+ const infos = result.issues.filter((i) => i.severity === "info");
3667
+ if (result.issues.length > 0) {
3668
+ lines.push("## Analysis Issues");
3669
+ lines.push("");
3670
+ if (errors.length > 0) {
3671
+ lines.push("### Errors");
3672
+ lines.push("");
3673
+ for (const issue of errors) {
3674
+ const location = issue.entity ? `**${issue.entity}${issue.field ? "." + issue.field : ""}**` : issue.path ?? "unknown";
3675
+ lines.push(`- [${issue.type}] ${location}: ${issue.message}`);
3676
+ if (issue.suggestion) {
3677
+ lines.push(` - Suggestion: ${issue.suggestion}`);
3678
+ }
3679
+ }
3680
+ lines.push("");
3681
+ }
3682
+ if (warnings.length > 0) {
3683
+ lines.push("### Warnings");
3684
+ lines.push("");
3685
+ for (const issue of warnings) {
3686
+ const location = issue.entity ? `**${issue.entity}${issue.field ? "." + issue.field : ""}**` : issue.path ?? "unknown";
3687
+ lines.push(`- [${issue.type}] ${location}: ${issue.message}`);
3688
+ if (issue.suggestion) {
3689
+ lines.push(` - Suggestion: ${issue.suggestion}`);
3690
+ }
3691
+ }
3692
+ lines.push("");
3693
+ }
3694
+ if (infos.length > 0) {
3695
+ lines.push("### Info");
3696
+ lines.push("");
3697
+ lines.push("<details>");
3698
+ lines.push("<summary>Show info messages</summary>");
3699
+ lines.push("");
3700
+ for (const issue of infos) {
3701
+ const location = issue.entity ? `**${issue.entity}${issue.field ? "." + issue.field : ""}**` : issue.path ?? "unknown";
3702
+ lines.push(`- [${issue.type}] ${location}: ${issue.message}`);
3703
+ }
3704
+ lines.push("");
3705
+ lines.push("</details>");
3706
+ lines.push("");
3707
+ }
3708
+ }
3709
+ return lines.join("\n");
3710
+ }
3711
+ function formatEntitySection(entity) {
3712
+ const lines = [];
3713
+ lines.push(`### ${entity.name}`);
3714
+ lines.push("");
3715
+ lines.push(`**Table:** \`${entity.table}\``);
3716
+ lines.push(`**Plural:** ${entity.plural}`);
3717
+ if (entity.behaviors.length > 0) {
3718
+ lines.push(`**Behaviors:** ${entity.behaviors.join(", ")}`);
3719
+ }
3720
+ lines.push("");
3721
+ lines.push("#### Fields");
3722
+ lines.push("");
3723
+ lines.push("| Name | Type | Required | Nullable | Index | Foreign Key |");
3724
+ lines.push("|------|------|----------|----------|-------|-------------|");
3725
+ for (const [name, field] of entity.fields) {
3726
+ const required = field.required ? "Yes" : "";
3727
+ const nullable = field.nullable ? "Yes" : "";
3728
+ const index = field.index ? "Yes" : field.unique ? "Unique" : "";
3729
+ const fk = field.foreignKey ? `${field.foreignKey.table}.${field.foreignKey.column}` : "";
3730
+ lines.push(`| ${name} | ${field.type} | ${required} | ${nullable} | ${index} | ${fk} |`);
3731
+ }
3732
+ lines.push("");
3733
+ if (entity.relationships.size > 0) {
3734
+ lines.push("#### Relationships");
3735
+ lines.push("");
3736
+ lines.push("| Name | Type | Target | Foreign Key |");
3737
+ lines.push("|------|------|--------|-------------|");
3738
+ for (const [name, rel] of entity.relationships) {
3739
+ lines.push(`| ${name} | ${rel.type} | ${rel.target} | ${rel.foreignKey} |`);
3740
+ }
3741
+ lines.push("");
3742
+ }
3743
+ return lines;
3744
+ }
3745
+ function generateMermaidErDiagram(result) {
3746
+ const lines = [];
3747
+ lines.push("erDiagram");
3748
+ for (const entity of result.entities) {
3749
+ const entityName = entity.name.toUpperCase();
3750
+ lines.push(` ${entityName} {`);
3751
+ const keyFields = Array.from(entity.fields.entries()).filter(
3752
+ ([name, field]) => field.foreignKey || field.unique || field.index || name === "id" || name === "name"
3753
+ ).slice(0, 6);
3754
+ for (const [name, field] of keyFields) {
3755
+ const typeStr = field.type;
3756
+ const pk = name === "id" ? "PK" : "";
3757
+ const fk = field.foreignKey ? "FK" : "";
3758
+ const marker = pk || fk ? ` "${pk}${fk}"` : "";
3759
+ lines.push(` ${typeStr} ${name}${marker}`);
3760
+ }
3761
+ if (entity.fields.size > keyFields.length) {
3762
+ lines.push(` string _more_fields`);
3763
+ }
3764
+ lines.push(" }");
3765
+ }
3766
+ for (const edge of result.graph.edges) {
3767
+ const from = edge.from.toUpperCase();
3768
+ const to = edge.to.toUpperCase();
3769
+ const cardinalitySymbol = getCardinalitySymbol(edge.cardinality);
3770
+ const label = edge.relationship.name;
3771
+ lines.push(` ${from} ${cardinalitySymbol} ${to} : "${label}"`);
3772
+ }
3773
+ return lines;
3774
+ }
3775
+ function getCardinalitySymbol(cardinality) {
3776
+ switch (cardinality) {
3777
+ case "1:N":
3778
+ return "||--o{";
3779
+ case "N:1":
3780
+ return "}o--||";
3781
+ case "1:1":
3782
+ return "||--||";
3783
+ case "N:M":
3784
+ return "}o--o{";
3785
+ default:
3786
+ return "||--o{";
3787
+ }
3788
+ }
3789
+ function formatMermaidGraph(result) {
3790
+ const lines = [];
3791
+ lines.push("```mermaid");
3792
+ lines.push("graph LR");
3793
+ lines.push(" classDef entity fill:#e1f5fe,stroke:#01579b");
3794
+ for (const entity of result.entities) {
3795
+ lines.push(` ${entity.name}["${entity.name}\\n(${entity.fields.size} fields)"]`);
3796
+ }
3797
+ for (const edge of result.graph.edges) {
3798
+ const style = edge.bidirectional ? "<-->" : "-->";
3799
+ lines.push(` ${edge.from} ${style}|${edge.relationship.type}| ${edge.to}`);
3800
+ }
3801
+ const entityList = result.entities.map((e) => e.name).join(",");
3802
+ if (entityList) {
3803
+ lines.push(` class ${entityList} entity`);
3804
+ }
3805
+ lines.push("```");
3806
+ return lines.join("\n");
3807
+ }
3808
+
3809
+ // src/patterns/library/activity.pattern.ts
3810
+ var ActivityPattern = definePattern({
3811
+ name: "Activity",
3812
+ extends: ["Base"],
3813
+ repositoryClass: "ActivityEntityRepository",
3814
+ serviceClass: "ActivityEntityService",
3815
+ repositoryImport: "@shared/base-classes/activity-entity-repository",
3816
+ serviceImport: "@shared/base-classes/activity-entity-service",
3817
+ repositoryInheritedMethods: [
3818
+ "findById, findByIds, list, count, exists, create, update, delete, upsertMany",
3819
+ "findByDateRange, findByUserId, findByOpportunityId, findRecentByOpportunityId"
3820
+ ],
3821
+ serviceInheritedMethods: [
3822
+ "findById, findByIds, list, count, exists, create, update, delete",
3823
+ "findByDateRange, findByUserId, findByOpportunityId, findRecentByOpportunityId"
3824
+ ],
3825
+ description: "Time-bounded interaction entities \u2014 date-range + opportunity scoped lookups"
3826
+ });
3827
+
3828
+ // src/patterns/library/base.pattern.ts
3829
+ var BasePattern = definePattern({
3830
+ name: "Base",
3831
+ repositoryClass: "BaseRepository",
3832
+ serviceClass: "BaseService",
3833
+ repositoryImport: "@shared/base-classes/base-repository",
3834
+ serviceImport: "@shared/base-classes/base-service",
3835
+ repositoryInheritedMethods: [
3836
+ "findById, findByIds, list, count, exists, create, update, delete, upsertMany"
3837
+ ],
3838
+ serviceInheritedMethods: [
3839
+ "findById, findByIds, list, count, exists, create, update, delete"
3840
+ ],
3841
+ description: "Identity pattern \u2014 base CRUD, no extra columns or methods"
3842
+ });
3843
+
3844
+ // src/patterns/library/junction.pattern.ts
3845
+ import { z as z6 } from "zod";
3846
+ var JunctionPatternConfigSchema = z6.object({}).strict();
3847
+ var JunctionPattern = definePattern({
3848
+ name: "Junction",
3849
+ description: "Explicit many-to-many junction with role + temporal + sourcing metadata",
3850
+ columns: [...BaseJunctionFields],
3851
+ configSchema: JunctionPatternConfigSchema
3852
+ });
3853
+
3854
+ // src/patterns/library/knowledge.pattern.ts
3855
+ var KnowledgePattern = definePattern({
3856
+ name: "Knowledge",
3857
+ extends: ["Base"],
3858
+ repositoryClass: "KnowledgeEntityRepository",
3859
+ serviceClass: "KnowledgeEntityService",
3860
+ repositoryImport: "@shared/base-classes/knowledge-entity-repository",
3861
+ serviceImport: "@shared/base-classes/knowledge-entity-service",
3862
+ repositoryInheritedMethods: [
3863
+ "findById, findByIds, list, count, exists, create, update, delete, upsertMany",
3864
+ "semanticSearch, findPendingByOpportunityId, updateStatus, updateStatusBatch"
3865
+ ],
3866
+ serviceInheritedMethods: [
3867
+ "findById, findByIds, list, count, exists, create, update, delete",
3868
+ "semanticSearch, findPendingByOpportunityId, updateStatus, updateStatusBatch"
3869
+ ],
3870
+ description: "Knowledge entities \u2014 semantic search + workflow status"
3871
+ });
3872
+
3873
+ // src/patterns/library/metadata.pattern.ts
3874
+ var MetadataPattern = definePattern({
3875
+ name: "Metadata",
3876
+ extends: ["Base"],
3877
+ repositoryClass: "MetadataEntityRepository",
3878
+ serviceClass: "MetadataEntityService",
3879
+ repositoryImport: "@shared/base-classes/metadata-entity-repository",
3880
+ serviceImport: "@shared/base-classes/metadata-entity-service",
3881
+ repositoryInheritedMethods: [
3882
+ "findById, findByIds, list, count, exists, create, update, delete, upsertMany",
3883
+ "findByEntityIdAndType, listByEntityId, listHistoryByEntityId"
3884
+ ],
3885
+ serviceInheritedMethods: [
3886
+ "findById, findByIds, list, count, exists, create, update, delete",
3887
+ "findByEntityIdAndType, listByEntityId, listHistoryByEntityId"
3888
+ ],
3889
+ description: "History-tracked metadata rows \u2014 entity-id + type scoped lookups"
3890
+ });
3891
+
3892
+ // src/patterns/library/integrated.pattern.ts
3893
+ var IntegratedPattern = definePattern({
3894
+ name: "Integrated",
3895
+ extends: ["Base"],
3896
+ repositoryClass: "IntegratedEntityRepository",
3897
+ serviceClass: "IntegratedEntityService",
3898
+ repositoryImport: "@shared/base-classes/integrated-entity-repository",
3899
+ serviceImport: "@shared/base-classes/integrated-entity-service",
3900
+ repositoryInheritedMethods: [
3901
+ "findById, findByIds, list, count, exists, create, update, delete, upsertMany",
3902
+ "findByExternalId, findManyByExternalIds, findAllByUserId, findVisibleByUserId",
3903
+ "integrationUpsertOne, findByExternalIdProjected, softDeleteByExternalId, integrationUpsert"
3904
+ ],
3905
+ serviceInheritedMethods: [
3906
+ "findById, findByIds, list, count, exists, create, update, delete",
3907
+ "findByExternalId, findAllByUserId, findVisibleByUserId"
3908
+ ],
3909
+ impliedBehaviors: ["external_id_tracking"],
3910
+ description: "External CRM/system integration columns and integrationUpsert methods"
3911
+ });
3912
+
3913
+ // src/patterns/library/index.ts
3914
+ registerLibraryPattern(BasePattern);
3915
+ registerLibraryPattern(IntegratedPattern);
3916
+ registerLibraryPattern(ActivityPattern);
3917
+ registerLibraryPattern(KnowledgePattern);
3918
+ registerLibraryPattern(MetadataPattern);
3919
+ registerLibraryPattern(JunctionPattern);
3920
+
3921
+ // src/index.ts
3922
+ async function analyzeDomain(entitiesDir, relationshipsOrOptions) {
3923
+ const opts = typeof relationshipsOrOptions === "string" ? { relationshipsDir: relationshipsOrOptions } : relationshipsOrOptions ?? {};
3924
+ const relationshipsDir = opts.relationshipsDir;
3925
+ const { entities, issues: loadIssues } = loadEntities(entitiesDir);
3926
+ const { relationships: relationshipDefinitions, issues: relLoadIssues } = relationshipsDir ? loadRelationships(relationshipsDir) : { relationships: [], issues: [] };
3927
+ const resolveIssues = resolveReferences(entities);
3928
+ const relResolveIssues = resolveRelationshipReferences(
3929
+ relationshipDefinitions,
3930
+ entities
3931
+ );
3932
+ const graph = buildDomainGraph(entities, relationshipDefinitions);
3933
+ const consistencyIssues = checkConsistency(graph);
3934
+ const patternIssues = entities.flatMap((e) => validatePatternComposition(e));
3935
+ const patternProjectIssues = validatePatternProject({
3936
+ entities,
3937
+ architecture: opts.architecture
3938
+ });
3939
+ const orchestrationProjectIssues = validateOrchestrationProject({
3940
+ orchestrationPatterns: getAllOrchestrationPatterns(),
3941
+ domainPatternNames: getAllPatternNames()
3942
+ });
3943
+ const statistics = computeStatistics(graph);
3944
+ const allIssues = [
3945
+ ...loadIssues,
3946
+ ...relLoadIssues,
3947
+ ...resolveIssues,
3948
+ ...relResolveIssues,
3949
+ ...consistencyIssues,
3950
+ ...patternIssues,
3951
+ ...patternProjectIssues,
3952
+ ...orchestrationProjectIssues
3953
+ ];
3954
+ const hasErrors = allIssues.some((i) => i.severity === "error");
3955
+ return {
3956
+ isValid: !hasErrors,
3957
+ entities,
3958
+ relationshipDefinitions,
3959
+ graph,
3960
+ issues: allIssues,
3961
+ statistics
3962
+ };
3963
+ }
3964
+ function validateEntities(entitiesDir) {
3965
+ const { entities, issues } = loadEntities(entitiesDir);
3966
+ const errors = issues.filter((i) => i.severity === "error").map((i) => i.message);
3967
+ return {
3968
+ valid: errors.length === 0,
3969
+ errors
3970
+ };
3971
+ }
3972
+
3973
+ export {
3974
+ findYamlFiles,
3975
+ EVENT_FIELD_TYPES,
3976
+ DIRECTION_TO_POOL,
3977
+ BaseJunctionFields,
3978
+ BASE_JUNCTION_FIELD_NAMES,
3979
+ JunctionDefinitionSchema,
3980
+ validateJunctionDefinition,
3981
+ safeValidateJunctionDefinition,
3982
+ parseImportRef,
3983
+ loadEntityFromYaml,
3984
+ loadEntitiesFromYaml,
3985
+ loadRelationshipFromYaml,
3986
+ loadEventFromYaml,
3987
+ loadJunctionFromYaml,
3988
+ loadProvidersFromYaml,
3989
+ detectYamlType,
3990
+ loadEntities,
3991
+ loadRelationships,
3992
+ collectEntitySurfaces,
3993
+ validateProviders,
3994
+ buildDomainGraph,
3995
+ getRelatedEntities,
3996
+ findOrphanEntities,
3997
+ findCircularDependencies,
3998
+ checkConsistency,
3999
+ computeStatistics,
4000
+ suggestTransitiveRelationships,
4001
+ getManifestDir,
4002
+ readManifest,
4003
+ writeManifest,
4004
+ isManifestStale,
4005
+ buildManifest,
4006
+ updateSuggestionStatus,
4007
+ updateAllSuggestionStatus,
4008
+ getPendingSuggestions,
4009
+ serializeDomainGraph,
4010
+ definePattern,
4011
+ isPatternDefinition,
4012
+ isOrchestrationPattern,
4013
+ isDomainPattern,
4014
+ defineOrchestrationPattern,
4015
+ registerLibraryPattern,
4016
+ getPattern,
4017
+ getAllPatternNames,
4018
+ getLibraryPatternNames,
4019
+ getAppPatternNames,
4020
+ getOrchestrationPattern,
4021
+ getOrchestrationPatternNames,
4022
+ getAllOrchestrationPatterns,
4023
+ loadAppPatterns,
4024
+ _resetRegistryForTests,
4025
+ validatePatternComposition,
4026
+ validatePatternProject,
4027
+ validateOrchestrationProject,
4028
+ formatConsole,
4029
+ formatJson,
4030
+ formatStatsJson,
4031
+ formatMarkdown,
4032
+ formatMermaidGraph,
4033
+ ActivityPattern,
4034
+ BasePattern,
4035
+ JunctionPattern,
4036
+ KnowledgePattern,
4037
+ MetadataPattern,
4038
+ IntegratedPattern,
4039
+ analyzeDomain,
4040
+ validateEntities
4041
+ };
4042
+ //# sourceMappingURL=chunk-32DOFN3T.js.map