@pattern-stack/codegen 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/README.md +214 -0
  3. package/dist/runtime/analytics/index.d.ts +6 -0
  4. package/dist/runtime/analytics/index.js +49 -0
  5. package/dist/runtime/analytics/index.js.map +1 -0
  6. package/dist/runtime/analytics/metrics.d.ts +75 -0
  7. package/dist/runtime/analytics/metrics.js +1 -0
  8. package/dist/runtime/analytics/metrics.js.map +1 -0
  9. package/dist/runtime/analytics/packs/crm-entity-measures.d.ts +21 -0
  10. package/dist/runtime/analytics/packs/crm-entity-measures.js +1 -0
  11. package/dist/runtime/analytics/packs/crm-entity-measures.js.map +1 -0
  12. package/dist/runtime/analytics/packs/index.d.ts +3 -0
  13. package/dist/runtime/analytics/packs/index.js +1 -0
  14. package/dist/runtime/analytics/packs/index.js.map +1 -0
  15. package/dist/runtime/analytics/packs/monetary-measures.d.ts +21 -0
  16. package/dist/runtime/analytics/packs/monetary-measures.js +1 -0
  17. package/dist/runtime/analytics/packs/monetary-measures.js.map +1 -0
  18. package/dist/runtime/analytics/specs.d.ts +49 -0
  19. package/dist/runtime/analytics/specs.js +1 -0
  20. package/dist/runtime/analytics/specs.js.map +1 -0
  21. package/dist/runtime/analytics/types.d.ts +85 -0
  22. package/dist/runtime/analytics/types.js +49 -0
  23. package/dist/runtime/analytics/types.js.map +1 -0
  24. package/dist/runtime/base-classes/activity-entity-repository.d.ts +26 -0
  25. package/dist/runtime/base-classes/activity-entity-repository.js +195 -0
  26. package/dist/runtime/base-classes/activity-entity-repository.js.map +1 -0
  27. package/dist/runtime/base-classes/activity-entity-service.d.ts +39 -0
  28. package/dist/runtime/base-classes/activity-entity-service.js +214 -0
  29. package/dist/runtime/base-classes/activity-entity-service.js.map +1 -0
  30. package/dist/runtime/base-classes/base-read-use-cases.d.ts +68 -0
  31. package/dist/runtime/base-classes/base-read-use-cases.js +32 -0
  32. package/dist/runtime/base-classes/base-read-use-cases.js.map +1 -0
  33. package/dist/runtime/base-classes/base-repository.d.ts +99 -0
  34. package/dist/runtime/base-classes/base-repository.js +160 -0
  35. package/dist/runtime/base-classes/base-repository.js.map +1 -0
  36. package/dist/runtime/base-classes/base-service.d.ts +98 -0
  37. package/dist/runtime/base-classes/base-service.js +186 -0
  38. package/dist/runtime/base-classes/base-service.js.map +1 -0
  39. package/dist/runtime/base-classes/index.d.ts +18 -0
  40. package/dist/runtime/base-classes/index.js +617 -0
  41. package/dist/runtime/base-classes/index.js.map +1 -0
  42. package/dist/runtime/base-classes/knowledge-entity-repository.d.ts +17 -0
  43. package/dist/runtime/base-classes/knowledge-entity-repository.js +166 -0
  44. package/dist/runtime/base-classes/knowledge-entity-repository.js.map +1 -0
  45. package/dist/runtime/base-classes/knowledge-entity-service.d.ts +15 -0
  46. package/dist/runtime/base-classes/knowledge-entity-service.js +192 -0
  47. package/dist/runtime/base-classes/knowledge-entity-service.js.map +1 -0
  48. package/dist/runtime/base-classes/lifecycle-events.d.ts +49 -0
  49. package/dist/runtime/base-classes/lifecycle-events.js +76 -0
  50. package/dist/runtime/base-classes/lifecycle-events.js.map +1 -0
  51. package/dist/runtime/base-classes/metadata-entity-repository.d.ts +27 -0
  52. package/dist/runtime/base-classes/metadata-entity-repository.js +212 -0
  53. package/dist/runtime/base-classes/metadata-entity-repository.js.map +1 -0
  54. package/dist/runtime/base-classes/metadata-entity-service.d.ts +39 -0
  55. package/dist/runtime/base-classes/metadata-entity-service.js +214 -0
  56. package/dist/runtime/base-classes/metadata-entity-service.js.map +1 -0
  57. package/dist/runtime/base-classes/synced-entity-repository.d.ts +32 -0
  58. package/dist/runtime/base-classes/synced-entity-repository.js +203 -0
  59. package/dist/runtime/base-classes/synced-entity-repository.js.map +1 -0
  60. package/dist/runtime/base-classes/synced-entity-service.d.ts +41 -0
  61. package/dist/runtime/base-classes/synced-entity-service.js +215 -0
  62. package/dist/runtime/base-classes/synced-entity-service.js.map +1 -0
  63. package/dist/runtime/base-classes/with-analytics.d.ts +18 -0
  64. package/dist/runtime/base-classes/with-analytics.js +11 -0
  65. package/dist/runtime/base-classes/with-analytics.js.map +1 -0
  66. package/dist/runtime/constants/tokens.d.ts +29 -0
  67. package/dist/runtime/constants/tokens.js +8 -0
  68. package/dist/runtime/constants/tokens.js.map +1 -0
  69. package/dist/runtime/subsystems/analytics/analytics-query.protocol.d.ts +30 -0
  70. package/dist/runtime/subsystems/analytics/analytics-query.protocol.js +1 -0
  71. package/dist/runtime/subsystems/analytics/analytics-query.protocol.js.map +1 -0
  72. package/dist/runtime/subsystems/analytics/analytics.module.d.ts +34 -0
  73. package/dist/runtime/subsystems/analytics/analytics.module.js +117 -0
  74. package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -0
  75. package/dist/runtime/subsystems/analytics/analytics.tokens.d.ts +24 -0
  76. package/dist/runtime/subsystems/analytics/analytics.tokens.js +10 -0
  77. package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -0
  78. package/dist/runtime/subsystems/analytics/cube-backend.d.ts +28 -0
  79. package/dist/runtime/subsystems/analytics/cube-backend.js +71 -0
  80. package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -0
  81. package/dist/runtime/subsystems/analytics/index.d.ts +6 -0
  82. package/dist/runtime/subsystems/analytics/index.js +122 -0
  83. package/dist/runtime/subsystems/analytics/index.js.map +1 -0
  84. package/dist/runtime/subsystems/analytics/noop-backend.d.ts +7 -0
  85. package/dist/runtime/subsystems/analytics/noop-backend.js +25 -0
  86. package/dist/runtime/subsystems/analytics/noop-backend.js.map +1 -0
  87. package/dist/runtime/subsystems/cache/cache.drizzle-backend.d.ts +43 -0
  88. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +133 -0
  89. package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -0
  90. package/dist/runtime/subsystems/cache/cache.memory-backend.d.ts +21 -0
  91. package/dist/runtime/subsystems/cache/cache.memory-backend.js +100 -0
  92. package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -0
  93. package/dist/runtime/subsystems/cache/cache.module.d.ts +37 -0
  94. package/dist/runtime/subsystems/cache/cache.module.js +272 -0
  95. package/dist/runtime/subsystems/cache/cache.module.js.map +1 -0
  96. package/dist/runtime/subsystems/cache/cache.protocol.d.ts +42 -0
  97. package/dist/runtime/subsystems/cache/cache.protocol.js +1 -0
  98. package/dist/runtime/subsystems/cache/cache.protocol.js.map +1 -0
  99. package/dist/runtime/subsystems/cache/cache.schema.d.ts +64 -0
  100. package/dist/runtime/subsystems/cache/cache.schema.js +18 -0
  101. package/dist/runtime/subsystems/cache/cache.schema.js.map +1 -0
  102. package/dist/runtime/subsystems/cache/cache.tokens.d.ts +18 -0
  103. package/dist/runtime/subsystems/cache/cache.tokens.js +8 -0
  104. package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -0
  105. package/dist/runtime/subsystems/cache/index.d.ts +11 -0
  106. package/dist/runtime/subsystems/cache/index.js +277 -0
  107. package/dist/runtime/subsystems/cache/index.js.map +1 -0
  108. package/dist/runtime/subsystems/events/domain-events.schema.d.ts +187 -0
  109. package/dist/runtime/subsystems/events/domain-events.schema.js +32 -0
  110. package/dist/runtime/subsystems/events/domain-events.schema.js.map +1 -0
  111. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +38 -0
  112. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +199 -0
  113. package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -0
  114. package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +18 -0
  115. package/dist/runtime/subsystems/events/event-bus.memory-backend.js +71 -0
  116. package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -0
  117. package/dist/runtime/subsystems/events/event-bus.protocol.d.ts +52 -0
  118. package/dist/runtime/subsystems/events/event-bus.protocol.js +1 -0
  119. package/dist/runtime/subsystems/events/event-bus.protocol.js.map +1 -0
  120. package/dist/runtime/subsystems/events/event-bus.redis-backend.d.ts +95 -0
  121. package/dist/runtime/subsystems/events/event-bus.redis-backend.js +229 -0
  122. package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -0
  123. package/dist/runtime/subsystems/events/events.module.d.ts +46 -0
  124. package/dist/runtime/subsystems/events/events.module.js +531 -0
  125. package/dist/runtime/subsystems/events/events.module.js.map +1 -0
  126. package/dist/runtime/subsystems/events/events.tokens.d.ts +19 -0
  127. package/dist/runtime/subsystems/events/events.tokens.js +8 -0
  128. package/dist/runtime/subsystems/events/events.tokens.js.map +1 -0
  129. package/dist/runtime/subsystems/events/index.d.ts +12 -0
  130. package/dist/runtime/subsystems/events/index.js +536 -0
  131. package/dist/runtime/subsystems/events/index.js.map +1 -0
  132. package/dist/runtime/subsystems/index.d.ts +24 -0
  133. package/dist/runtime/subsystems/index.js +1643 -0
  134. package/dist/runtime/subsystems/index.js.map +1 -0
  135. package/dist/runtime/subsystems/jobs/index.d.ts +14 -0
  136. package/dist/runtime/subsystems/jobs/index.js +680 -0
  137. package/dist/runtime/subsystems/jobs/index.js.map +1 -0
  138. package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.d.ts +54 -0
  139. package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.js +186 -0
  140. package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.js.map +1 -0
  141. package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.d.ts +38 -0
  142. package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.js +228 -0
  143. package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.js.map +1 -0
  144. package/dist/runtime/subsystems/jobs/job-queue.memory-backend.d.ts +12 -0
  145. package/dist/runtime/subsystems/jobs/job-queue.memory-backend.js +44 -0
  146. package/dist/runtime/subsystems/jobs/job-queue.memory-backend.js.map +1 -0
  147. package/dist/runtime/subsystems/jobs/job-queue.protocol.d.ts +48 -0
  148. package/dist/runtime/subsystems/jobs/job-queue.protocol.js +1 -0
  149. package/dist/runtime/subsystems/jobs/job-queue.protocol.js.map +1 -0
  150. package/dist/runtime/subsystems/jobs/job-queue.redis-backend.d.ts +46 -0
  151. package/dist/runtime/subsystems/jobs/job-queue.redis-backend.js +187 -0
  152. package/dist/runtime/subsystems/jobs/job-queue.redis-backend.js.map +1 -0
  153. package/dist/runtime/subsystems/jobs/job-queue.schema.d.ts +237 -0
  154. package/dist/runtime/subsystems/jobs/job-queue.schema.js +44 -0
  155. package/dist/runtime/subsystems/jobs/job-queue.schema.js.map +1 -0
  156. package/dist/runtime/subsystems/jobs/jobs.module.d.ts +18 -0
  157. package/dist/runtime/subsystems/jobs/jobs.module.js +676 -0
  158. package/dist/runtime/subsystems/jobs/jobs.module.js.map +1 -0
  159. package/dist/runtime/subsystems/jobs/jobs.tokens.d.ts +13 -0
  160. package/dist/runtime/subsystems/jobs/jobs.tokens.js +8 -0
  161. package/dist/runtime/subsystems/jobs/jobs.tokens.js.map +1 -0
  162. package/dist/runtime/subsystems/storage/index.d.ts +6 -0
  163. package/dist/runtime/subsystems/storage/index.js +204 -0
  164. package/dist/runtime/subsystems/storage/index.js.map +1 -0
  165. package/dist/runtime/subsystems/storage/storage.local-backend.d.ts +18 -0
  166. package/dist/runtime/subsystems/storage/storage.local-backend.js +108 -0
  167. package/dist/runtime/subsystems/storage/storage.local-backend.js.map +1 -0
  168. package/dist/runtime/subsystems/storage/storage.memory-backend.d.ts +28 -0
  169. package/dist/runtime/subsystems/storage/storage.memory-backend.js +72 -0
  170. package/dist/runtime/subsystems/storage/storage.memory-backend.js.map +1 -0
  171. package/dist/runtime/subsystems/storage/storage.module.d.ts +40 -0
  172. package/dist/runtime/subsystems/storage/storage.module.js +201 -0
  173. package/dist/runtime/subsystems/storage/storage.module.js.map +1 -0
  174. package/dist/runtime/subsystems/storage/storage.protocol.d.ts +69 -0
  175. package/dist/runtime/subsystems/storage/storage.protocol.js +1 -0
  176. package/dist/runtime/subsystems/storage/storage.protocol.js.map +1 -0
  177. package/dist/runtime/subsystems/storage/storage.tokens.d.ts +11 -0
  178. package/dist/runtime/subsystems/storage/storage.tokens.js +6 -0
  179. package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -0
  180. package/dist/runtime/subsystems/storage/storage.utils.d.ts +9 -0
  181. package/dist/runtime/subsystems/storage/storage.utils.js +18 -0
  182. package/dist/runtime/subsystems/storage/storage.utils.js.map +1 -0
  183. package/dist/runtime/types/drizzle.d.ts +17 -0
  184. package/dist/runtime/types/drizzle.js +1 -0
  185. package/dist/runtime/types/drizzle.js.map +1 -0
  186. package/dist/src/cli/index.d.ts +1 -0
  187. package/dist/src/cli/index.js +7365 -0
  188. package/dist/src/cli/index.js.map +1 -0
  189. package/dist/src/index.d.ts +2384 -0
  190. package/dist/src/index.js +2198 -0
  191. package/dist/src/index.js.map +1 -0
  192. package/package.json +114 -0
  193. package/templates/broadcast/new/backend-interface.ejs.t +47 -0
  194. package/templates/broadcast/new/bridge-listener.ejs.t +67 -0
  195. package/templates/broadcast/new/channel.ejs.t +77 -0
  196. package/templates/broadcast/new/index.ejs.t +21 -0
  197. package/templates/broadcast/new/memory-backend.ejs.t +87 -0
  198. package/templates/broadcast/new/module.ejs.t +57 -0
  199. package/templates/broadcast/new/prompt.js +268 -0
  200. package/templates/broadcast/new/websocket-backend.ejs.t +259 -0
  201. package/templates/entity/new/backend/application/commands/create.ejs.t +55 -0
  202. package/templates/entity/new/backend/application/commands/delete.ejs.t +45 -0
  203. package/templates/entity/new/backend/application/commands/grouped-index.ejs.t +149 -0
  204. package/templates/entity/new/backend/application/commands/index.ejs.t +15 -0
  205. package/templates/entity/new/backend/application/commands/update.ejs.t +58 -0
  206. package/templates/entity/new/backend/application/queries/declarative-queries.ejs.t +36 -0
  207. package/templates/entity/new/backend/application/queries/get-by-id.ejs.t +42 -0
  208. package/templates/entity/new/backend/application/queries/grouped-index.ejs.t +81 -0
  209. package/templates/entity/new/backend/application/queries/index.ejs.t +14 -0
  210. package/templates/entity/new/backend/application/queries/list.ejs.t +36 -0
  211. package/templates/entity/new/backend/application/schemas/_inject-index.ejs.t +7 -0
  212. package/templates/entity/new/backend/application/schemas/dto.ejs.t +45 -0
  213. package/templates/entity/new/backend/database/_inject-index.ejs.t +7 -0
  214. package/templates/entity/new/backend/database/electric-migration.ejs.t +21 -0
  215. package/templates/entity/new/backend/database/repository.ejs.t +450 -0
  216. package/templates/entity/new/backend/database/schema.ejs.t +248 -0
  217. package/templates/entity/new/backend/domain/_inject-index.ejs.t +12 -0
  218. package/templates/entity/new/backend/domain/entity.ejs.t +108 -0
  219. package/templates/entity/new/backend/domain/grouped-index.ejs.t +163 -0
  220. package/templates/entity/new/backend/domain/index.ejs.t +15 -0
  221. package/templates/entity/new/backend/domain/repository-interface.ejs.t +71 -0
  222. package/templates/entity/new/backend/modules/core/_ensure-anchor-tokens.ejs.t +10 -0
  223. package/templates/entity/new/backend/modules/core/_inject-token.ejs.t +7 -0
  224. package/templates/entity/new/backend/modules/core/module.ejs.t +67 -0
  225. package/templates/entity/new/backend/modules/trpc/module.ejs.t +67 -0
  226. package/templates/entity/new/backend/presentation/controller.ejs.t +201 -0
  227. package/templates/entity/new/clean-lite-ps/controller.ejs.t +37 -0
  228. package/templates/entity/new/clean-lite-ps/dto/create.ejs.t +17 -0
  229. package/templates/entity/new/clean-lite-ps/dto/output.ejs.t +25 -0
  230. package/templates/entity/new/clean-lite-ps/dto/update.ejs.t +11 -0
  231. package/templates/entity/new/clean-lite-ps/entity.ejs.t +52 -0
  232. package/templates/entity/new/clean-lite-ps/index.ejs.t +20 -0
  233. package/templates/entity/new/clean-lite-ps/module.ejs.t +43 -0
  234. package/templates/entity/new/clean-lite-ps/prompt-extension.js +617 -0
  235. package/templates/entity/new/clean-lite-ps/repository.ejs.t +62 -0
  236. package/templates/entity/new/clean-lite-ps/service.ejs.t +34 -0
  237. package/templates/entity/new/clean-lite-ps/use-cases/declarative-queries.ejs.t +34 -0
  238. package/templates/entity/new/clean-lite-ps/use-cases/find-by-id.ejs.t +16 -0
  239. package/templates/entity/new/clean-lite-ps/use-cases/list.ejs.t +16 -0
  240. package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +7 -0
  241. package/templates/entity/new/frontend/_inject-entities-import.ejs.t +7 -0
  242. package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +10 -0
  243. package/templates/entity/new/frontend/collections/_inject-index.ejs.t +9 -0
  244. package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +9 -0
  245. package/templates/entity/new/frontend/collections/collection.ejs.t +61 -0
  246. package/templates/entity/new/frontend/collections/collections-base.ejs.t +24 -0
  247. package/templates/entity/new/frontend/entity/collection.ejs.t +172 -0
  248. package/templates/entity/new/frontend/entity/combined.ejs.t +474 -0
  249. package/templates/entity/new/frontend/entity/fields.ejs.t +104 -0
  250. package/templates/entity/new/frontend/entity/hooks.ejs.t +73 -0
  251. package/templates/entity/new/frontend/entity/index.ejs.t +21 -0
  252. package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +84 -0
  253. package/templates/entity/new/frontend/entity/mutations.ejs.t +38 -0
  254. package/templates/entity/new/frontend/entity/types.ejs.t +59 -0
  255. package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +7 -0
  256. package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +7 -0
  257. package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +7 -0
  258. package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +9 -0
  259. package/templates/entity/new/frontend/store/_inject-collections.ejs.t +9 -0
  260. package/templates/entity/new/frontend/store/_inject-entity.ejs.t +9 -0
  261. package/templates/entity/new/frontend/store/_inject-import.ejs.t +9 -0
  262. package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +9 -0
  263. package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +10 -0
  264. package/templates/entity/new/frontend/store/hooks.ejs.t +72 -0
  265. package/templates/entity/new/frontend/unified-entity.ejs.t +28 -0
  266. package/templates/entity/new/prompt.js +1421 -0
  267. package/templates/relationship/new/controller.ejs.t +36 -0
  268. package/templates/relationship/new/dto/create.ejs.t +41 -0
  269. package/templates/relationship/new/dto/output.ejs.t +44 -0
  270. package/templates/relationship/new/dto/update.ejs.t +10 -0
  271. package/templates/relationship/new/entity.ejs.t +98 -0
  272. package/templates/relationship/new/index.ejs.t +19 -0
  273. package/templates/relationship/new/module.ejs.t +35 -0
  274. package/templates/relationship/new/prompt.js +682 -0
  275. package/templates/relationship/new/repository.ejs.t +54 -0
  276. package/templates/relationship/new/service.ejs.t +31 -0
  277. package/templates/relationship/new/use-cases/declarative-queries.ejs.t +34 -0
  278. package/templates/relationship/new/use-cases/find-by-id.ejs.t +16 -0
  279. package/templates/relationship/new/use-cases/list.ejs.t +16 -0
