@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,682 @@
1
+ /**
2
+ * Hygen prompt.js — Loads relationship YAML and prepares template locals
3
+ *
4
+ * Usage: bunx hygen relationship new --yaml relationships/person_organization.yaml
5
+ *
6
+ * Mirrors the structure of templates/entity/new/prompt.js but adapted for
7
+ * first-class relationship definitions (junction tables with auto-generated
8
+ * FK columns, type enum, temporal/sourced fields).
9
+ */
10
+
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import yaml from "yaml";
14
+
15
+ // ============================================================================
16
+ // Naming Helpers (inlined to avoid import issues with Hygen)
17
+ // ============================================================================
18
+
19
+ const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
20
+ const camelCase = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
21
+ const pascalCase = (s) => capitalize(camelCase(s));
22
+ const pluralize = (s) => {
23
+ if (s.endsWith("y")) return s.slice(0, -1) + "ies";
24
+ if (s.endsWith("s") || s.endsWith("x") || s.endsWith("ch") || s.endsWith("sh"))
25
+ return s + "es";
26
+ return s + "s";
27
+ };
28
+ const kebabCase = (s) => s.replace(/_/g, "-");
29
+
30
+ // ============================================================================
31
+ // Relationship FK Derivation (mirrors relationship-definition.schema.ts)
32
+ // ============================================================================
33
+
34
+ function deriveRelationshipFKColumns(config) {
35
+ if (config.from === config.to) {
36
+ return {
37
+ fromColumn: `from_${config.from}_id`,
38
+ toColumn: `to_${config.to}_id`,
39
+ };
40
+ }
41
+ return {
42
+ fromColumn: `${config.from}_id`,
43
+ toColumn: `${config.to}_id`,
44
+ };
45
+ }
46
+
47
+ function deriveTableName(config) {
48
+ return config.table ?? `${config.name}s`;
49
+ }
50
+
51
+ function collectTypeNames(types) {
52
+ if (!types) return [];
53
+ if (Array.isArray(types)) return types;
54
+ return Object.keys(types);
55
+ }
56
+
57
+ function deriveUniqueConstraint(config) {
58
+ if (config.unique_on) return config.unique_on;
59
+
60
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
61
+ const columns = [fromColumn, toColumn];
62
+
63
+ if (config.types) {
64
+ columns.push("type");
65
+ }
66
+ if (config.temporal && config.types) {
67
+ columns.push("valid_from");
68
+ }
69
+
70
+ return columns;
71
+ }
72
+
73
+ function getReservedColumnNames(config) {
74
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
75
+ const reserved = new Set(["id", "created_at", "updated_at", fromColumn, toColumn]);
76
+
77
+ if (config.types) reserved.add("type");
78
+ if (config.temporal !== false) {
79
+ reserved.add("valid_from");
80
+ reserved.add("valid_to");
81
+ reserved.add("is_current");
82
+ }
83
+ if (config.sourced !== false) {
84
+ reserved.add("source");
85
+ reserved.add("confidence");
86
+ }
87
+
88
+ return reserved;
89
+ }
90
+
91
+ // ============================================================================
92
+ // Drizzle Type Maps
93
+ // ============================================================================
94
+
95
+ const DRIZZLE_TYPE_MAP = {
96
+ string: "text",
97
+ integer: "integer",
98
+ decimal: "numeric",
99
+ boolean: "boolean",
100
+ uuid: "uuid",
101
+ date: "date",
102
+ datetime: "timestamp",
103
+ json: "jsonb",
104
+ string_array: "text",
105
+ };
106
+
107
+ const DRIZZLE_IMPORT_MAP = {
108
+ text: "text",
109
+ integer: "integer",
110
+ numeric: "numeric",
111
+ boolean: "boolean",
112
+ uuid: "uuid",
113
+ date: "date",
114
+ timestamp: "timestamp",
115
+ jsonb: "jsonb",
116
+ };
117
+
118
+ const ZOD_TYPE_MAP = {
119
+ string: "z.string()",
120
+ integer: "z.number().int()",
121
+ decimal: "z.number()",
122
+ boolean: "z.boolean()",
123
+ uuid: "z.string().uuid()",
124
+ date: "z.coerce.date()",
125
+ datetime: "z.coerce.date()",
126
+ json: "z.record(z.unknown())",
127
+ };
128
+
129
+ const TS_TYPE_MAP = {
130
+ string: "string",
131
+ integer: "number",
132
+ decimal: "number",
133
+ boolean: "boolean",
134
+ uuid: "string",
135
+ date: "Date",
136
+ datetime: "Date",
137
+ json: "unknown",
138
+ };
139
+
140
+ // ============================================================================
141
+ // Field Processing
142
+ // ============================================================================
143
+
144
+ function buildDrizzleChain(fieldName, field, drizzleType) {
145
+ const nullable = field.nullable ?? false;
146
+ const required = field.required ?? false;
147
+ const hasDefault = field.default !== undefined && field.default !== null;
148
+
149
+ let chain = `${drizzleType}('${fieldName}')`;
150
+ if (required && !nullable) chain += ".notNull()";
151
+ if (drizzleType === "boolean" && hasDefault) chain += `.default(${field.default})`;
152
+
153
+ return chain;
154
+ }
155
+
156
+ function processFields(fields) {
157
+ const processed = [];
158
+
159
+ for (const [fieldName, field] of Object.entries(fields)) {
160
+ if (fieldName === "id") continue;
161
+
162
+ const type = field.type || "string";
163
+ const nullable = field.nullable ?? false;
164
+ const required = field.required ?? false;
165
+ const hasDefault = field.default !== undefined && field.default !== null;
166
+ const choices = field.choices;
167
+ const hasChoices = Array.isArray(choices) && choices.length > 0;
168
+
169
+ const drizzleType = DRIZZLE_TYPE_MAP[type] || "text";
170
+ const tsType = hasChoices
171
+ ? choices.map((c) => `'${c}'`).join(" | ")
172
+ : TS_TYPE_MAP[type] || "unknown";
173
+ const zodType = hasChoices
174
+ ? `z.enum([${choices.map((c) => `'${c}'`).join(", ")}])`
175
+ : ZOD_TYPE_MAP[type] || "z.unknown()";
176
+
177
+ const drizzleChain = buildDrizzleChain(fieldName, field, drizzleType);
178
+ const enumName = hasChoices ? camelCase(fieldName) + "Enum" : null;
179
+
180
+ processed.push({
181
+ name: fieldName,
182
+ camelName: camelCase(fieldName),
183
+ type,
184
+ drizzleType,
185
+ zodType,
186
+ tsType,
187
+ nullable,
188
+ required,
189
+ hasDefault,
190
+ isPrimaryKey: false,
191
+ drizzleChain,
192
+ choices,
193
+ hasChoices,
194
+ enumName,
195
+ foreignKey: field.foreign_key,
196
+ });
197
+ }
198
+
199
+ return processed;
200
+ }
201
+
202
+ // ============================================================================
203
+ // Zod chain helpers for DTOs
204
+ // ============================================================================
205
+
206
+ function zodChainForCreate(field) {
207
+ const { type, nullable, required, hasDefault, hasChoices, choices } = field;
208
+
209
+ if (hasChoices) {
210
+ const base = `z.enum([${choices.map((c) => `'${c}'`).join(", ")}])`;
211
+ if (!required && !nullable) return base + ".optional()";
212
+ if (nullable) return base + ".nullable()";
213
+ return base;
214
+ }
215
+
216
+ let base = ZOD_TYPE_MAP[type] || "z.unknown()";
217
+ if (type === "boolean" && hasDefault) {
218
+ base += `.default(${field.default ?? false})`;
219
+ return base;
220
+ }
221
+ if (nullable) return base + ".nullable()";
222
+ if (!required) return base + ".optional()";
223
+ return base;
224
+ }
225
+
226
+ function zodChainForOutput(field) {
227
+ const { type, nullable, hasChoices, choices } = field;
228
+
229
+ if (hasChoices) {
230
+ const base = `z.enum([${choices.map((c) => `'${c}'`).join(", ")}])`;
231
+ if (nullable) return base + ".nullable()";
232
+ return base;
233
+ }
234
+
235
+ let base = ZOD_TYPE_MAP[type] || "z.unknown()";
236
+ if (nullable) return base + ".nullable()";
237
+ return base;
238
+ }
239
+
240
+ // ============================================================================
241
+ // Query Processing
242
+ // ============================================================================
243
+
244
+ function deriveQueryMethodName(query) {
245
+ const byFields = Array.isArray(query.by) ? query.by : [];
246
+ const selectFields = Array.isArray(query.select) ? query.select : [];
247
+
248
+ const byPart = byFields.map((f) => pascalCase(f)).join("And");
249
+
250
+ if (selectFields.length > 0) {
251
+ const selectPart = selectFields.map((f) => pascalCase(f)).join("And") + "s";
252
+ return `find${selectPart}By${byPart}`;
253
+ }
254
+
255
+ return `findBy${byPart}`;
256
+ }
257
+
258
+ function processQueries(queriesBlock, allFieldsMap, entityNamePascal) {
259
+ if (!queriesBlock || !Array.isArray(queriesBlock) || queriesBlock.length === 0) {
260
+ return [];
261
+ }
262
+
263
+ return queriesBlock.map((q) => {
264
+ const byFields = Array.isArray(q.by) ? q.by : [];
265
+ const selectFields = Array.isArray(q.select) ? q.select : [];
266
+ const isUnique = q.unique ?? false;
267
+
268
+ const params = byFields.map((f) => ({
269
+ name: f,
270
+ camelName: camelCase(f),
271
+ tsType: allFieldsMap[f] || allFieldsMap[camelCase(f)] || "string",
272
+ }));
273
+
274
+ let orderBy = null;
275
+ let orderDirection = null;
276
+ if (q.order) {
277
+ const parts = q.order.trim().split(/\s+/);
278
+ orderBy = camelCase(parts[0]);
279
+ orderDirection = parts[1] || "asc";
280
+ }
281
+
282
+ const methodName = deriveQueryMethodName(q);
283
+
284
+ let returnType;
285
+ if (isUnique) {
286
+ returnType = `${entityNamePascal} | null`;
287
+ } else if (selectFields.length > 0) {
288
+ const camelFields = selectFields.map((f) => camelCase(f));
289
+ returnType =
290
+ selectFields.length === 1
291
+ ? `${allFieldsMap[selectFields[0]] || allFieldsMap[camelFields[0]] || "string"}[]`
292
+ : `Pick<${entityNamePascal}, ${camelFields.map((f) => `'${f}'`).join(" | ")}>[]`;
293
+ } else {
294
+ returnType = `${entityNamePascal}[]`;
295
+ }
296
+
297
+ const methodPascal = pascalCase(methodName);
298
+ const useCaseClassName =
299
+ methodPascal.replace(/^Find/, `Find${entityNamePascal}`) + "UseCase";
300
+
301
+ return {
302
+ by: byFields,
303
+ unique: isUnique,
304
+ select: selectFields,
305
+ order: q.order ?? null,
306
+ limit: q.limit ?? null,
307
+ methodName,
308
+ returnType,
309
+ params,
310
+ isUnique,
311
+ orderBy,
312
+ orderDirection,
313
+ selectFields: selectFields.map((f) => camelCase(f)),
314
+ useCaseClassName,
315
+ hasSelect: selectFields.length > 0,
316
+ hasOrder: q.order != null,
317
+ hasLimit: q.limit != null,
318
+ hasMultipleParams: params.length > 1,
319
+ };
320
+ });
321
+ }
322
+
323
+ // ============================================================================
324
+ // On-Delete Action Mapping
325
+ // ============================================================================
326
+
327
+ const ON_DELETE_MAP = {
328
+ restrict: "restrict",
329
+ cascade: "cascade",
330
+ set_null: "set null",
331
+ no_action: "no action",
332
+ };
333
+
334
+ // ============================================================================
335
+ // Main Export
336
+ // ============================================================================
337
+
338
+ export default {
339
+ prompt: async ({ args }) => {
340
+ const yamlPath = args.yaml;
341
+ if (!yamlPath) {
342
+ throw new Error(
343
+ "Missing --yaml argument. Usage: bunx hygen relationship new --yaml relationships/person_organization.yaml"
344
+ );
345
+ }
346
+
347
+ // Load and parse YAML
348
+ const fullPath = path.resolve(process.cwd(), yamlPath);
349
+ if (!fs.existsSync(fullPath)) {
350
+ throw new Error(`File not found: ${fullPath}`);
351
+ }
352
+
353
+ const content = fs.readFileSync(fullPath, "utf-8");
354
+ const definition = yaml.parse(content);
355
+
356
+ if (!definition.relationship) {
357
+ throw new Error(
358
+ `Not a relationship definition — expected top-level 'relationship:' key in ${yamlPath}`
359
+ );
360
+ }
361
+
362
+ const config = definition.relationship;
363
+ const fields = definition.fields || {};
364
+ const queriesBlock = definition.queries || null;
365
+
366
+ // ======================================================================
367
+ // Derive auto-generated metadata
368
+ // ======================================================================
369
+
370
+ const { fromColumn, toColumn } = deriveRelationshipFKColumns(config);
371
+ const tableName = deriveTableName(config);
372
+ const uniqueOn = deriveUniqueConstraint(config);
373
+ const typeNames = collectTypeNames(config.types);
374
+ const hasTypes = typeNames.length > 0;
375
+ const selfReferential = config.from === config.to;
376
+ const temporal = config.temporal !== false; // default true
377
+ const sourced = config.sourced !== false; // default true
378
+
379
+ // On-delete actions
380
+ const onDeleteFrom = config.on_delete_from ?? "restrict";
381
+ const onDeleteTo = config.on_delete_to ?? "restrict";
382
+ const onDeleteFromSql = ON_DELETE_MAP[onDeleteFrom] || "restrict";
383
+ const onDeleteToSql = ON_DELETE_MAP[onDeleteTo] || "restrict";
384
+
385
+ // ======================================================================
386
+ // Name variations
387
+ // ======================================================================
388
+
389
+ const name = config.name; // person_organization
390
+ const entityNamePascal = pascalCase(name); // PersonOrganization
391
+ const entityNameCamel = camelCase(name); // personOrganization
392
+ const entityNamePlural = tableName; // person_organizations
393
+ const tableVarName = camelCase(entityNamePlural); // personOrganizations
394
+ const entityNamePluralPascal = pascalCase(entityNamePlural); // PersonOrganizations
395
+ const entityNameKebab = kebabCase(name); // person-organization
396
+ const entityNamePluralKebab = kebabCase(entityNamePlural); // person-organizations
397
+
398
+ // From/to entity name variations
399
+ const fromEntityPascal = pascalCase(config.from);
400
+ const toEntityPascal = pascalCase(config.to);
401
+ const fromEntityPlural = pluralize(config.from);
402
+ const toEntityPlural = pluralize(config.to);
403
+ const fromColumnCamel = camelCase(fromColumn);
404
+ const toColumnCamel = camelCase(toColumn);
405
+
406
+ // ======================================================================
407
+ // Process custom fields
408
+ // ======================================================================
409
+
410
+ const processedFields = processFields(fields);
411
+ const enumFields = processedFields.filter((f) => f.hasChoices);
412
+
413
+ // Build a lookup of ALL fields (auto-generated + custom) for query resolution
414
+ const allFieldsTypeMap = {};
415
+
416
+ // Auto-generated FK fields
417
+ allFieldsTypeMap[fromColumn] = "string";
418
+ allFieldsTypeMap[fromColumnCamel] = "string";
419
+ allFieldsTypeMap[toColumn] = "string";
420
+ allFieldsTypeMap[toColumnCamel] = "string";
421
+
422
+ // Type field
423
+ if (hasTypes) {
424
+ const typeUnion = typeNames.map((t) => `'${t}'`).join(" | ");
425
+ allFieldsTypeMap["type"] = typeUnion;
426
+ }
427
+
428
+ // Temporal fields
429
+ if (temporal) {
430
+ allFieldsTypeMap["valid_from"] = "Date";
431
+ allFieldsTypeMap["validFrom"] = "Date";
432
+ allFieldsTypeMap["valid_to"] = "Date";
433
+ allFieldsTypeMap["validTo"] = "Date";
434
+ allFieldsTypeMap["is_current"] = "boolean";
435
+ allFieldsTypeMap["isCurrent"] = "boolean";
436
+ }
437
+
438
+ // Sourced fields
439
+ if (sourced) {
440
+ allFieldsTypeMap["source"] = "string";
441
+ allFieldsTypeMap["confidence"] = "number";
442
+ }
443
+
444
+ // Custom fields
445
+ for (const pf of processedFields) {
446
+ allFieldsTypeMap[pf.name] = pf.tsType;
447
+ allFieldsTypeMap[pf.camelName] = pf.tsType;
448
+ }
449
+
450
+ // ======================================================================
451
+ // Process declarative queries
452
+ // ======================================================================
453
+
454
+ const processedQueries = processQueries(
455
+ queriesBlock,
456
+ allFieldsTypeMap,
457
+ entityNamePascal
458
+ );
459
+ const hasDeclarativeQueries = processedQueries.length > 0;
460
+ const declarativeQueryClasses = processedQueries.map((q) => q.useCaseClassName);
461
+ const hasMultiFieldQuery = processedQueries.some((q) => q.hasMultipleParams);
462
+ const hasOrderedQuery = processedQueries.some((q) => q.hasOrder);
463
+
464
+ // ======================================================================
465
+ // Drizzle imports
466
+ // ======================================================================
467
+
468
+ const drizzleImportsNeeded = new Set(["pgTable", "uuid"]);
469
+
470
+ // FK columns need uuid
471
+ drizzleImportsNeeded.add("uuid");
472
+
473
+ // Type enum
474
+ if (hasTypes) {
475
+ drizzleImportsNeeded.add("pgEnum");
476
+ }
477
+
478
+ // Custom field enums
479
+ if (enumFields.length > 0) {
480
+ drizzleImportsNeeded.add("pgEnum");
481
+ }
482
+
483
+ // Sourced — source is also an enum
484
+ if (sourced) {
485
+ drizzleImportsNeeded.add("pgEnum");
486
+ }
487
+
488
+ // Temporal fields
489
+ if (temporal) {
490
+ drizzleImportsNeeded.add("date");
491
+ drizzleImportsNeeded.add("boolean");
492
+ }
493
+
494
+ // Sourced fields
495
+ if (sourced) {
496
+ drizzleImportsNeeded.add("numeric");
497
+ }
498
+
499
+ // Timestamps (always present on relationships)
500
+ drizzleImportsNeeded.add("timestamp");
501
+
502
+ // Custom field types
503
+ for (const field of processedFields) {
504
+ const importName = DRIZZLE_IMPORT_MAP[field.drizzleType];
505
+ if (importName) drizzleImportsNeeded.add(importName);
506
+ }
507
+
508
+ // Unique constraint
509
+ drizzleImportsNeeded.add("uniqueIndex");
510
+
511
+ const drizzleImports = Array.from(drizzleImportsNeeded).sort();
512
+
513
+ // ======================================================================
514
+ // Zod chains for DTO fields
515
+ // ======================================================================
516
+
517
+ const createDtoFields = processedFields.map((f) => ({
518
+ ...f,
519
+ zodChainCreate: zodChainForCreate(f),
520
+ }));
521
+
522
+ const outputDtoFields = processedFields.map((f) => ({
523
+ ...f,
524
+ zodChainOutput: zodChainForOutput(f),
525
+ }));
526
+
527
+ // ======================================================================
528
+ // Source root — default to 'src'
529
+ // ======================================================================
530
+
531
+ const srcRoot = "src";
532
+
533
+ // ======================================================================
534
+ // Output paths (mirrors clean-lite-ps layout)
535
+ // ======================================================================
536
+
537
+ const outputPaths = {
538
+ entity: `${srcRoot}/modules/${entityNamePlural}/${name}.entity.ts`,
539
+ repository: `${srcRoot}/modules/${entityNamePlural}/${name}.repository.ts`,
540
+ service: `${srcRoot}/modules/${entityNamePlural}/${name}.service.ts`,
541
+ controller: `${srcRoot}/modules/${entityNamePlural}/${name}.controller.ts`,
542
+ module: `${srcRoot}/modules/${entityNamePlural}/${entityNamePlural}.module.ts`,
543
+ createDto: `${srcRoot}/modules/${entityNamePlural}/dto/create-${name}.dto.ts`,
544
+ updateDto: `${srcRoot}/modules/${entityNamePlural}/dto/update-${name}.dto.ts`,
545
+ outputDto: `${srcRoot}/modules/${entityNamePlural}/dto/${name}-output.dto.ts`,
546
+ index: `${srcRoot}/modules/${entityNamePlural}/index.ts`,
547
+ findByIdUseCase: `${srcRoot}/modules/${entityNamePlural}/use-cases/find-${name}-by-id.use-case.ts`,
548
+ listUseCase: `${srcRoot}/modules/${entityNamePlural}/use-cases/list-${entityNamePlural}.use-case.ts`,
549
+ declarativeQueries: hasDeclarativeQueries
550
+ ? `${srcRoot}/modules/${entityNamePlural}/use-cases/declarative-queries.ts`
551
+ : null,
552
+ };
553
+
554
+ // ======================================================================
555
+ // Class names
556
+ // ======================================================================
557
+
558
+ const classNames = {
559
+ entity: entityNamePascal,
560
+ entityTable: entityNamePlural,
561
+ repository: `${entityNamePascal}Repository`,
562
+ service: `${entityNamePascal}Service`,
563
+ controller: `${entityNamePascal}Controller`,
564
+ module: `${entityNamePluralPascal}Module`,
565
+ findByIdUseCase: `Find${entityNamePascal}ByIdUseCase`,
566
+ listUseCase: `List${entityNamePluralPascal}UseCase`,
567
+ createDto: `Create${entityNamePascal}Dto`,
568
+ updateDto: `Update${entityNamePascal}Dto`,
569
+ outputDto: `${entityNamePascal}OutputDto`,
570
+ createSchema: `Create${entityNamePascal}Schema`,
571
+ updateSchema: `Update${entityNamePascal}Schema`,
572
+ outputSchema: `${entityNamePascal}OutputSchema`,
573
+ };
574
+
575
+ // ======================================================================
576
+ // Unique constraint columns with camelCase names
577
+ // ======================================================================
578
+
579
+ const uniqueOnCamel = uniqueOn.map((col) => camelCase(col));
580
+
581
+ // ======================================================================
582
+ // Type enum details
583
+ // ======================================================================
584
+
585
+ const typeEnumName = hasTypes ? `${entityNameCamel}TypeEnum` : null;
586
+ const typeEnumValues = typeNames;
587
+
588
+ // Source enum (if sourced)
589
+ const sourceEnumName = sourced ? `${entityNameCamel}SourceEnum` : null;
590
+ const sourceEnumValues = sourced
591
+ ? ["manual", "system", "import", "integration", "ai"]
592
+ : [];
593
+
594
+ // ======================================================================
595
+ // Return all template locals
596
+ // ======================================================================
597
+
598
+ return {
599
+ // Identity
600
+ name,
601
+ entityNamePascal,
602
+ entityNameCamel,
603
+ entityNamePlural,
604
+ entityNamePluralPascal,
605
+ entityNameKebab,
606
+ entityNamePluralKebab,
607
+ tableName,
608
+ tableVarName,
609
+
610
+ // Relationship config
611
+ from: config.from,
612
+ to: config.to,
613
+ fromEntityPascal,
614
+ toEntityPascal,
615
+ fromEntityPlural,
616
+ toEntityPlural,
617
+ selfReferential,
618
+
619
+ // FK columns
620
+ fromColumn,
621
+ toColumn,
622
+ fromColumnCamel,
623
+ toColumnCamel,
624
+
625
+ // Type taxonomy
626
+ hasTypes,
627
+ typeNames,
628
+ typeEnumName,
629
+ typeEnumValues,
630
+
631
+ // Behavioral flags
632
+ temporal,
633
+ sourced,
634
+
635
+ // On-delete
636
+ onDeleteFrom,
637
+ onDeleteTo,
638
+ onDeleteFromSql,
639
+ onDeleteToSql,
640
+
641
+ // Unique constraint
642
+ uniqueOn,
643
+ uniqueOnCamel,
644
+
645
+ // Source tracking
646
+ sourceEnumName,
647
+ sourceEnumValues,
648
+
649
+ // Custom fields
650
+ processedFields,
651
+ enumFields,
652
+ hasCustomFields: processedFields.length > 0,
653
+
654
+ // DTO fields
655
+ createDtoFields,
656
+ outputDtoFields,
657
+
658
+ // Declarative queries
659
+ processedQueries,
660
+ hasDeclarativeQueries,
661
+ declarativeQueryClasses,
662
+ hasMultiFieldQuery,
663
+ hasOrderedQuery,
664
+
665
+ // Drizzle
666
+ drizzleImports,
667
+
668
+ // Output paths
669
+ outputPaths,
670
+
671
+ // Class names
672
+ classNames,
673
+
674
+ // srcRoot
675
+ srcRoot,
676
+
677
+ // From entity table references (for FK .references())
678
+ fromTable: fromEntityPlural,
679
+ toTable: toEntityPlural,
680
+ };
681
+ },
682
+ };