@pattern-stack/codegen 0.15.1 → 0.15.3

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