@@ -0,0 +1,2198 @@
1
+ // src/parser/load-entities.ts
2
+ import { readdirSync } from "fs";
3
+ import { join, resolve } from "path";
4
+
5
+ // src/utils/yaml-loader.ts
6
+ import { readFileSync, existsSync } from "fs";
7
+ import { parse as parseYaml } from "yaml";
8
+
9
+ // src/schema/entity-definition.schema.ts
10
+ import { z } from "zod";
11
+ var FieldTypeSchema = z.enum([
12
+ "string",
13
+ "integer",
14
+ "decimal",
15
+ "boolean",
16
+ "uuid",
17
+ "date",
18
+ "datetime",
19
+ "json",
20
+ "entity_ref",
21
+ // Polymorphic reference: generates {field}EntityType + {field}EntityId columns
22
+ "string_array",
23
+ // Array of strings: generates text[] column
24
+ "enum"
25
+ // Enum type with choices or choices_from
26
+ ]);
27
+ var UiTypeSchema = z.enum([
28
+ "text",
29
+ "textarea",
30
+ "number",
31
+ "money",
32
+ "percentage",
33
+ "email",
34
+ "url",
35
+ "date",
36
+ "datetime",
37
+ "boolean",
38
+ "enum",
39
+ "reference",
40
+ "json",
41
+ "badge",
42
+ "password"
43
+ ]);
44
+ var UiImportanceSchema = z.enum(["primary", "secondary", "tertiary"]);
45
+ var AnalyticsAggregationSchema = z.enum([
46
+ "sum",
47
+ "min",
48
+ "max",
49
+ "count",
50
+ "count_distinct",
51
+ "average",
52
+ "median",
53
+ "percentile",
54
+ "sum_boolean"
55
+ ]);
56
+ var AnalyticsDimensionTypeSchema = z.enum(["categorical", "time"]);
57
+ var AnalyticsEntityTypeSchema = z.enum(["primary", "unique", "foreign", "natural"]);
58
+ var AnalyticsTimeGranularitySchema = z.enum(["day", "week", "month", "quarter", "year"]);
59
+ var AnalyticsVisibilitySchema = z.enum(["internal", "agent", "public"]);
60
+ var NonAdditiveDimensionSchema = z.union([
61
+ z.string(),
62
+ z.object({
63
+ name: z.string(),
64
+ window_choice: z.string().optional(),
65
+ window_groupings: z.array(z.string()).optional()
66
+ })
67
+ ]);
68
+ var SemanticMetadataSchema = z.object({
69
+ measure: z.boolean().optional(),
70
+ analytics_aggregation: AnalyticsAggregationSchema.optional(),
71
+ agg_time_dimension: z.string().optional(),
72
+ non_additive_dimension: NonAdditiveDimensionSchema.optional(),
73
+ dimension: z.boolean().optional(),
74
+ dimension_type: AnalyticsDimensionTypeSchema.optional(),
75
+ time_granularity: AnalyticsTimeGranularitySchema.optional(),
76
+ is_partition: z.boolean().optional(),
77
+ entity: z.boolean().optional(),
78
+ entity_type: AnalyticsEntityTypeSchema.optional(),
79
+ entity_role: z.string().optional(),
80
+ analytics_visibility: AnalyticsVisibilitySchema.optional(),
81
+ semantic_expr: z.string().optional(),
82
+ semantic_label: z.string().optional()
83
+ });
84
+ var UiMetadataSchema = z.object({
85
+ ui_label: z.string().optional(),
86
+ ui_type: UiTypeSchema.optional(),
87
+ ui_importance: UiImportanceSchema.optional(),
88
+ ui_group: z.string().optional(),
89
+ ui_sortable: z.boolean().optional(),
90
+ ui_filterable: z.boolean().optional(),
91
+ ui_visible: z.boolean().optional(),
92
+ ui_placeholder: z.string().optional(),
93
+ ui_help: z.string().optional(),
94
+ ui_format: z.record(z.unknown()).optional()
95
+ });
96
+ var BaseFieldSchema = z.object({
97
+ type: FieldTypeSchema,
98
+ required: z.boolean().optional().default(false),
99
+ nullable: z.boolean().optional().default(false),
100
+ // String constraints
101
+ max_length: z.number().int().positive().optional(),
102
+ min_length: z.number().int().nonnegative().optional(),
103
+ // Numeric constraints
104
+ min: z.number().optional(),
105
+ max: z.number().optional(),
106
+ // Enum/choices (inline definition)
107
+ choices: z.array(z.string()).optional(),
108
+ // Enum/choices from external file (e.g., "relationship_types.yaml")
109
+ // Mutually exclusive with choices - parser loads file and extracts keys
110
+ choices_from: z.string().optional(),
111
+ // Entity reference: allowed entity types for polymorphic refs
112
+ // Required when type is 'entity_ref'
113
+ allowed_types: z.array(z.string()).optional(),
114
+ // Default value
115
+ default: z.unknown().optional(),
116
+ // Indexing
117
+ index: z.boolean().optional(),
118
+ unique: z.boolean().optional(),
119
+ // Foreign key reference (e.g., "accounts.id")
120
+ foreign_key: z.string().optional()
121
+ });
122
+ var FieldDefinitionSchema = BaseFieldSchema.merge(UiMetadataSchema).merge(SemanticMetadataSchema).refine((data) => !(data.required === true && data.nullable === true), {
123
+ message: "'required: true' and 'nullable: true' cannot both be set. A required field cannot be null.",
124
+ path: ["required"]
125
+ }).refine(
126
+ (data) => {
127
+ if (data.min_length !== void 0 && data.type !== "string") {
128
+ return false;
129
+ }
130
+ return true;
131
+ },
132
+ {
133
+ message: "'min_length' can only be used with type 'string'",
134
+ path: ["min_length"]
135
+ }
136
+ ).refine(
137
+ (data) => {
138
+ if (data.max_length !== void 0 && data.type !== "string") {
139
+ return false;
140
+ }
141
+ return true;
142
+ },
143
+ {
144
+ message: "'max_length' can only be used with type 'string'",
145
+ path: ["max_length"]
146
+ }
147
+ ).refine(
148
+ (data) => {
149
+ if (data.min !== void 0 && !["integer", "decimal"].includes(data.type)) {
150
+ return false;
151
+ }
152
+ return true;
153
+ },
154
+ {
155
+ message: "'min' can only be used with numeric types",
156
+ path: ["min"]
157
+ }
158
+ ).refine(
159
+ (data) => {
160
+ if (data.max !== void 0 && !["integer", "decimal"].includes(data.type)) {
161
+ return false;
162
+ }
163
+ return true;
164
+ },
165
+ {
166
+ message: "'max' can only be used with numeric types",
167
+ path: ["max"]
168
+ }
169
+ ).refine(
170
+ (data) => {
171
+ if (data.type === "entity_ref" && !data.allowed_types?.length) {
172
+ return false;
173
+ }
174
+ return true;
175
+ },
176
+ {
177
+ message: "'entity_ref' type requires 'allowed_types' to be specified",
178
+ path: ["allowed_types"]
179
+ }
180
+ ).refine(
181
+ (data) => {
182
+ if (data.allowed_types !== void 0 && data.type !== "entity_ref") {
183
+ return false;
184
+ }
185
+ return true;
186
+ },
187
+ {
188
+ message: "'allowed_types' can only be used with type 'entity_ref'",
189
+ path: ["allowed_types"]
190
+ }
191
+ ).refine(
192
+ (data) => {
193
+ if (data.choices !== void 0 && data.choices_from !== void 0) {
194
+ return false;
195
+ }
196
+ return true;
197
+ },
198
+ {
199
+ message: "'choices' and 'choices_from' cannot both be specified",
200
+ path: ["choices_from"]
201
+ }
202
+ ).refine(
203
+ (data) => {
204
+ if (data.type === "enum" && !data.choices?.length && !data.choices_from) {
205
+ return false;
206
+ }
207
+ return true;
208
+ },
209
+ {
210
+ message: "'enum' type requires either 'choices' or 'choices_from'",
211
+ path: ["choices"]
212
+ }
213
+ ).refine(
214
+ (data) => {
215
+ if (data.measure === true && !data.analytics_aggregation) {
216
+ return false;
217
+ }
218
+ return true;
219
+ },
220
+ {
221
+ message: "When 'measure' is true, 'analytics_aggregation' must be specified",
222
+ path: ["analytics_aggregation"]
223
+ }
224
+ );
225
+ var RelationshipTypeSchema = z.enum(["belongs_to", "has_many", "has_one"]);
226
+ var RelationshipSchema = z.object({
227
+ type: RelationshipTypeSchema,
228
+ target: z.string(),
229
+ // Target entity name (e.g., "account")
230
+ foreign_key: z.string(),
231
+ // FK field name (e.g., "account_id")
232
+ through: z.string().optional(),
233
+ // For transitive: "owned_opportunities.updates"
234
+ inverse: z.string().optional()
235
+ // Name of inverse relationship on target entity
236
+ }).strict();
237
+ var BehaviorConfigSchema = z.union([
238
+ z.string(),
239
+ z.object({
240
+ name: z.string(),
241
+ options: z.record(z.unknown()).optional()
242
+ })
243
+ ]);
244
+ var BehaviorStrategySchema = z.enum(["base_class", "inline"]);
245
+ var FolderStructureSchema = z.enum(["nested", "flat"]).default("nested");
246
+ var FileGroupingSchema = z.enum(["separate", "grouped"]).default("separate");
247
+ var ExposeLayerSchema = z.enum(["repository", "rest", "trpc", "electric"]);
248
+ var EntityConfigSchema = z.object({
249
+ name: z.string().regex(
250
+ /^[a-z][a-z0-9_]*$/,
251
+ "Entity name must be lowercase with underscores (e.g., 'opportunity')"
252
+ ),
253
+ plural: z.string().regex(/^[a-z][a-z0-9_]*$/, "Plural must be lowercase"),
254
+ table: z.string().regex(/^[a-z][a-z0-9_]*$/, "Table must be lowercase"),
255
+ // Layout options (orthogonal concerns)
256
+ // folder_structure: controls directory nesting
257
+ // file_grouping: controls file organization
258
+ folder_structure: FolderStructureSchema.optional(),
259
+ file_grouping: FileGroupingSchema.optional(),
260
+ // Per-entity behavior strategy override (overrides codegen.config.yaml)
261
+ behavior_strategy: BehaviorStrategySchema.optional(),
262
+ // Which layers to generate (default: all)
263
+ expose: z.array(ExposeLayerSchema).optional().default(["repository", "rest", "trpc"]),
264
+ // v2: Entity family classification (ADR-005)
265
+ // Determines which base class hierarchy the entity inherits from
266
+ family: z.enum(["base", "synced", "activity", "knowledge", "metadata"]).optional()
267
+ }).strict();
268
+ var QueryDeclarationSchema = z.object({
269
+ by: z.array(z.string()).min(1),
270
+ unique: z.boolean().optional(),
271
+ select: z.array(z.string()).optional(),
272
+ order: z.string().optional(),
273
+ limit: z.boolean().optional(),
274
+ via: z.string().optional()
275
+ });
276
+ var SyncDirectionSchema = z.enum([
277
+ "inbound",
278
+ "outbound",
279
+ "bidirectional"
280
+ ]);
281
+ var ProviderSyncSchema = z.object({
282
+ remote_entity: z.string(),
283
+ direction: SyncDirectionSchema,
284
+ cdc: z.boolean().optional().default(false),
285
+ field_mapping: z.record(z.string(), z.string()).optional(),
286
+ read_only_fields: z.array(z.string()).optional()
287
+ });
288
+ var SyncConfigSchema = z.object({
289
+ electric: z.boolean().optional().default(false),
290
+ providers: z.record(z.string(), ProviderSyncSchema).optional()
291
+ });
292
+ var EventDeclarationSchema = z.object({
293
+ name: z.string().regex(/^[a-z][a-z0-9_]*$/, "Event name must be snake_case"),
294
+ queue: z.string(),
295
+ body: z.record(z.string(), z.string()),
296
+ generate_handler: z.boolean().optional().default(false)
297
+ });
298
+ var SimpleMetricSchema = z.object({
299
+ type: z.literal("simple"),
300
+ measure: z.string(),
301
+ agg: AnalyticsAggregationSchema.optional(),
302
+ filter: z.string().optional(),
303
+ description: z.string().optional(),
304
+ label: z.string().optional()
305
+ });
306
+ var DerivedMetricSchema = z.object({
307
+ type: z.literal("derived"),
308
+ expr: z.string(),
309
+ metrics: z.array(z.string()),
310
+ description: z.string().optional(),
311
+ label: z.string().optional()
312
+ });
313
+ var RatioMetricSchema = z.object({
314
+ type: z.literal("ratio"),
315
+ numerator: z.union([z.string(), SimpleMetricSchema]),
316
+ denominator: z.union([z.string(), SimpleMetricSchema]),
317
+ filter: z.string().optional(),
318
+ description: z.string().optional(),
319
+ label: z.string().optional()
320
+ });
321
+ var CumulativeMetricSchema = z.object({
322
+ type: z.literal("cumulative"),
323
+ measure: z.string(),
324
+ window: z.string().optional(),
325
+ grain_to_date: AnalyticsTimeGranularitySchema.optional(),
326
+ description: z.string().optional(),
327
+ label: z.string().optional()
328
+ });
329
+ var MetricDefinitionSchema = z.discriminatedUnion("type", [
330
+ SimpleMetricSchema,
331
+ DerivedMetricSchema,
332
+ RatioMetricSchema,
333
+ CumulativeMetricSchema
334
+ ]);
335
+ var AnalyticsBlockSchema = z.object({
336
+ measure_packs: z.array(z.string()).optional(),
337
+ cube_name: z.string().optional(),
338
+ metrics: z.record(z.string(), MetricDefinitionSchema).optional()
339
+ });
340
+ var EntityDefinitionSchema = z.object({
341
+ entity: EntityConfigSchema,
342
+ fields: z.record(z.string(), FieldDefinitionSchema),
343
+ relationships: z.record(z.string(), RelationshipSchema).optional(),
344
+ // Behaviors add cross-cutting concerns (timestamps, soft_delete, user_tracking, etc.)
345
+ behaviors: z.array(BehaviorConfigSchema).optional().default([]),
346
+ // v2: Declarative query generation (ADR-005)
347
+ // Generates repository + service + use case methods from declarations
348
+ queries: z.array(QueryDeclarationSchema).optional(),
349
+ // v2: Integration sync configuration (CODEGEN-EVOLUTION-PLAN Phase 2)
350
+ // Electric SQL + provider sync (Salesforce, HubSpot, etc.)
351
+ sync: SyncConfigSchema.optional(),
352
+ // v2: Domain event declarations (CODEGEN-EVOLUTION-PLAN Phase 2)
353
+ // Generates typed event classes, handlers, and queue registration
354
+ events: z.array(EventDeclarationSchema).optional(),
355
+ // v2: Analytics / semantic layer configuration
356
+ // Cube.js measure packs, custom cube name, and metric definitions
357
+ analytics: AnalyticsBlockSchema.optional()
358
+ }).strict();
359
+
360
+ // src/schema/relationship-definition.schema.ts
361
+ import { z as z2 } from "zod";
362
+ var TypeDirectionSchema = z2.object({
363
+ /** Name of the inverse type when viewed from the other direction */
364
+ inverse: z2.string().optional(),
365
+ /** Both directions are equivalent — queries should check both FK columns */
366
+ bidirectional: z2.boolean().optional(),
367
+ /** Explicitly directed, no named inverse (default behavior) */
368
+ directed: z2.boolean().optional()
369
+ }).refine(
370
+ (data) => {
371
+ const set = [data.inverse, data.bidirectional, data.directed].filter(
372
+ (v) => v !== void 0
373
+ );
374
+ return set.length === 1;
375
+ },
376
+ {
377
+ message: "Exactly one of inverse, bidirectional, or directed must be specified"
378
+ }
379
+ );
380
+ var RelationshipTypesSchema = z2.union([
381
+ // Simple list: all types are directed from→to
382
+ z2.array(z2.string().regex(/^[a-z][a-z0-9_]*$/, "Type must be snake_case")),
383
+ // Object map: each type has direction metadata
384
+ z2.record(
385
+ z2.string().regex(/^[a-z][a-z0-9_]*$/, "Type key must be snake_case"),
386
+ TypeDirectionSchema
387
+ )
388
+ ]);
389
+ var OnDeleteActionSchema = z2.enum(["restrict", "cascade", "set_null", "no_action"]).default("restrict");
390
+ var RelationshipConfigSchema = z2.object({
391
+ /** Relationship name (snake_case). Used for class/file naming. */
392
+ name: z2.string().regex(
393
+ /^[a-z][a-z0-9_]*$/,
394
+ "Relationship name must be snake_case"
395
+ ),
396
+ /** Database table name. Defaults to {name}s if not specified. */
397
+ table: z2.string().regex(/^[a-z][a-z0-9_]*$/, "Table must be snake_case").optional(),
398
+ /** The "from" entity — generates {entity}_id FK column (subject). */
399
+ from: z2.string().regex(/^[a-z][a-z0-9_]*$/, "Entity name must be snake_case"),
400
+ /** The "to" entity — generates {entity}_id FK column (object). */
401
+ to: z2.string().regex(/^[a-z][a-z0-9_]*$/, "Entity name must be snake_case"),
402
+ /**
403
+ * Relationship subtypes. Optional — omit for untyped junctions.
404
+ * When present, generates a `type` enum column on the junction table.
405
+ *
406
+ * Simple list: all types are directed (from→to). Use for cross-type
407
+ * relationships where entity asymmetry makes direction obvious.
408
+ *
409
+ * Object map: each type declares its own direction metadata.
410
+ * Required for self-referential relationships (from === to).
411
+ */
412
+ types: RelationshipTypesSchema.optional(),
413
+ /**
414
+ * Generate temporal validity fields: valid_from (date), valid_to (date?),
415
+ * is_current (boolean, denormalized for query performance).
416
+ * Default: true
417
+ */
418
+ temporal: z2.boolean().default(true),
419
+ /**
420
+ * Generate source tracking fields: source (enum), confidence (decimal 0-1).
421
+ * Default: true
422
+ */
423
+ sourced: z2.boolean().default(true),
424
+ /** on_delete action for the "from" endpoint FK. Default: restrict */
425
+ on_delete_from: OnDeleteActionSchema.optional(),
426
+ /** on_delete action for the "to" endpoint FK. Default: restrict */
427
+ on_delete_to: OnDeleteActionSchema.optional(),
428
+ /**
429
+ * Override the default unique constraint columns.
430
+ *
431
+ * Defaults:
432
+ * - Typed: [from_id, to_id, type]
433
+ * - Typed + temporal: [from_id, to_id, type, valid_from]
434
+ * - Untyped: [from_id, to_id]
435
+ *
436
+ * Use this when the default doesn't fit — e.g., allowing multiple
437
+ * relationships of the same type between the same entities at different times.
438
+ */
439
+ unique_on: z2.array(z2.string()).optional()
440
+ }).strict();
441
+ var RelationshipQuerySchema = z2.object({
442
+ by: z2.array(z2.string()).min(1),
443
+ unique: z2.boolean().optional(),
444
+ select: z2.array(z2.string()).optional(),
445
+ order: z2.string().optional(),
446
+ limit: z2.boolean().optional()
447
+ });
448
+ var RelationshipDefinitionSchema = z2.object({
449
+ /** Relationship configuration block */
450
+ relationship: RelationshipConfigSchema,
451
+ /**
452
+ * Additional fields beyond auto-generated ones.
453
+ * These describe the relationship, not either endpoint entity.
454
+ * Uses the same field definition schema as entity fields.
455
+ */
456
+ fields: z2.record(z2.string(), z2.any()).optional(),
457
+ /** Declarative queries — same syntax as entity queries. */
458
+ queries: z2.array(RelationshipQuerySchema).optional()
459
+ }).strict().refine(
460
+ (data) => {
461
+ if (data.relationship.from === data.relationship.to && data.relationship.types) {
462
+ return !Array.isArray(data.relationship.types);
463
+ }
464
+ return true;
465
+ },
466
+ {
467
+ message: "Self-referential relationships must use the object map form for types (with inverse/bidirectional/directed metadata), not a simple list",
468
+ path: ["relationship", "types"]
469
+ }
470
+ ).refine(
471
+ (data) => {
472
+ if (!data.fields) return true;
473
+ const reserved = getReservedColumnNames(data.relationship);
474
+ const collisions = Object.keys(data.fields).filter(
475
+ (key) => reserved.has(key)
476
+ );
477
+ return collisions.length === 0;
478
+ },
479
+ {
480
+ 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).",
481
+ path: ["fields"]
482
+ }
483
+ );
484
+ function getReservedColumnNames(config) {
485
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
486
+ const reserved = /* @__PURE__ */ new Set([
487
+ "id",
488
+ "created_at",
489
+ "updated_at",
490
+ fromColumn,
491
+ toColumn
492
+ ]);
493
+ if (config.types) {
494
+ reserved.add("type");
495
+ }
496
+ if (config.temporal) {
497
+ reserved.add("valid_from");
498
+ reserved.add("valid_to");
499
+ reserved.add("is_current");
500
+ }
501
+ if (config.sourced) {
502
+ reserved.add("source");
503
+ reserved.add("confidence");
504
+ }
505
+ return reserved;
506
+ }
507
+ function deriveRelationshipFKColumns(config) {
508
+ if (config.from === config.to) {
509
+ return {
510
+ fromColumn: `from_${config.from}_id`,
511
+ toColumn: `to_${config.to}_id`
512
+ };
513
+ }
514
+ return {
515
+ fromColumn: `${config.from}_id`,
516
+ toColumn: `${config.to}_id`
517
+ };
518
+ }
519
+ function deriveTableName(config) {
520
+ return config.table ?? `${config.name}s`;
521
+ }
522
+ function deriveUniqueConstraint(config) {
523
+ if (config.unique_on) return config.unique_on;
524
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
525
+ const columns = [fromColumn, toColumn];
526
+ if (config.types) {
527
+ columns.push("type");
528
+ }
529
+ if (config.temporal && config.types) {
530
+ columns.push("valid_from");
531
+ }
532
+ return columns;
533
+ }
534
+
535
+ // src/utils/yaml-loader.ts
536
+ function loadEntityFromYaml(filePath) {
537
+ if (!existsSync(filePath)) {
538
+ return {
539
+ success: false,
540
+ error: `File not found: ${filePath}`,
541
+ filePath
542
+ };
543
+ }
544
+ let content;
545
+ try {
546
+ content = readFileSync(filePath, "utf-8");
547
+ } catch (err) {
548
+ return {
549
+ success: false,
550
+ error: `Failed to read file: ${filePath}`,
551
+ details: [err instanceof Error ? err.message : String(err)],
552
+ filePath
553
+ };
554
+ }
555
+ let parsed;
556
+ try {
557
+ parsed = parseYaml(content);
558
+ } catch (err) {
559
+ return {
560
+ success: false,
561
+ error: `Invalid YAML syntax in ${filePath}`,
562
+ details: [err instanceof Error ? err.message : String(err)],
563
+ filePath
564
+ };
565
+ }
566
+ const result = EntityDefinitionSchema.safeParse(parsed);
567
+ if (!result.success) {
568
+ return {
569
+ success: false,
570
+ error: `Validation failed for ${filePath}`,
571
+ details: formatZodErrors(result.error),
572
+ filePath
573
+ };
574
+ }
575
+ return {
576
+ success: true,
577
+ definition: result.data,
578
+ filePath
579
+ };
580
+ }
581
+ function formatZodErrors(error) {
582
+ return error.errors.map((err) => {
583
+ const path = err.path.join(".");
584
+ const location = path ? `at '${path}'` : "at root";
585
+ return `${err.message} ${location}`;
586
+ });
587
+ }
588
+ function loadRelationshipFromYaml(filePath) {
589
+ if (!existsSync(filePath)) {
590
+ return {
591
+ success: false,
592
+ error: `File not found: ${filePath}`,
593
+ filePath
594
+ };
595
+ }
596
+ let content;
597
+ try {
598
+ content = readFileSync(filePath, "utf-8");
599
+ } catch (err) {
600
+ return {
601
+ success: false,
602
+ error: `Failed to read file: ${filePath}`,
603
+ details: [err instanceof Error ? err.message : String(err)],
604
+ filePath
605
+ };
606
+ }
607
+ let parsed;
608
+ try {
609
+ parsed = parseYaml(content);
610
+ } catch (err) {
611
+ return {
612
+ success: false,
613
+ error: `Invalid YAML syntax in ${filePath}`,
614
+ details: [err instanceof Error ? err.message : String(err)],
615
+ filePath
616
+ };
617
+ }
618
+ const result = RelationshipDefinitionSchema.safeParse(parsed);
619
+ if (!result.success) {
620
+ return {
621
+ success: false,
622
+ error: `Validation failed for ${filePath}`,
623
+ details: formatZodErrors(result.error),
624
+ filePath
625
+ };
626
+ }
627
+ return {
628
+ success: true,
629
+ definition: result.data,
630
+ filePath
631
+ };
632
+ }
633
+
634
+ // src/parser/load-entities.ts
635
+ function transformToEntity(result) {
636
+ const { definition, filePath } = result;
637
+ const queries = definition.queries?.map((q) => ({
638
+ by: q.by,
639
+ unique: q.unique,
640
+ select: q.select,
641
+ order: q.order,
642
+ limit: q.limit,
643
+ via: q.via
644
+ }));
645
+ const entity = {
646
+ name: definition.entity.name,
647
+ plural: definition.entity.plural,
648
+ table: definition.entity.table,
649
+ family: definition.entity.family,
650
+ folderStructure: definition.entity.folder_structure ?? "nested",
651
+ fields: /* @__PURE__ */ new Map(),
652
+ relationships: /* @__PURE__ */ new Map(),
653
+ behaviors: definition.behaviors.map((b) => typeof b === "string" ? b : b.name),
654
+ queries,
655
+ sourcePath: filePath
656
+ };
657
+ for (const [name, fieldDef] of Object.entries(definition.fields)) {
658
+ const field = {
659
+ name,
660
+ type: fieldDef.type,
661
+ required: fieldDef.required ?? false,
662
+ nullable: fieldDef.nullable ?? false,
663
+ unique: fieldDef.unique ?? false,
664
+ index: fieldDef.index ?? false,
665
+ foreignKey: fieldDef.foreign_key ? parseForeignKey(fieldDef.foreign_key) : void 0,
666
+ choices: fieldDef.choices,
667
+ constraints: {
668
+ minLength: fieldDef.min_length,
669
+ maxLength: fieldDef.max_length,
670
+ min: fieldDef.min,
671
+ max: fieldDef.max
672
+ },
673
+ ui: {
674
+ label: fieldDef.ui_label,
675
+ type: fieldDef.ui_type,
676
+ importance: fieldDef.ui_importance,
677
+ group: fieldDef.ui_group,
678
+ sortable: fieldDef.ui_sortable,
679
+ filterable: fieldDef.ui_filterable,
680
+ visible: fieldDef.ui_visible
681
+ }
682
+ };
683
+ entity.fields.set(name, field);
684
+ }
685
+ if (definition.relationships) {
686
+ for (const [name, relDef] of Object.entries(definition.relationships)) {
687
+ const relationship = {
688
+ name,
689
+ type: relDef.type,
690
+ target: relDef.target,
691
+ foreignKey: relDef.foreign_key,
692
+ inverse: relDef.inverse,
693
+ through: relDef.through,
694
+ resolved: false
695
+ };
696
+ entity.relationships.set(name, relationship);
697
+ }
698
+ }
699
+ if (definition.sync) {
700
+ const syncDef = definition.sync;
701
+ const parsedSync = {
702
+ electric: syncDef.electric ?? false
703
+ };
704
+ if (syncDef.providers) {
705
+ parsedSync.providers = {};
706
+ for (const [providerName, providerDef] of Object.entries(syncDef.providers)) {
707
+ const parsedProvider = {
708
+ remoteEntity: providerDef.remote_entity,
709
+ direction: providerDef.direction,
710
+ cdc: providerDef.cdc ?? false
711
+ };
712
+ if (providerDef.field_mapping) {
713
+ parsedProvider.fieldMapping = providerDef.field_mapping;
714
+ }
715
+ if (providerDef.read_only_fields) {
716
+ parsedProvider.readOnlyFields = providerDef.read_only_fields;
717
+ }
718
+ parsedSync.providers[providerName] = parsedProvider;
719
+ }
720
+ }
721
+ entity.sync = parsedSync;
722
+ }
723
+ if (definition.events) {
724
+ entity.events = definition.events.map((ev) => ({
725
+ name: ev.name,
726
+ queue: ev.queue,
727
+ body: ev.body,
728
+ generateHandler: ev.generate_handler
729
+ }));
730
+ }
731
+ return entity;
732
+ }
733
+ function parseForeignKey(fk) {
734
+ const [table, column] = fk.split(".");
735
+ return { table, column: column ?? "id" };
736
+ }
737
+ function loadErrorToIssue(error) {
738
+ const issues = [];
739
+ issues.push({
740
+ severity: "error",
741
+ type: "parse_error",
742
+ message: error.error,
743
+ path: error.filePath
744
+ });
745
+ if (error.details) {
746
+ for (const detail of error.details) {
747
+ issues.push({
748
+ severity: "error",
749
+ type: "schema_error",
750
+ message: detail,
751
+ path: error.filePath
752
+ });
753
+ }
754
+ }
755
+ return issues;
756
+ }
757
+ function loadEntities(entitiesDir) {
758
+ const entities = [];
759
+ const issues = [];
760
+ const resolvedDir = resolve(entitiesDir);
761
+ let files;
762
+ try {
763
+ files = readdirSync(resolvedDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).map((f) => join(resolvedDir, f));
764
+ } catch (err) {
765
+ issues.push({
766
+ severity: "error",
767
+ type: "parse_error",
768
+ message: `Failed to read directory: ${resolvedDir}`,
769
+ path: resolvedDir
770
+ });
771
+ return { entities, issues };
772
+ }
773
+ if (files.length === 0) {
774
+ issues.push({
775
+ severity: "warning",
776
+ type: "no_files",
777
+ message: `No YAML files found in directory: ${resolvedDir}`,
778
+ path: resolvedDir
779
+ });
780
+ return { entities, issues };
781
+ }
782
+ for (const filePath of files) {
783
+ const result = loadEntityFromYaml(filePath);
784
+ if (result.success) {
785
+ entities.push(transformToEntity(result));
786
+ } else {
787
+ issues.push(...loadErrorToIssue(result));
788
+ }
789
+ }
790
+ return { entities, issues };
791
+ }
792
+ function resolveReferences(entities) {
793
+ const issues = [];
794
+ const entityMap = /* @__PURE__ */ new Map();
795
+ for (const entity of entities) {
796
+ if (entityMap.has(entity.name)) {
797
+ issues.push({
798
+ severity: "error",
799
+ type: "duplicate_entity",
800
+ entity: entity.name,
801
+ message: `Duplicate entity name: ${entity.name}`,
802
+ path: entity.sourcePath
803
+ });
804
+ }
805
+ entityMap.set(entity.name, entity);
806
+ }
807
+ for (const entity of entities) {
808
+ for (const [relName, rel] of entity.relationships) {
809
+ const targetEntity = entityMap.get(rel.target);
810
+ if (targetEntity) {
811
+ rel.resolved = true;
812
+ } else {
813
+ issues.push({
814
+ severity: "error",
815
+ type: "missing_target",
816
+ entity: entity.name,
817
+ field: relName,
818
+ message: `Relationship '${relName}' references unknown entity '${rel.target}'`,
819
+ path: entity.sourcePath,
820
+ suggestion: `Define entity '${rel.target}' or fix the target name`
821
+ });
822
+ }
823
+ }
824
+ for (const [fieldName, field] of entity.fields) {
825
+ if (field.foreignKey) {
826
+ const targetTable = field.foreignKey.table;
827
+ const targetEntity = Array.from(entityMap.values()).find(
828
+ (e) => e.table === targetTable
829
+ );
830
+ if (!targetEntity) {
831
+ issues.push({
832
+ severity: "warning",
833
+ type: "missing_fk_target",
834
+ entity: entity.name,
835
+ field: fieldName,
836
+ message: `Foreign key references unknown table '${targetTable}'`,
837
+ path: entity.sourcePath,
838
+ suggestion: `Define entity with table '${targetTable}' or fix the foreign_key reference`
839
+ });
840
+ }
841
+ }
842
+ }
843
+ }
844
+ return issues;
845
+ }
846
+ function transformToRelationshipDefinition(result) {
847
+ const { definition, filePath } = result;
848
+ const config = definition.relationship;
849
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
850
+ const table = deriveTableName(config);
851
+ const uniqueOn = deriveUniqueConstraint(config);
852
+ const types = resolveTypeDirections(config.types);
853
+ const fields = /* @__PURE__ */ new Map();
854
+ if (definition.fields) {
855
+ for (const [name, fieldDef] of Object.entries(definition.fields)) {
856
+ const field = {
857
+ name,
858
+ type: fieldDef.type,
859
+ required: fieldDef.required ?? false,
860
+ nullable: fieldDef.nullable ?? false,
861
+ unique: fieldDef.unique ?? false,
862
+ index: fieldDef.index ?? false,
863
+ foreignKey: fieldDef.foreign_key ? parseForeignKey(fieldDef.foreign_key) : void 0,
864
+ choices: fieldDef.choices,
865
+ constraints: {
866
+ minLength: fieldDef.min_length,
867
+ maxLength: fieldDef.max_length,
868
+ min: fieldDef.min,
869
+ max: fieldDef.max
870
+ },
871
+ ui: {
872
+ label: fieldDef.ui_label,
873
+ type: fieldDef.ui_type,
874
+ importance: fieldDef.ui_importance,
875
+ group: fieldDef.ui_group,
876
+ sortable: fieldDef.ui_sortable,
877
+ filterable: fieldDef.ui_filterable,
878
+ visible: fieldDef.ui_visible
879
+ }
880
+ };
881
+ fields.set(name, field);
882
+ }
883
+ }
884
+ const queries = definition.queries?.map((q) => ({
885
+ by: q.by,
886
+ unique: q.unique,
887
+ select: q.select,
888
+ order: q.order,
889
+ limit: q.limit
890
+ }));
891
+ return {
892
+ name: config.name,
893
+ table,
894
+ from: config.from,
895
+ to: config.to,
896
+ selfReferential: config.from === config.to,
897
+ fromColumn,
898
+ toColumn,
899
+ types,
900
+ hasTypes: types.length > 0,
901
+ temporal: config.temporal,
902
+ sourced: config.sourced,
903
+ onDeleteFrom: config.on_delete_from ?? "restrict",
904
+ onDeleteTo: config.on_delete_to ?? "restrict",
905
+ uniqueOn,
906
+ fields,
907
+ queries,
908
+ sourcePath: filePath
909
+ };
910
+ }
911
+ function resolveTypeDirections(types) {
912
+ if (!types) return [];
913
+ if (Array.isArray(types)) {
914
+ return types.map((name) => ({
915
+ name,
916
+ bidirectional: false,
917
+ directed: true
918
+ }));
919
+ }
920
+ return Object.entries(types).map(([name, dir]) => {
921
+ const direction = dir;
922
+ return {
923
+ name,
924
+ inverse: direction.inverse,
925
+ bidirectional: direction.bidirectional ?? false,
926
+ directed: direction.directed ?? (!direction.bidirectional && !direction.inverse)
927
+ };
928
+ });
929
+ }
930
+ function loadRelationships(relationshipsDir) {
931
+ const relationships = [];
932
+ const issues = [];
933
+ const resolvedDir = resolve(relationshipsDir);
934
+ let files;
935
+ try {
936
+ files = readdirSync(resolvedDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).map((f) => join(resolvedDir, f));
937
+ } catch {
938
+ return { relationships, issues };
939
+ }
940
+ if (files.length === 0) {
941
+ return { relationships, issues };
942
+ }
943
+ for (const filePath of files) {
944
+ const result = loadRelationshipFromYaml(filePath);
945
+ if (result.success) {
946
+ relationships.push(transformToRelationshipDefinition(result));
947
+ } else {
948
+ issues.push(...loadErrorToIssue(result));
949
+ }
950
+ }
951
+ return { relationships, issues };
952
+ }
953
+ function resolveRelationshipReferences(relationshipDefs, entities) {
954
+ const issues = [];
955
+ const entityNames = new Set(entities.map((e) => e.name));
956
+ for (const relDef of relationshipDefs) {
957
+ if (!entityNames.has(relDef.from)) {
958
+ issues.push({
959
+ severity: "warning",
960
+ type: "missing_relationship_endpoint",
961
+ entity: relDef.name,
962
+ message: `Relationship '${relDef.name}' references unknown 'from' entity '${relDef.from}'`,
963
+ path: relDef.sourcePath,
964
+ suggestion: `Define entity '${relDef.from}' or fix the 'from' value`
965
+ });
966
+ }
967
+ if (!entityNames.has(relDef.to)) {
968
+ issues.push({
969
+ severity: "warning",
970
+ type: "missing_relationship_endpoint",
971
+ entity: relDef.name,
972
+ message: `Relationship '${relDef.name}' references unknown 'to' entity '${relDef.to}'`,
973
+ path: relDef.sourcePath,
974
+ suggestion: `Define entity '${relDef.to}' or fix the 'to' value`
975
+ });
976
+ }
977
+ const dupes = relationshipDefs.filter((r) => r.name === relDef.name);
978
+ if (dupes.length > 1) {
979
+ issues.push({
980
+ severity: "error",
981
+ type: "duplicate_relationship",
982
+ entity: relDef.name,
983
+ message: `Duplicate relationship name: ${relDef.name}`,
984
+ path: relDef.sourcePath
985
+ });
986
+ }
987
+ }
988
+ return issues;
989
+ }
990
+
991
+ // src/analyzer/graph-builder.ts
992
+ function inferCardinality(type) {
993
+ switch (type) {
994
+ case "belongs_to":
995
+ return "N:1";
996
+ case "has_many":
997
+ return "1:N";
998
+ case "has_one":
999
+ return "1:1";
1000
+ default:
1001
+ return "1:N";
1002
+ }
1003
+ }
1004
+ function hasReverseEdge(edges, from, to) {
1005
+ return edges.find((e) => e.from === to && e.to === from);
1006
+ }
1007
+ function buildDomainGraph(entities, relationshipDefinitions = []) {
1008
+ const entityMap = /* @__PURE__ */ new Map();
1009
+ const relDefMap = /* @__PURE__ */ new Map();
1010
+ const edges = [];
1011
+ for (const entity of entities) {
1012
+ entityMap.set(entity.name, entity);
1013
+ }
1014
+ for (const relDef of relationshipDefinitions) {
1015
+ relDefMap.set(relDef.name, relDef);
1016
+ }
1017
+ for (const entity of entities) {
1018
+ for (const [relName, rel] of entity.relationships) {
1019
+ if (!rel.resolved) continue;
1020
+ const reverseEdge = hasReverseEdge(edges, entity.name, rel.target);
1021
+ const edge = {
1022
+ from: entity.name,
1023
+ to: rel.target,
1024
+ relationship: rel,
1025
+ cardinality: inferCardinality(rel.type),
1026
+ bidirectional: reverseEdge !== void 0
1027
+ };
1028
+ if (reverseEdge) {
1029
+ reverseEdge.bidirectional = true;
1030
+ }
1031
+ edges.push(edge);
1032
+ }
1033
+ }
1034
+ for (const relDef of relationshipDefinitions) {
1035
+ const fromExists = entityMap.has(relDef.from);
1036
+ const toExists = entityMap.has(relDef.to);
1037
+ if (fromExists && toExists) {
1038
+ const edge = {
1039
+ from: relDef.from,
1040
+ to: relDef.to,
1041
+ relationship: {
1042
+ name: relDef.name,
1043
+ type: "has_many",
1044
+ target: relDef.to,
1045
+ foreignKey: relDef.fromColumn,
1046
+ resolved: true
1047
+ },
1048
+ cardinality: "N:M",
1049
+ bidirectional: relDef.types.some((t) => t.bidirectional)
1050
+ };
1051
+ edges.push(edge);
1052
+ }
1053
+ }
1054
+ return { entities: entityMap, relationshipDefinitions: relDefMap, edges };
1055
+ }
1056
+ function getRelatedEntities(graph, entityName, depth = 1) {
1057
+ const related = /* @__PURE__ */ new Set();
1058
+ const visited = /* @__PURE__ */ new Set();
1059
+ const queue = [
1060
+ { name: entityName, currentDepth: 0 }
1061
+ ];
1062
+ while (queue.length > 0) {
1063
+ const item = queue.shift();
1064
+ if (!item) continue;
1065
+ const { name, currentDepth } = item;
1066
+ if (visited.has(name) || currentDepth > depth) continue;
1067
+ visited.add(name);
1068
+ for (const edge of graph.edges) {
1069
+ if (edge.from === name && !visited.has(edge.to)) {
1070
+ related.add(edge.to);
1071
+ queue.push({ name: edge.to, currentDepth: currentDepth + 1 });
1072
+ }
1073
+ if (edge.to === name && !visited.has(edge.from)) {
1074
+ related.add(edge.from);
1075
+ queue.push({ name: edge.from, currentDepth: currentDepth + 1 });
1076
+ }
1077
+ }
1078
+ }
1079
+ return related;
1080
+ }
1081
+ function findOrphanEntities(graph) {
1082
+ const orphans = [];
1083
+ for (const [name] of graph.entities) {
1084
+ const hasRelationship = graph.edges.some((e) => e.from === name || e.to === name);
1085
+ if (!hasRelationship) {
1086
+ orphans.push(name);
1087
+ }
1088
+ }
1089
+ return orphans;
1090
+ }
1091
+ function findCircularDependencies(graph) {
1092
+ const cycles = [];
1093
+ const visited = /* @__PURE__ */ new Set();
1094
+ const recursionStack = /* @__PURE__ */ new Set();
1095
+ function dfs(node, path) {
1096
+ visited.add(node);
1097
+ recursionStack.add(node);
1098
+ const outgoingEdges = graph.edges.filter((e) => e.from === node);
1099
+ for (const edge of outgoingEdges) {
1100
+ if (!visited.has(edge.to)) {
1101
+ dfs(edge.to, [...path, edge.to]);
1102
+ } else if (recursionStack.has(edge.to)) {
1103
+ const cycleStart = path.indexOf(edge.to);
1104
+ if (cycleStart !== -1) {
1105
+ cycles.push([...path.slice(cycleStart), edge.to]);
1106
+ } else {
1107
+ cycles.push([...path, edge.to]);
1108
+ }
1109
+ }
1110
+ }
1111
+ recursionStack.delete(node);
1112
+ }
1113
+ for (const [name] of graph.entities) {
1114
+ if (!visited.has(name)) {
1115
+ dfs(name, [name]);
1116
+ }
1117
+ }
1118
+ const uniqueCycles = [];
1119
+ const seen = /* @__PURE__ */ new Set();
1120
+ for (const cycle of cycles) {
1121
+ const minIndex = cycle.indexOf(
1122
+ cycle.reduce((min, val) => val < min ? val : min, cycle[0])
1123
+ );
1124
+ const normalized = [...cycle.slice(minIndex), ...cycle.slice(0, minIndex)];
1125
+ const key = normalized.join("->");
1126
+ if (!seen.has(key)) {
1127
+ seen.add(key);
1128
+ uniqueCycles.push(cycle);
1129
+ }
1130
+ }
1131
+ return uniqueCycles;
1132
+ }
1133
+
1134
+ // src/behaviors/external-id-tracking.ts
1135
+ var externalIdTrackingBehavior = {
1136
+ name: "external_id_tracking",
1137
+ description: "Adds external_id, provider, and provider_metadata fields for external system sync tracking",
1138
+ fields: [
1139
+ {
1140
+ name: "external_id",
1141
+ camelName: "externalId",
1142
+ type: "string",
1143
+ tsType: "string | null",
1144
+ drizzleType: "varchar",
1145
+ drizzleImports: ["varchar", "index"],
1146
+ zodType: "z.string().nullable()",
1147
+ nullable: true,
1148
+ ui: {
1149
+ label: "External ID",
1150
+ type: "text",
1151
+ importance: "tertiary",
1152
+ group: "metadata",
1153
+ visible: false
1154
+ }
1155
+ },
1156
+ {
1157
+ name: "provider",
1158
+ camelName: "provider",
1159
+ type: "string",
1160
+ tsType: "string | null",
1161
+ drizzleType: "varchar",
1162
+ drizzleImports: ["varchar"],
1163
+ zodType: "z.string().nullable()",
1164
+ nullable: true,
1165
+ ui: {
1166
+ label: "Provider",
1167
+ type: "text",
1168
+ importance: "tertiary",
1169
+ group: "metadata",
1170
+ visible: false
1171
+ }
1172
+ },
1173
+ {
1174
+ name: "provider_metadata",
1175
+ camelName: "providerMetadata",
1176
+ type: "json",
1177
+ tsType: "unknown | null",
1178
+ drizzleType: "jsonb",
1179
+ drizzleImports: ["jsonb"],
1180
+ zodType: "z.unknown().nullable()",
1181
+ nullable: true,
1182
+ ui: {
1183
+ label: "Provider Metadata",
1184
+ type: "json",
1185
+ importance: "tertiary",
1186
+ group: "metadata",
1187
+ visible: false
1188
+ }
1189
+ }
1190
+ ],
1191
+ drizzleImports: ["varchar", "jsonb", "index"],
1192
+ configKey: "externalIdTracking"
1193
+ };
1194
+
1195
+ // src/behaviors/soft-delete.ts
1196
+ var softDeleteBehavior = {
1197
+ name: "soft_delete",
1198
+ description: "Adds deleted_at field for soft delete functionality",
1199
+ fields: [
1200
+ {
1201
+ name: "deleted_at",
1202
+ camelName: "deletedAt",
1203
+ type: "datetime",
1204
+ tsType: "Date | null",
1205
+ drizzleType: "timestamp",
1206
+ drizzleImports: ["timestamp"],
1207
+ zodType: "z.coerce.date().nullable()",
1208
+ nullable: true,
1209
+ ui: {
1210
+ label: "Deleted At",
1211
+ type: "datetime",
1212
+ importance: "tertiary",
1213
+ group: "metadata",
1214
+ visible: false
1215
+ }
1216
+ }
1217
+ ],
1218
+ drizzleImports: ["timestamp"],
1219
+ methods: [
1220
+ "softDelete",
1221
+ "restore",
1222
+ "findWithDeleted",
1223
+ "findOnlyDeleted",
1224
+ "baseQuery"
1225
+ // Modified to filter deleted records
1226
+ ],
1227
+ configKey: "softDelete"
1228
+ };
1229
+
1230
+ // src/behaviors/timestamps.ts
1231
+ var timestampsBehavior = {
1232
+ name: "timestamps",
1233
+ description: "Adds created_at and updated_at timestamp fields",
1234
+ fields: [
1235
+ {
1236
+ name: "created_at",
1237
+ camelName: "createdAt",
1238
+ type: "datetime",
1239
+ tsType: "Date",
1240
+ drizzleType: "timestamp",
1241
+ drizzleImports: ["timestamp"],
1242
+ zodType: "z.coerce.date()",
1243
+ nullable: false,
1244
+ default: "now()",
1245
+ ui: {
1246
+ label: "Created At",
1247
+ type: "datetime",
1248
+ importance: "tertiary",
1249
+ group: "metadata",
1250
+ visible: false
1251
+ }
1252
+ },
1253
+ {
1254
+ name: "updated_at",
1255
+ camelName: "updatedAt",
1256
+ type: "datetime",
1257
+ tsType: "Date",
1258
+ drizzleType: "timestamp",
1259
+ drizzleImports: ["timestamp"],
1260
+ zodType: "z.coerce.date()",
1261
+ nullable: false,
1262
+ default: "now()",
1263
+ ui: {
1264
+ label: "Updated At",
1265
+ type: "datetime",
1266
+ importance: "tertiary",
1267
+ group: "metadata",
1268
+ visible: false
1269
+ }
1270
+ }
1271
+ ],
1272
+ drizzleImports: ["timestamp"],
1273
+ methods: ["applyTimestampsOnCreate", "applyTimestampsOnUpdate"],
1274
+ configKey: "timestamps"
1275
+ };
1276
+
1277
+ // src/behaviors/user-tracking.ts
1278
+ var userTrackingBehavior = {
1279
+ name: "user_tracking",
1280
+ description: "Adds created_by and updated_by user reference fields",
1281
+ fields: [
1282
+ {
1283
+ name: "created_by",
1284
+ camelName: "createdBy",
1285
+ type: "uuid",
1286
+ tsType: "string | null",
1287
+ drizzleType: "uuid",
1288
+ drizzleImports: ["uuid"],
1289
+ zodType: "z.string().uuid().nullable()",
1290
+ nullable: true,
1291
+ foreignKey: "users.id",
1292
+ ui: {
1293
+ label: "Created By",
1294
+ type: "reference",
1295
+ importance: "tertiary",
1296
+ group: "metadata",
1297
+ visible: false
1298
+ }
1299
+ },
1300
+ {
1301
+ name: "updated_by",
1302
+ camelName: "updatedBy",
1303
+ type: "uuid",
1304
+ tsType: "string | null",
1305
+ drizzleType: "uuid",
1306
+ drizzleImports: ["uuid"],
1307
+ zodType: "z.string().uuid().nullable()",
1308
+ nullable: true,
1309
+ foreignKey: "users.id",
1310
+ ui: {
1311
+ label: "Updated By",
1312
+ type: "reference",
1313
+ importance: "tertiary",
1314
+ group: "metadata",
1315
+ visible: false
1316
+ }
1317
+ }
1318
+ ],
1319
+ drizzleImports: ["uuid"],
1320
+ methods: ["applyUserTrackingOnCreate", "applyUserTrackingOnUpdate"],
1321
+ configKey: "userTracking"
1322
+ };
1323
+
1324
+ // src/behaviors/index.ts
1325
+ var behaviorRegistry = /* @__PURE__ */ new Map([
1326
+ ["timestamps", timestampsBehavior],
1327
+ ["soft_delete", softDeleteBehavior],
1328
+ ["user_tracking", userTrackingBehavior],
1329
+ ["external_id_tracking", externalIdTrackingBehavior]
1330
+ ]);
1331
+ function getBehavior(name) {
1332
+ return behaviorRegistry.get(name);
1333
+ }
1334
+ function normalizeBehaviorConfig(config) {
1335
+ if (typeof config === "string") {
1336
+ return { name: config, options: {} };
1337
+ }
1338
+ return { name: config.name, options: config.options ?? {} };
1339
+ }
1340
+ function normalizeBehaviorConfigs(configs) {
1341
+ return configs.map(normalizeBehaviorConfig);
1342
+ }
1343
+ function resolveBehaviorFields(configs) {
1344
+ const normalized = normalizeBehaviorConfigs(configs);
1345
+ const fields = [];
1346
+ const addedFieldNames = /* @__PURE__ */ new Set();
1347
+ for (const config of normalized) {
1348
+ const behavior = getBehavior(config.name);
1349
+ if (!behavior) continue;
1350
+ for (const field of behavior.fields) {
1351
+ if (!addedFieldNames.has(field.name)) {
1352
+ fields.push(field);
1353
+ addedFieldNames.add(field.name);
1354
+ }
1355
+ }
1356
+ }
1357
+ return fields;
1358
+ }
1359
+
1360
+ // src/analyzer/consistency-checker.ts
1361
+ function checkConsistency(graph) {
1362
+ const issues = [];
1363
+ for (const [name, entity] of graph.entities) {
1364
+ issues.push(...checkEntityConsistency(entity));
1365
+ issues.push(...checkRelationshipConsistency(entity, graph));
1366
+ issues.push(...checkNamingConventions(entity));
1367
+ issues.push(...checkMissingIndexes(entity));
1368
+ issues.push(...checkUiMetadata(entity));
1369
+ if (entity.queries !== void 0) {
1370
+ issues.push(...checkQueryFieldReferences(entity));
1371
+ }
1372
+ if (entity.sync !== void 0) {
1373
+ issues.push(...checkSyncFieldMappingReferences(entity));
1374
+ issues.push(...checkExternalIdTrackingCollision(entity));
1375
+ }
1376
+ }
1377
+ issues.push(...checkOrphanEntities(graph));
1378
+ issues.push(...checkCircularReferences(graph));
1379
+ issues.push(...checkMissingInverses(graph));
1380
+ return issues;
1381
+ }
1382
+ function checkEntityConsistency(entity) {
1383
+ const issues = [];
1384
+ if (!entity.fields.has("id")) {
1385
+ issues.push({
1386
+ severity: "info",
1387
+ type: "missing_id",
1388
+ entity: entity.name,
1389
+ message: 'Entity missing standard "id" field',
1390
+ suggestion: 'Add an "id" field with type "uuid"'
1391
+ });
1392
+ }
1393
+ const hasCreatedAt = entity.fields.has("created_at");
1394
+ const hasTimestampsBehavior = entity.behaviors.includes("timestamps");
1395
+ if (!hasCreatedAt && !hasTimestampsBehavior) {
1396
+ issues.push({
1397
+ severity: "info",
1398
+ type: "missing_timestamps",
1399
+ entity: entity.name,
1400
+ message: 'Entity missing "created_at" field and "timestamps" behavior',
1401
+ suggestion: 'Add "timestamps" to behaviors or add created_at/updated_at fields'
1402
+ });
1403
+ }
1404
+ return issues;
1405
+ }
1406
+ function checkRelationshipConsistency(entity, graph) {
1407
+ const issues = [];
1408
+ for (const [relName, rel] of entity.relationships) {
1409
+ if (rel.type === "belongs_to") {
1410
+ const fkField = entity.fields.get(rel.foreignKey);
1411
+ if (!fkField) {
1412
+ issues.push({
1413
+ severity: "warning",
1414
+ type: "missing_fk_field",
1415
+ entity: entity.name,
1416
+ field: relName,
1417
+ message: `Relationship "${relName}" references foreign key "${rel.foreignKey}" but field doesn't exist`,
1418
+ suggestion: `Add field "${rel.foreignKey}" with foreign_key reference`
1419
+ });
1420
+ }
1421
+ }
1422
+ if (rel.type === "has_many" || rel.type === "has_one") {
1423
+ const targetEntity = graph.entities.get(rel.target);
1424
+ if (targetEntity) {
1425
+ const targetFkField = targetEntity.fields.get(rel.foreignKey);
1426
+ if (!targetFkField) {
1427
+ issues.push({
1428
+ severity: "warning",
1429
+ type: "missing_target_fk",
1430
+ entity: entity.name,
1431
+ field: relName,
1432
+ message: `Relationship "${relName}" expects foreign key "${rel.foreignKey}" on "${rel.target}" but field doesn't exist`,
1433
+ suggestion: `Add field "${rel.foreignKey}" to "${rel.target}" entity`
1434
+ });
1435
+ }
1436
+ }
1437
+ }
1438
+ }
1439
+ return issues;
1440
+ }
1441
+ function checkNamingConventions(entity) {
1442
+ const issues = [];
1443
+ if (entity.name !== entity.name.toLowerCase()) {
1444
+ issues.push({
1445
+ severity: "warning",
1446
+ type: "naming_convention",
1447
+ entity: entity.name,
1448
+ message: "Entity name should be lowercase",
1449
+ suggestion: `Use "${entity.name.toLowerCase()}"`
1450
+ });
1451
+ }
1452
+ for (const [fieldName] of entity.fields) {
1453
+ if (fieldName !== fieldName.toLowerCase()) {
1454
+ issues.push({
1455
+ severity: "warning",
1456
+ type: "naming_convention",
1457
+ entity: entity.name,
1458
+ field: fieldName,
1459
+ message: "Field name should be snake_case",
1460
+ suggestion: `Use "${toSnakeCase(fieldName)}"`
1461
+ });
1462
+ }
1463
+ }
1464
+ for (const [relName] of entity.relationships) {
1465
+ if (relName !== relName.toLowerCase()) {
1466
+ issues.push({
1467
+ severity: "warning",
1468
+ type: "naming_convention",
1469
+ entity: entity.name,
1470
+ field: relName,
1471
+ message: "Relationship name should be snake_case",
1472
+ suggestion: `Use "${toSnakeCase(relName)}"`
1473
+ });
1474
+ }
1475
+ }
1476
+ return issues;
1477
+ }
1478
+ function checkMissingIndexes(entity) {
1479
+ const issues = [];
1480
+ for (const [fieldName, field] of entity.fields) {
1481
+ if (field.ui.filterable && !field.index && !field.unique) {
1482
+ issues.push({
1483
+ severity: "warning",
1484
+ type: "missing_index",
1485
+ entity: entity.name,
1486
+ field: fieldName,
1487
+ message: `Field "${fieldName}" is filterable but has no index`,
1488
+ suggestion: 'Add "index: true" to improve query performance'
1489
+ });
1490
+ }
1491
+ if (field.foreignKey && !field.index && !field.unique) {
1492
+ issues.push({
1493
+ severity: "info",
1494
+ type: "missing_fk_index",
1495
+ entity: entity.name,
1496
+ field: fieldName,
1497
+ message: `Foreign key field "${fieldName}" has no index`,
1498
+ suggestion: 'Add "index: true" for better join performance'
1499
+ });
1500
+ }
1501
+ }
1502
+ return issues;
1503
+ }
1504
+ function checkUiMetadata(entity) {
1505
+ const issues = [];
1506
+ const systemFields = /* @__PURE__ */ new Set(["id", "created_at", "updated_at", "deleted_at", "tenant_id"]);
1507
+ for (const [fieldName, field] of entity.fields) {
1508
+ if (systemFields.has(fieldName)) continue;
1509
+ const hasAnyUiMeta = field.ui.label !== void 0 || field.ui.type !== void 0 || field.ui.group !== void 0;
1510
+ if (!hasAnyUiMeta) {
1511
+ issues.push({
1512
+ severity: "info",
1513
+ type: "missing_ui_metadata",
1514
+ entity: entity.name,
1515
+ field: fieldName,
1516
+ message: `Field "${fieldName}" has no UI metadata`,
1517
+ suggestion: "Add ui_label, ui_type, ui_group for better admin panel display"
1518
+ });
1519
+ }
1520
+ }
1521
+ return issues;
1522
+ }
1523
+ function checkOrphanEntities(graph) {
1524
+ const orphans = findOrphanEntities(graph);
1525
+ return orphans.map((name) => ({
1526
+ severity: "info",
1527
+ type: "orphan_entity",
1528
+ entity: name,
1529
+ message: `Entity "${name}" has no relationships to other entities`,
1530
+ suggestion: "Consider if this entity should be related to others"
1531
+ }));
1532
+ }
1533
+ function checkCircularReferences(graph) {
1534
+ const cycles = findCircularDependencies(graph);
1535
+ return cycles.map((cycle) => ({
1536
+ severity: "info",
1537
+ type: "circular_dependency",
1538
+ entity: cycle[0],
1539
+ message: `Circular reference detected: ${cycle.join(" -> ")}`,
1540
+ suggestion: "Verify this is intentional (e.g., self-referential hierarchy)"
1541
+ }));
1542
+ }
1543
+ function checkMissingInverses(graph) {
1544
+ const issues = [];
1545
+ for (const edge of graph.edges) {
1546
+ const { from, to, relationship } = edge;
1547
+ const targetEntity = graph.entities.get(to);
1548
+ if (!targetEntity) continue;
1549
+ const hasInverse = Array.from(targetEntity.relationships.values()).some(
1550
+ (rel) => rel.target === from
1551
+ );
1552
+ if (!hasInverse && relationship.type !== "belongs_to") {
1553
+ issues.push({
1554
+ severity: "info",
1555
+ type: "missing_inverse",
1556
+ entity: from,
1557
+ field: relationship.name,
1558
+ message: `Relationship "${relationship.name}" to "${to}" has no inverse defined on target`,
1559
+ suggestion: `Add inverse relationship on "${to}" pointing back to "${from}"`
1560
+ });
1561
+ }
1562
+ }
1563
+ return issues;
1564
+ }
1565
+ function getAvailableFieldNames(entity) {
1566
+ const entityFieldNames = Array.from(entity.fields.keys());
1567
+ const behaviorFields = resolveBehaviorFields(entity.behaviors);
1568
+ const behaviorFieldNames = behaviorFields.map((f) => f.name);
1569
+ const belongsToFkNames = [];
1570
+ for (const rel of entity.relationships.values()) {
1571
+ if (rel.type === "belongs_to" && rel.foreignKey) {
1572
+ belongsToFkNames.push(rel.foreignKey);
1573
+ }
1574
+ }
1575
+ return [.../* @__PURE__ */ new Set([...entityFieldNames, ...behaviorFieldNames, ...belongsToFkNames])];
1576
+ }
1577
+ function checkQueryFieldReferences(entity) {
1578
+ const issues = [];
1579
+ const availableFields = getAvailableFieldNames(entity);
1580
+ const availableSet = new Set(availableFields);
1581
+ for (const query of entity.queries ?? []) {
1582
+ if (!query.via) {
1583
+ for (const fieldName of query.by) {
1584
+ if (!availableSet.has(fieldName)) {
1585
+ issues.push({
1586
+ severity: "error",
1587
+ type: "unknown_query_field",
1588
+ entity: entity.name,
1589
+ field: fieldName,
1590
+ message: `Query references unknown field "${fieldName}" in entity "${entity.name}". Available fields: ${availableFields.join(", ")}`
1591
+ });
1592
+ }
1593
+ }
1594
+ }
1595
+ for (const fieldName of query.select ?? []) {
1596
+ if (!availableSet.has(fieldName)) {
1597
+ issues.push({
1598
+ severity: "error",
1599
+ type: "unknown_query_field",
1600
+ entity: entity.name,
1601
+ field: fieldName,
1602
+ message: `Query references unknown field "${fieldName}" in entity "${entity.name}". Available fields: ${availableFields.join(", ")}`
1603
+ });
1604
+ }
1605
+ }
1606
+ }
1607
+ return issues;
1608
+ }
1609
+ function checkSyncFieldMappingReferences(entity) {
1610
+ const issues = [];
1611
+ const availableFields = getAvailableFieldNames(entity);
1612
+ const availableSet = new Set(availableFields);
1613
+ for (const [providerName, provider] of Object.entries(entity.sync?.providers ?? {})) {
1614
+ for (const fieldName of Object.keys(provider.fieldMapping ?? {})) {
1615
+ if (!availableSet.has(fieldName)) {
1616
+ issues.push({
1617
+ severity: "warning",
1618
+ type: "unknown_sync_field_mapping",
1619
+ entity: entity.name,
1620
+ field: fieldName,
1621
+ message: `Sync field mapping references unknown field "${fieldName}" for provider "${providerName}" in entity "${entity.name}"`
1622
+ });
1623
+ }
1624
+ }
1625
+ for (const fieldName of provider.readOnlyFields ?? []) {
1626
+ if (!availableSet.has(fieldName)) {
1627
+ issues.push({
1628
+ severity: "warning",
1629
+ type: "unknown_sync_field_mapping",
1630
+ entity: entity.name,
1631
+ field: fieldName,
1632
+ message: `Sync field mapping references unknown field "${fieldName}" for provider "${providerName}" in entity "${entity.name}"`
1633
+ });
1634
+ }
1635
+ }
1636
+ }
1637
+ return issues;
1638
+ }
1639
+ function checkExternalIdTrackingCollision(entity) {
1640
+ const issues = [];
1641
+ const hasExternalIdTracking = entity.behaviors.includes("external_id_tracking");
1642
+ if (!hasExternalIdTracking) return issues;
1643
+ for (const [providerName, provider] of Object.entries(entity.sync?.providers ?? {})) {
1644
+ if (provider.fieldMapping && "external_id" in provider.fieldMapping) {
1645
+ issues.push({
1646
+ severity: "warning",
1647
+ type: "external_id_tracking_collision",
1648
+ entity: entity.name,
1649
+ field: "external_id",
1650
+ message: `Entity "${entity.name}" has external_id_tracking behavior and also maps "external_id" in sync field_mapping for provider "${providerName}". The behavior-added field may collide with the mapped field.`
1651
+ });
1652
+ }
1653
+ }
1654
+ return issues;
1655
+ }
1656
+ function toSnakeCase(str) {
1657
+ return str.replace(/([A-Z])/g, "_$1").toLowerCase().replace(/^_/, "");
1658
+ }
1659
+
1660
+ // src/analyzer/statistics.ts
1661
+ function computeStatistics(graph) {
1662
+ const entities = Array.from(graph.entities.values());
1663
+ const fieldsByType = {};
1664
+ const relationshipsByType = {};
1665
+ let totalFields = 0;
1666
+ let totalRelationships = 0;
1667
+ let entitiesWithBehaviors = 0;
1668
+ for (const entity of entities) {
1669
+ totalFields += entity.fields.size;
1670
+ totalRelationships += entity.relationships.size;
1671
+ if (entity.behaviors.length > 0) {
1672
+ entitiesWithBehaviors++;
1673
+ }
1674
+ for (const field of entity.fields.values()) {
1675
+ fieldsByType[field.type] = (fieldsByType[field.type] ?? 0) + 1;
1676
+ }
1677
+ for (const rel of entity.relationships.values()) {
1678
+ relationshipsByType[rel.type] = (relationshipsByType[rel.type] ?? 0) + 1;
1679
+ }
1680
+ }
1681
+ return {
1682
+ totalEntities: entities.length,
1683
+ totalFields,
1684
+ totalRelationships,
1685
+ fieldsByType,
1686
+ relationshipsByType,
1687
+ entitiesWithBehaviors,
1688
+ averageFieldsPerEntity: entities.length > 0 ? totalFields / entities.length : 0
1689
+ };
1690
+ }
1691
+
1692
+ // src/formatters/console-formatter.ts
1693
+ var colors = {
1694
+ reset: "\x1B[0m",
1695
+ bold: "\x1B[1m",
1696
+ dim: "\x1B[2m",
1697
+ red: "\x1B[31m",
1698
+ green: "\x1B[32m",
1699
+ yellow: "\x1B[33m",
1700
+ blue: "\x1B[34m",
1701
+ magenta: "\x1B[35m",
1702
+ cyan: "\x1B[36m",
1703
+ white: "\x1B[37m",
1704
+ bgRed: "\x1B[41m",
1705
+ bgGreen: "\x1B[42m",
1706
+ bgYellow: "\x1B[43m"
1707
+ };
1708
+ function color(text, ...styles) {
1709
+ return `${styles.join("")}${text}${colors.reset}`;
1710
+ }
1711
+ function severityColor(severity) {
1712
+ switch (severity) {
1713
+ case "error":
1714
+ return colors.red;
1715
+ case "warning":
1716
+ return colors.yellow;
1717
+ case "info":
1718
+ return colors.cyan;
1719
+ }
1720
+ }
1721
+ function severityIcon(severity) {
1722
+ switch (severity) {
1723
+ case "error":
1724
+ return "X";
1725
+ case "warning":
1726
+ return "!";
1727
+ case "info":
1728
+ return "i";
1729
+ }
1730
+ }
1731
+ function formatConsole(result) {
1732
+ const lines = [];
1733
+ lines.push("");
1734
+ lines.push(color("=".repeat(60), colors.dim));
1735
+ lines.push(color(" Domain Analysis Report", colors.bold, colors.cyan));
1736
+ lines.push(color("=".repeat(60), colors.dim));
1737
+ lines.push("");
1738
+ lines.push(...formatStatistics(result));
1739
+ lines.push(...formatEntities(result));
1740
+ lines.push(...formatRelationships(result));
1741
+ if (result.issues.length > 0) {
1742
+ lines.push(...formatIssues(result.issues));
1743
+ }
1744
+ lines.push("");
1745
+ lines.push(color("-".repeat(60), colors.dim));
1746
+ const errors = result.issues.filter((i) => i.severity === "error");
1747
+ const warnings = result.issues.filter((i) => i.severity === "warning");
1748
+ const infos = result.issues.filter((i) => i.severity === "info");
1749
+ if (result.isValid) {
1750
+ lines.push(
1751
+ color(
1752
+ `[OK] Domain is valid (${warnings.length} warnings, ${infos.length} info)`,
1753
+ colors.green
1754
+ )
1755
+ );
1756
+ } else {
1757
+ lines.push(color(`[FAIL] Domain has ${errors.length} errors`, colors.red));
1758
+ }
1759
+ lines.push(color("-".repeat(60), colors.dim));
1760
+ lines.push("");
1761
+ return lines.join("\n");
1762
+ }
1763
+ function formatStatistics(result) {
1764
+ const lines = [];
1765
+ const stats = result.statistics;
1766
+ lines.push(color("Statistics:", colors.bold));
1767
+ lines.push("");
1768
+ lines.push(` Entities: ${stats.totalEntities}`);
1769
+ lines.push(
1770
+ ` Fields: ${stats.totalFields} (avg ${stats.averageFieldsPerEntity.toFixed(1)}/entity)`
1771
+ );
1772
+ lines.push(` Relationships: ${stats.totalRelationships}`);
1773
+ lines.push(` With behaviors: ${stats.entitiesWithBehaviors}`);
1774
+ lines.push("");
1775
+ lines.push(" Field types:");
1776
+ const sortedTypes = Object.entries(stats.fieldsByType).sort((a, b) => b[1] - a[1]);
1777
+ for (const [type, count] of sortedTypes) {
1778
+ const bar = color("|".repeat(Math.min(count, 20)), colors.blue);
1779
+ lines.push(` ${type.padEnd(12)} ${bar} ${count}`);
1780
+ }
1781
+ lines.push("");
1782
+ if (stats.totalRelationships > 0) {
1783
+ lines.push(" Relationship types:");
1784
+ const sortedRels = Object.entries(stats.relationshipsByType).sort((a, b) => b[1] - a[1]);
1785
+ for (const [type, count] of sortedRels) {
1786
+ const bar = color("|".repeat(Math.min(count, 20)), colors.magenta);
1787
+ lines.push(` ${type.padEnd(12)} ${bar} ${count}`);
1788
+ }
1789
+ lines.push("");
1790
+ }
1791
+ return lines;
1792
+ }
1793
+ function formatEntities(result) {
1794
+ const lines = [];
1795
+ lines.push(color("Entities:", colors.bold));
1796
+ lines.push("");
1797
+ for (const entity of result.entities) {
1798
+ const fieldCount = entity.fields.size;
1799
+ const relCount = entity.relationships.size;
1800
+ lines.push(
1801
+ ` ${color(entity.name, colors.cyan)} (${fieldCount} fields, ${relCount} relationships)`
1802
+ );
1803
+ if (entity.behaviors.length > 0) {
1804
+ lines.push(color(` behaviors: ${entity.behaviors.join(", ")}`, colors.dim));
1805
+ }
1806
+ }
1807
+ lines.push("");
1808
+ return lines;
1809
+ }
1810
+ function formatRelationships(result) {
1811
+ const lines = [];
1812
+ if (result.graph.edges.length === 0) {
1813
+ return lines;
1814
+ }
1815
+ lines.push(color("Relationships:", colors.bold));
1816
+ lines.push("");
1817
+ for (const edge of result.graph.edges) {
1818
+ const arrow = getCardinalityArrow(edge.cardinality);
1819
+ const bidir = edge.bidirectional ? color(" (bidirectional)", colors.dim) : "";
1820
+ lines.push(
1821
+ ` ${edge.from.padEnd(20)} ${arrow} ${edge.to} ${color(`(${edge.relationship.type})`, colors.dim)}${bidir}`
1822
+ );
1823
+ }
1824
+ lines.push("");
1825
+ return lines;
1826
+ }
1827
+ function getCardinalityArrow(cardinality) {
1828
+ switch (cardinality) {
1829
+ case "1:N":
1830
+ return color("--<", colors.magenta);
1831
+ case "N:1":
1832
+ return color(">--", colors.magenta);
1833
+ case "1:1":
1834
+ return color("---", colors.magenta);
1835
+ case "N:M":
1836
+ return color(">-<", colors.magenta);
1837
+ default:
1838
+ return color("-->", colors.magenta);
1839
+ }
1840
+ }
1841
+ function formatIssues(issues) {
1842
+ const lines = [];
1843
+ const errors = issues.filter((i) => i.severity === "error");
1844
+ const warnings = issues.filter((i) => i.severity === "warning");
1845
+ const infos = issues.filter((i) => i.severity === "info");
1846
+ if (errors.length > 0) {
1847
+ lines.push(color(`Errors (${errors.length}):`, colors.bold, colors.red));
1848
+ lines.push("");
1849
+ lines.push(...formatIssueList(errors, "error"));
1850
+ lines.push("");
1851
+ }
1852
+ if (warnings.length > 0) {
1853
+ lines.push(color(`Warnings (${warnings.length}):`, colors.bold, colors.yellow));
1854
+ lines.push("");
1855
+ lines.push(...formatIssueList(warnings, "warning"));
1856
+ lines.push("");
1857
+ }
1858
+ if (infos.length > 0) {
1859
+ lines.push(color(`Info (${infos.length}):`, colors.bold, colors.cyan));
1860
+ lines.push("");
1861
+ lines.push(...formatIssueList(infos, "info", 5));
1862
+ lines.push("");
1863
+ }
1864
+ return lines;
1865
+ }
1866
+ function formatIssueList(issues, severity, limit) {
1867
+ const lines = [];
1868
+ const displayIssues = limit ? issues.slice(0, limit) : issues;
1869
+ const byType = /* @__PURE__ */ new Map();
1870
+ for (const issue of displayIssues) {
1871
+ const list = byType.get(issue.type) ?? [];
1872
+ list.push(issue);
1873
+ byType.set(issue.type, list);
1874
+ }
1875
+ for (const [type, typeIssues] of byType) {
1876
+ lines.push(color(` ${type} (${typeIssues.length}):`, colors.dim));
1877
+ for (const issue of typeIssues.slice(0, 5)) {
1878
+ const location = issue.entity ? `${issue.entity}${issue.field ? "." + issue.field : ""}` : issue.path ?? "unknown";
1879
+ const icon = color(`[${severityIcon(severity)}]`, severityColor(severity));
1880
+ lines.push(` ${icon} ${color(location, colors.bold)}: ${issue.message}`);
1881
+ if (issue.suggestion) {
1882
+ lines.push(color(` -> ${issue.suggestion}`, colors.dim));
1883
+ }
1884
+ }
1885
+ if (typeIssues.length > 5) {
1886
+ lines.push(color(` ... and ${typeIssues.length - 5} more`, colors.dim));
1887
+ }
1888
+ }
1889
+ if (limit && issues.length > limit) {
1890
+ lines.push(color(` ... and ${issues.length - limit} more info messages`, colors.dim));
1891
+ }
1892
+ return lines;
1893
+ }
1894
+
1895
+ // src/formatters/json-formatter.ts
1896
+ function mapToObject(map) {
1897
+ const obj = {};
1898
+ for (const [key, value] of map) {
1899
+ obj[key] = value;
1900
+ }
1901
+ return obj;
1902
+ }
1903
+ function serializeEntity(entity) {
1904
+ return {
1905
+ name: entity.name,
1906
+ plural: entity.plural,
1907
+ table: entity.table,
1908
+ folderStructure: entity.folderStructure,
1909
+ fields: mapToObject(entity.fields),
1910
+ relationships: mapToObject(entity.relationships),
1911
+ behaviors: entity.behaviors,
1912
+ sourcePath: entity.sourcePath
1913
+ };
1914
+ }
1915
+ function serializeGraph(graph) {
1916
+ const entities = {};
1917
+ for (const [name, entity] of graph.entities) {
1918
+ entities[name] = serializeEntity(entity);
1919
+ }
1920
+ return {
1921
+ entities,
1922
+ edges: graph.edges
1923
+ };
1924
+ }
1925
+ function formatJson(result, pretty = true) {
1926
+ const output = {
1927
+ isValid: result.isValid,
1928
+ summary: {
1929
+ entities: result.statistics.totalEntities,
1930
+ fields: result.statistics.totalFields,
1931
+ relationships: result.statistics.totalRelationships,
1932
+ errors: result.issues.filter((i) => i.severity === "error").length,
1933
+ warnings: result.issues.filter((i) => i.severity === "warning").length,
1934
+ info: result.issues.filter((i) => i.severity === "info").length
1935
+ },
1936
+ entities: result.entities.map(serializeEntity),
1937
+ graph: serializeGraph(result.graph),
1938
+ issues: result.issues,
1939
+ statistics: result.statistics,
1940
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1941
+ };
1942
+ return pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
1943
+ }
1944
+
1945
+ // src/formatters/markdown-formatter.ts
1946
+ function formatMarkdown(result) {
1947
+ const lines = [];
1948
+ lines.push("# Domain Model Documentation");
1949
+ lines.push("");
1950
+ lines.push(`Generated: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1951
+ lines.push("");
1952
+ lines.push("## Overview");
1953
+ lines.push("");
1954
+ lines.push("| Metric | Value |");
1955
+ lines.push("|--------|-------|");
1956
+ lines.push(`| Entities | ${result.statistics.totalEntities} |`);
1957
+ lines.push(`| Total Fields | ${result.statistics.totalFields} |`);
1958
+ lines.push(`| Total Relationships | ${result.statistics.totalRelationships} |`);
1959
+ lines.push(
1960
+ `| Avg Fields/Entity | ${result.statistics.averageFieldsPerEntity.toFixed(1)} |`
1961
+ );
1962
+ lines.push("");
1963
+ lines.push("### Field Type Distribution");
1964
+ lines.push("");
1965
+ lines.push("| Type | Count |");
1966
+ lines.push("|------|-------|");
1967
+ const sortedTypes = Object.entries(result.statistics.fieldsByType).sort(
1968
+ (a, b) => b[1] - a[1]
1969
+ );
1970
+ for (const [type, count] of sortedTypes) {
1971
+ lines.push(`| ${type} | ${count} |`);
1972
+ }
1973
+ lines.push("");
1974
+ if (result.statistics.totalRelationships > 0) {
1975
+ lines.push("### Relationship Type Distribution");
1976
+ lines.push("");
1977
+ lines.push("| Type | Count |");
1978
+ lines.push("|------|-------|");
1979
+ const sortedRels = Object.entries(result.statistics.relationshipsByType).sort(
1980
+ (a, b) => b[1] - a[1]
1981
+ );
1982
+ for (const [type, count] of sortedRels) {
1983
+ lines.push(`| ${type} | ${count} |`);
1984
+ }
1985
+ lines.push("");
1986
+ }
1987
+ lines.push("## Entity Relationship Diagram");
1988
+ lines.push("");
1989
+ lines.push("```mermaid");
1990
+ lines.push(...generateMermaidErDiagram(result));
1991
+ lines.push("```");
1992
+ lines.push("");
1993
+ lines.push("## Entities");
1994
+ lines.push("");
1995
+ for (const entity of result.entities) {
1996
+ lines.push(...formatEntitySection(entity));
1997
+ }
1998
+ const errors = result.issues.filter((i) => i.severity === "error");
1999
+ const warnings = result.issues.filter((i) => i.severity === "warning");
2000
+ const infos = result.issues.filter((i) => i.severity === "info");
2001
+ if (result.issues.length > 0) {
2002
+ lines.push("## Analysis Issues");
2003
+ lines.push("");
2004
+ if (errors.length > 0) {
2005
+ lines.push("### Errors");
2006
+ lines.push("");
2007
+ for (const issue of errors) {
2008
+ const location = issue.entity ? `**${issue.entity}${issue.field ? "." + issue.field : ""}**` : issue.path ?? "unknown";
2009
+ lines.push(`- [${issue.type}] ${location}: ${issue.message}`);
2010
+ if (issue.suggestion) {
2011
+ lines.push(` - Suggestion: ${issue.suggestion}`);
2012
+ }
2013
+ }
2014
+ lines.push("");
2015
+ }
2016
+ if (warnings.length > 0) {
2017
+ lines.push("### Warnings");
2018
+ lines.push("");
2019
+ for (const issue of warnings) {
2020
+ const location = issue.entity ? `**${issue.entity}${issue.field ? "." + issue.field : ""}**` : issue.path ?? "unknown";
2021
+ lines.push(`- [${issue.type}] ${location}: ${issue.message}`);
2022
+ if (issue.suggestion) {
2023
+ lines.push(` - Suggestion: ${issue.suggestion}`);
2024
+ }
2025
+ }
2026
+ lines.push("");
2027
+ }
2028
+ if (infos.length > 0) {
2029
+ lines.push("### Info");
2030
+ lines.push("");
2031
+ lines.push("<details>");
2032
+ lines.push("<summary>Show info messages</summary>");
2033
+ lines.push("");
2034
+ for (const issue of infos) {
2035
+ const location = issue.entity ? `**${issue.entity}${issue.field ? "." + issue.field : ""}**` : issue.path ?? "unknown";
2036
+ lines.push(`- [${issue.type}] ${location}: ${issue.message}`);
2037
+ }
2038
+ lines.push("");
2039
+ lines.push("</details>");
2040
+ lines.push("");
2041
+ }
2042
+ }
2043
+ return lines.join("\n");
2044
+ }
2045
+ function formatEntitySection(entity) {
2046
+ const lines = [];
2047
+ lines.push(`### ${entity.name}`);
2048
+ lines.push("");
2049
+ lines.push(`**Table:** \`${entity.table}\``);
2050
+ lines.push(`**Plural:** ${entity.plural}`);
2051
+ if (entity.behaviors.length > 0) {
2052
+ lines.push(`**Behaviors:** ${entity.behaviors.join(", ")}`);
2053
+ }
2054
+ lines.push("");
2055
+ lines.push("#### Fields");
2056
+ lines.push("");
2057
+ lines.push("| Name | Type | Required | Nullable | Index | Foreign Key |");
2058
+ lines.push("|------|------|----------|----------|-------|-------------|");
2059
+ for (const [name, field] of entity.fields) {
2060
+ const required = field.required ? "Yes" : "";
2061
+ const nullable = field.nullable ? "Yes" : "";
2062
+ const index = field.index ? "Yes" : field.unique ? "Unique" : "";
2063
+ const fk = field.foreignKey ? `${field.foreignKey.table}.${field.foreignKey.column}` : "";
2064
+ lines.push(`| ${name} | ${field.type} | ${required} | ${nullable} | ${index} | ${fk} |`);
2065
+ }
2066
+ lines.push("");
2067
+ if (entity.relationships.size > 0) {
2068
+ lines.push("#### Relationships");
2069
+ lines.push("");
2070
+ lines.push("| Name | Type | Target | Foreign Key |");
2071
+ lines.push("|------|------|--------|-------------|");
2072
+ for (const [name, rel] of entity.relationships) {
2073
+ lines.push(`| ${name} | ${rel.type} | ${rel.target} | ${rel.foreignKey} |`);
2074
+ }
2075
+ lines.push("");
2076
+ }
2077
+ return lines;
2078
+ }
2079
+ function generateMermaidErDiagram(result) {
2080
+ const lines = [];
2081
+ lines.push("erDiagram");
2082
+ for (const entity of result.entities) {
2083
+ const entityName = entity.name.toUpperCase();
2084
+ lines.push(` ${entityName} {`);
2085
+ const keyFields = Array.from(entity.fields.entries()).filter(
2086
+ ([name, field]) => field.foreignKey || field.unique || field.index || name === "id" || name === "name"
2087
+ ).slice(0, 6);
2088
+ for (const [name, field] of keyFields) {
2089
+ const typeStr = field.type;
2090
+ const pk = name === "id" ? "PK" : "";
2091
+ const fk = field.foreignKey ? "FK" : "";
2092
+ const marker = pk || fk ? ` "${pk}${fk}"` : "";
2093
+ lines.push(` ${typeStr} ${name}${marker}`);
2094
+ }
2095
+ if (entity.fields.size > keyFields.length) {
2096
+ lines.push(` string _more_fields`);
2097
+ }
2098
+ lines.push(" }");
2099
+ }
2100
+ for (const edge of result.graph.edges) {
2101
+ const from = edge.from.toUpperCase();
2102
+ const to = edge.to.toUpperCase();
2103
+ const cardinalitySymbol = getCardinalitySymbol(edge.cardinality);
2104
+ const label = edge.relationship.name;
2105
+ lines.push(` ${from} ${cardinalitySymbol} ${to} : "${label}"`);
2106
+ }
2107
+ return lines;
2108
+ }
2109
+ function getCardinalitySymbol(cardinality) {
2110
+ switch (cardinality) {
2111
+ case "1:N":
2112
+ return "||--o{";
2113
+ case "N:1":
2114
+ return "}o--||";
2115
+ case "1:1":
2116
+ return "||--||";
2117
+ case "N:M":
2118
+ return "}o--o{";
2119
+ default:
2120
+ return "||--o{";
2121
+ }
2122
+ }
2123
+ function formatMermaidGraph(result) {
2124
+ const lines = [];
2125
+ lines.push("```mermaid");
2126
+ lines.push("graph LR");
2127
+ lines.push(" classDef entity fill:#e1f5fe,stroke:#01579b");
2128
+ for (const entity of result.entities) {
2129
+ lines.push(` ${entity.name}["${entity.name}\\n(${entity.fields.size} fields)"]`);
2130
+ }
2131
+ for (const edge of result.graph.edges) {
2132
+ const style = edge.bidirectional ? "<-->" : "-->";
2133
+ lines.push(` ${edge.from} ${style}|${edge.relationship.type}| ${edge.to}`);
2134
+ }
2135
+ const entityList = result.entities.map((e) => e.name).join(",");
2136
+ if (entityList) {
2137
+ lines.push(` class ${entityList} entity`);
2138
+ }
2139
+ lines.push("```");
2140
+ return lines.join("\n");
2141
+ }
2142
+
2143
+ // src/index.ts
2144
+ async function analyzeDomain(entitiesDir, relationshipsDir) {
2145
+ const { entities, issues: loadIssues } = loadEntities(entitiesDir);
2146
+ const { relationships: relationshipDefinitions, issues: relLoadIssues } = relationshipsDir ? loadRelationships(relationshipsDir) : { relationships: [], issues: [] };
2147
+ const resolveIssues = resolveReferences(entities);
2148
+ const relResolveIssues = resolveRelationshipReferences(
2149
+ relationshipDefinitions,
2150
+ entities
2151
+ );
2152
+ const graph = buildDomainGraph(entities, relationshipDefinitions);
2153
+ const consistencyIssues = checkConsistency(graph);
2154
+ const statistics = computeStatistics(graph);
2155
+ const allIssues = [
2156
+ ...loadIssues,
2157
+ ...relLoadIssues,
2158
+ ...resolveIssues,
2159
+ ...relResolveIssues,
2160
+ ...consistencyIssues
2161
+ ];
2162
+ const hasErrors = allIssues.some((i) => i.severity === "error");
2163
+ return {
2164
+ isValid: !hasErrors,
2165
+ entities,
2166
+ relationshipDefinitions,
2167
+ graph,
2168
+ issues: allIssues,
2169
+ statistics
2170
+ };
2171
+ }
2172
+ function validateEntities(entitiesDir) {
2173
+ const { entities, issues } = loadEntities(entitiesDir);
2174
+ const errors = issues.filter((i) => i.severity === "error").map((i) => i.message);
2175
+ return {
2176
+ valid: errors.length === 0,
2177
+ errors
2178
+ };
2179
+ }
2180
+ export {
2181
+ analyzeDomain,
2182
+ buildDomainGraph,
2183
+ checkConsistency,
2184
+ computeStatistics,
2185
+ findCircularDependencies,
2186
+ findOrphanEntities,
2187
+ formatConsole,
2188
+ formatJson,
2189
+ formatMarkdown,
2190
+ formatMermaidGraph,
2191
+ getRelatedEntities,
2192
+ loadEntities,
2193
+ loadEntityFromYaml,
2194
+ loadRelationshipFromYaml,
2195
+ loadRelationships,
2196
+ validateEntities
2197
+ };
2198
+ //# sourceMappingURL=index.js.map