@warlock.js/cascade 4.8.2 → 4.9.1

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 (319) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/cjs/index.cjs +172 -127
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/context/database-data-source-context.d.mts +1 -1
  5. package/esm/context/database-data-source-context.d.mts.map +1 -1
  6. package/esm/context/database-data-source-context.mjs +1 -1
  7. package/esm/context/database-data-source-context.mjs.map +1 -1
  8. package/esm/context/database-transaction-context.d.mts +1 -1
  9. package/esm/context/database-transaction-context.d.mts.map +1 -1
  10. package/esm/context/database-transaction-context.mjs +1 -1
  11. package/esm/context/database-transaction-context.mjs.map +1 -1
  12. package/esm/contracts/database-driver.contract.d.mts +1 -1
  13. package/esm/contracts/database-driver.contract.d.mts.map +1 -1
  14. package/esm/contracts/database-id-generator.contract.d.mts +1 -1
  15. package/esm/contracts/database-id-generator.contract.d.mts.map +1 -1
  16. package/esm/contracts/database-remover.contract.d.mts +1 -1
  17. package/esm/contracts/database-remover.contract.d.mts.map +1 -1
  18. package/esm/contracts/database-restorer.contract.d.mts +1 -1
  19. package/esm/contracts/database-restorer.contract.d.mts.map +1 -1
  20. package/esm/contracts/database-writer.contract.d.mts +1 -1
  21. package/esm/contracts/database-writer.contract.d.mts.map +1 -1
  22. package/esm/contracts/driver-blueprint.contract.d.mts +1 -1
  23. package/esm/contracts/driver-blueprint.contract.d.mts.map +1 -1
  24. package/esm/contracts/migration-driver.contract.d.mts +1 -1
  25. package/esm/contracts/migration-driver.contract.d.mts.map +1 -1
  26. package/esm/contracts/query-builder.contract.d.mts +1 -1
  27. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  28. package/esm/contracts/sync-adapter.contract.d.mts +1 -1
  29. package/esm/contracts/sync-adapter.contract.d.mts.map +1 -1
  30. package/esm/data-source/data-source-registry.d.mts +1 -1
  31. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  32. package/esm/data-source/data-source-registry.mjs +1 -1
  33. package/esm/data-source/data-source-registry.mjs.map +1 -1
  34. package/esm/data-source/data-source.d.mts +1 -1
  35. package/esm/data-source/data-source.d.mts.map +1 -1
  36. package/esm/data-source/data-source.mjs +1 -1
  37. package/esm/data-source/data-source.mjs.map +1 -1
  38. package/esm/database-dirty-tracker.d.mts +12 -3
  39. package/esm/database-dirty-tracker.d.mts.map +1 -1
  40. package/esm/database-dirty-tracker.mjs +13 -4
  41. package/esm/database-dirty-tracker.mjs.map +1 -1
  42. package/esm/drivers/mongodb/mongodb-blueprint.mjs +1 -1
  43. package/esm/drivers/mongodb/mongodb-blueprint.mjs.map +1 -1
  44. package/esm/drivers/mongodb/mongodb-driver.d.mts +1 -1
  45. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  46. package/esm/drivers/mongodb/mongodb-driver.mjs +1 -1
  47. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  48. package/esm/drivers/mongodb/mongodb-id-generator.d.mts +1 -1
  49. package/esm/drivers/mongodb/mongodb-id-generator.d.mts.map +1 -1
  50. package/esm/drivers/mongodb/mongodb-id-generator.mjs +1 -1
  51. package/esm/drivers/mongodb/mongodb-id-generator.mjs.map +1 -1
  52. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
  53. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
  54. package/esm/drivers/mongodb/mongodb-migration-driver.mjs +1 -1
  55. package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
  56. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +1 -1
  57. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  58. package/esm/drivers/mongodb/mongodb-query-builder.mjs +1 -1
  59. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  60. package/esm/drivers/mongodb/mongodb-query-operations.d.mts +1 -1
  61. package/esm/drivers/mongodb/mongodb-query-operations.d.mts.map +1 -1
  62. package/esm/drivers/mongodb/mongodb-query-operations.mjs +1 -1
  63. package/esm/drivers/mongodb/mongodb-query-operations.mjs.map +1 -1
  64. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +1 -1
  65. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  66. package/esm/drivers/mongodb/mongodb-query-parser.mjs +1 -1
  67. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  68. package/esm/drivers/mongodb/mongodb-sync-adapter.d.mts +1 -1
  69. package/esm/drivers/mongodb/mongodb-sync-adapter.d.mts.map +1 -1
  70. package/esm/drivers/mongodb/mongodb-sync-adapter.mjs +1 -1
  71. package/esm/drivers/mongodb/mongodb-sync-adapter.mjs.map +1 -1
  72. package/esm/drivers/mongodb/types.d.mts +1 -1
  73. package/esm/drivers/mongodb/types.d.mts.map +1 -1
  74. package/esm/drivers/postgres/postgres-blueprint.d.mts +1 -1
  75. package/esm/drivers/postgres/postgres-blueprint.d.mts.map +1 -1
  76. package/esm/drivers/postgres/postgres-blueprint.mjs +1 -1
  77. package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
  78. package/esm/drivers/postgres/postgres-dialect.d.mts +1 -1
  79. package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
  80. package/esm/drivers/postgres/postgres-dialect.mjs +1 -1
  81. package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
  82. package/esm/drivers/postgres/postgres-driver.d.mts +1 -1
  83. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  84. package/esm/drivers/postgres/postgres-driver.mjs +1 -1
  85. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  86. package/esm/drivers/postgres/postgres-migration-driver.d.mts +1 -1
  87. package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
  88. package/esm/drivers/postgres/postgres-migration-driver.mjs +1 -1
  89. package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
  90. package/esm/drivers/postgres/postgres-query-builder.d.mts +1 -1
  91. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  92. package/esm/drivers/postgres/postgres-query-builder.mjs +1 -1
  93. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  94. package/esm/drivers/postgres/postgres-query-parser.d.mts +1 -1
  95. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  96. package/esm/drivers/postgres/postgres-query-parser.mjs +1 -1
  97. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  98. package/esm/drivers/postgres/postgres-sql-serializer.mjs +1 -1
  99. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  100. package/esm/drivers/postgres/postgres-sync-adapter.d.mts +1 -1
  101. package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +1 -1
  102. package/esm/drivers/postgres/postgres-sync-adapter.mjs +1 -1
  103. package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +1 -1
  104. package/esm/drivers/postgres/types.d.mts +1 -1
  105. package/esm/drivers/postgres/types.d.mts.map +1 -1
  106. package/esm/drivers/sql/sql-dialect.contract.d.mts +1 -1
  107. package/esm/drivers/sql/sql-dialect.contract.d.mts.map +1 -1
  108. package/esm/drivers/sql/sql-types.d.mts +1 -1
  109. package/esm/drivers/sql/sql-types.d.mts.map +1 -1
  110. package/esm/errors/missing-data-source.error.d.mts +1 -1
  111. package/esm/errors/missing-data-source.error.d.mts.map +1 -1
  112. package/esm/errors/missing-data-source.error.mjs +1 -1
  113. package/esm/errors/missing-data-source.error.mjs.map +1 -1
  114. package/esm/errors/transaction-rollback.error.d.mts +1 -1
  115. package/esm/errors/transaction-rollback.error.d.mts.map +1 -1
  116. package/esm/errors/transaction-rollback.error.mjs +1 -1
  117. package/esm/errors/transaction-rollback.error.mjs.map +1 -1
  118. package/esm/events/model-events.d.mts +1 -1
  119. package/esm/events/model-events.d.mts.map +1 -1
  120. package/esm/events/model-events.mjs +1 -1
  121. package/esm/events/model-events.mjs.map +1 -1
  122. package/esm/expressions/aggregate-expressions.d.mts +1 -1
  123. package/esm/expressions/aggregate-expressions.d.mts.map +1 -1
  124. package/esm/expressions/aggregate-expressions.mjs +1 -1
  125. package/esm/expressions/aggregate-expressions.mjs.map +1 -1
  126. package/esm/expressions/column-expressions.d.mts +1 -1
  127. package/esm/expressions/column-expressions.d.mts.map +1 -1
  128. package/esm/expressions/column-expressions.mjs +1 -1
  129. package/esm/expressions/column-expressions.mjs.map +1 -1
  130. package/esm/migration/column-builder.d.mts +1 -1
  131. package/esm/migration/column-builder.d.mts.map +1 -1
  132. package/esm/migration/column-builder.mjs +1 -1
  133. package/esm/migration/column-builder.mjs.map +1 -1
  134. package/esm/migration/column-helpers.d.mts +1 -1
  135. package/esm/migration/column-helpers.d.mts.map +1 -1
  136. package/esm/migration/column-helpers.mjs +1 -1
  137. package/esm/migration/column-helpers.mjs.map +1 -1
  138. package/esm/migration/foreign-key-builder.d.mts +1 -1
  139. package/esm/migration/foreign-key-builder.d.mts.map +1 -1
  140. package/esm/migration/foreign-key-builder.mjs +1 -1
  141. package/esm/migration/foreign-key-builder.mjs.map +1 -1
  142. package/esm/migration/migration-runner.d.mts +1 -1
  143. package/esm/migration/migration-runner.d.mts.map +1 -1
  144. package/esm/migration/migration-runner.mjs +4 -40
  145. package/esm/migration/migration-runner.mjs.map +1 -1
  146. package/esm/migration/migration.d.mts +1 -1
  147. package/esm/migration/migration.d.mts.map +1 -1
  148. package/esm/migration/migration.mjs +1 -1
  149. package/esm/migration/migration.mjs.map +1 -1
  150. package/esm/migration/parse-created-at.mjs +67 -0
  151. package/esm/migration/parse-created-at.mjs.map +1 -0
  152. package/esm/migration/sql-grammar.mjs +16 -6
  153. package/esm/migration/sql-grammar.mjs.map +1 -1
  154. package/esm/migration/sql-serializer.d.mts +1 -1
  155. package/esm/migration/sql-serializer.d.mts.map +1 -1
  156. package/esm/migration/sql-serializer.mjs +1 -1
  157. package/esm/migration/sql-serializer.mjs.map +1 -1
  158. package/esm/migration/types.d.mts +1 -1
  159. package/esm/migration/types.d.mts.map +1 -1
  160. package/esm/model/methods/accessor-methods.mjs +1 -1
  161. package/esm/model/methods/accessor-methods.mjs.map +1 -1
  162. package/esm/model/methods/delete-methods.mjs +1 -1
  163. package/esm/model/methods/delete-methods.mjs.map +1 -1
  164. package/esm/model/methods/dirty-methods.mjs +1 -1
  165. package/esm/model/methods/dirty-methods.mjs.map +1 -1
  166. package/esm/model/methods/hydration-methods.mjs +1 -1
  167. package/esm/model/methods/hydration-methods.mjs.map +1 -1
  168. package/esm/model/methods/instance-event-methods.mjs +1 -1
  169. package/esm/model/methods/instance-event-methods.mjs.map +1 -1
  170. package/esm/model/methods/meta-methods.mjs +1 -1
  171. package/esm/model/methods/meta-methods.mjs.map +1 -1
  172. package/esm/model/methods/pivot-methods.mjs +1 -1
  173. package/esm/model/methods/pivot-methods.mjs.map +1 -1
  174. package/esm/model/methods/query-methods.mjs +1 -1
  175. package/esm/model/methods/query-methods.mjs.map +1 -1
  176. package/esm/model/methods/restore-methods.mjs +1 -1
  177. package/esm/model/methods/restore-methods.mjs.map +1 -1
  178. package/esm/model/methods/scope-methods.mjs +1 -1
  179. package/esm/model/methods/scope-methods.mjs.map +1 -1
  180. package/esm/model/methods/serialization-methods.mjs +1 -1
  181. package/esm/model/methods/serialization-methods.mjs.map +1 -1
  182. package/esm/model/methods/static-event-methods.mjs +1 -1
  183. package/esm/model/methods/static-event-methods.mjs.map +1 -1
  184. package/esm/model/methods/write-methods.d.mts +1 -1
  185. package/esm/model/methods/write-methods.d.mts.map +1 -1
  186. package/esm/model/methods/write-methods.mjs +1 -1
  187. package/esm/model/methods/write-methods.mjs.map +1 -1
  188. package/esm/model/model.d.mts +1 -1
  189. package/esm/model/model.d.mts.map +1 -1
  190. package/esm/model/model.mjs +1 -1
  191. package/esm/model/model.mjs.map +1 -1
  192. package/esm/model/model.types.d.mts +1 -1
  193. package/esm/model/model.types.d.mts.map +1 -1
  194. package/esm/model/register-model.d.mts +1 -1
  195. package/esm/model/register-model.d.mts.map +1 -1
  196. package/esm/model/register-model.mjs +1 -1
  197. package/esm/model/register-model.mjs.map +1 -1
  198. package/esm/model/relation-decorators.d.mts +1 -1
  199. package/esm/model/relation-decorators.d.mts.map +1 -1
  200. package/esm/model/relation-decorators.mjs +1 -1
  201. package/esm/model/relation-decorators.mjs.map +1 -1
  202. package/esm/operations/database.d.mts +1 -1
  203. package/esm/operations/database.d.mts.map +1 -1
  204. package/esm/operations/database.mjs +1 -1
  205. package/esm/operations/database.mjs.map +1 -1
  206. package/esm/operations/migrations.d.mts +1 -1
  207. package/esm/operations/migrations.d.mts.map +1 -1
  208. package/esm/operations/migrations.mjs +1 -1
  209. package/esm/operations/migrations.mjs.map +1 -1
  210. package/esm/query-builder/query-builder.d.mts +1 -1
  211. package/esm/query-builder/query-builder.d.mts.map +1 -1
  212. package/esm/query-builder/query-builder.mjs +1 -1
  213. package/esm/query-builder/query-builder.mjs.map +1 -1
  214. package/esm/relations/key-conventions.mjs +1 -1
  215. package/esm/relations/key-conventions.mjs.map +1 -1
  216. package/esm/relations/pivot-operations.d.mts +1 -1
  217. package/esm/relations/pivot-operations.d.mts.map +1 -1
  218. package/esm/relations/pivot-operations.mjs +1 -1
  219. package/esm/relations/pivot-operations.mjs.map +1 -1
  220. package/esm/relations/relation-hydrator.d.mts +1 -1
  221. package/esm/relations/relation-hydrator.d.mts.map +1 -1
  222. package/esm/relations/relation-hydrator.mjs +1 -1
  223. package/esm/relations/relation-hydrator.mjs.map +1 -1
  224. package/esm/relations/relation-loader.d.mts +1 -1
  225. package/esm/relations/relation-loader.d.mts.map +1 -1
  226. package/esm/relations/relation-loader.mjs +1 -1
  227. package/esm/relations/relation-loader.mjs.map +1 -1
  228. package/esm/relations/types.d.mts +1 -1
  229. package/esm/relations/types.d.mts.map +1 -1
  230. package/esm/remover/database-remover.d.mts +1 -1
  231. package/esm/remover/database-remover.d.mts.map +1 -1
  232. package/esm/remover/database-remover.mjs +1 -1
  233. package/esm/remover/database-remover.mjs.map +1 -1
  234. package/esm/restorer/database-restorer.d.mts +1 -1
  235. package/esm/restorer/database-restorer.d.mts.map +1 -1
  236. package/esm/restorer/database-restorer.mjs +1 -1
  237. package/esm/restorer/database-restorer.mjs.map +1 -1
  238. package/esm/sql-database-dirty-tracker.d.mts +1 -1
  239. package/esm/sql-database-dirty-tracker.d.mts.map +1 -1
  240. package/esm/sql-database-dirty-tracker.mjs +1 -1
  241. package/esm/sql-database-dirty-tracker.mjs.map +1 -1
  242. package/esm/sync/model-events.mjs +1 -1
  243. package/esm/sync/model-events.mjs.map +1 -1
  244. package/esm/sync/model-sync-operation.d.mts +1 -1
  245. package/esm/sync/model-sync-operation.d.mts.map +1 -1
  246. package/esm/sync/model-sync-operation.mjs +1 -1
  247. package/esm/sync/model-sync-operation.mjs.map +1 -1
  248. package/esm/sync/model-sync.d.mts +1 -1
  249. package/esm/sync/model-sync.d.mts.map +1 -1
  250. package/esm/sync/model-sync.mjs +1 -1
  251. package/esm/sync/model-sync.mjs.map +1 -1
  252. package/esm/sync/sync-context.d.mts +1 -1
  253. package/esm/sync/sync-context.d.mts.map +1 -1
  254. package/esm/sync/sync-context.mjs +1 -1
  255. package/esm/sync/sync-context.mjs.map +1 -1
  256. package/esm/sync/sync-manager.d.mts +1 -1
  257. package/esm/sync/sync-manager.d.mts.map +1 -1
  258. package/esm/sync/sync-manager.mjs +1 -1
  259. package/esm/sync/sync-manager.mjs.map +1 -1
  260. package/esm/sync/types.d.mts +1 -1
  261. package/esm/sync/types.d.mts.map +1 -1
  262. package/esm/types.d.mts +1 -1
  263. package/esm/types.d.mts.map +1 -1
  264. package/esm/utils/connect-to-database.d.mts +1 -1
  265. package/esm/utils/connect-to-database.d.mts.map +1 -1
  266. package/esm/utils/connect-to-database.mjs +1 -1
  267. package/esm/utils/connect-to-database.mjs.map +1 -1
  268. package/esm/utils/database-writer.utils.d.mts +1 -1
  269. package/esm/utils/database-writer.utils.d.mts.map +1 -1
  270. package/esm/utils/database-writer.utils.mjs +1 -1
  271. package/esm/utils/database-writer.utils.mjs.map +1 -1
  272. package/esm/utils/define-model.d.mts +1 -1
  273. package/esm/utils/define-model.d.mts.map +1 -1
  274. package/esm/utils/define-model.mjs +1 -1
  275. package/esm/utils/define-model.mjs.map +1 -1
  276. package/esm/utils/is-valid-date-value.mjs +1 -1
  277. package/esm/utils/is-valid-date-value.mjs.map +1 -1
  278. package/esm/utils/once-connected.d.mts +1 -1
  279. package/esm/utils/once-connected.d.mts.map +1 -1
  280. package/esm/utils/once-connected.mjs +1 -1
  281. package/esm/utils/once-connected.mjs.map +1 -1
  282. package/esm/validation/database-seal-plugins.mjs +1 -1
  283. package/esm/validation/database-seal-plugins.mjs.map +1 -1
  284. package/esm/validation/database-writer-validation-error.d.mts +1 -1
  285. package/esm/validation/database-writer-validation-error.d.mts.map +1 -1
  286. package/esm/validation/database-writer-validation-error.mjs +1 -1
  287. package/esm/validation/database-writer-validation-error.mjs.map +1 -1
  288. package/esm/validation/mutators/embed-mutator.mjs +1 -1
  289. package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
  290. package/esm/validation/plugins/database-rules-plugin.d.mts +1 -1
  291. package/esm/validation/plugins/database-rules-plugin.d.mts.map +1 -1
  292. package/esm/validation/plugins/database-rules-plugin.mjs +1 -1
  293. package/esm/validation/plugins/database-rules-plugin.mjs.map +1 -1
  294. package/esm/validation/plugins/embed-validator-plugin.d.mts +1 -1
  295. package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
  296. package/esm/validation/plugins/embed-validator-plugin.mjs +1 -1
  297. package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
  298. package/esm/validation/rules/database-model-rule.mjs +1 -1
  299. package/esm/validation/rules/database-model-rule.mjs.map +1 -1
  300. package/esm/validation/rules/exists-rule.mjs +1 -1
  301. package/esm/validation/rules/exists-rule.mjs.map +1 -1
  302. package/esm/validation/rules/unique-rule.mjs +1 -1
  303. package/esm/validation/rules/unique-rule.mjs.map +1 -1
  304. package/esm/validation/transformers/embed-model-transformer.mjs +1 -1
  305. package/esm/validation/transformers/embed-model-transformer.mjs.map +1 -1
  306. package/esm/validation/types.d.mts +1 -1
  307. package/esm/validation/types.d.mts.map +1 -1
  308. package/esm/validation/validators/embed-validator.d.mts +1 -1
  309. package/esm/validation/validators/embed-validator.d.mts.map +1 -1
  310. package/esm/validation/validators/embed-validator.mjs +1 -1
  311. package/esm/validation/validators/embed-validator.mjs.map +1 -1
  312. package/esm/writer/database-writer.d.mts +1 -1
  313. package/esm/writer/database-writer.d.mts.map +1 -1
  314. package/esm/writer/database-writer.mjs +1 -1
  315. package/esm/writer/database-writer.mjs.map +1 -1
  316. package/llms-full.txt +32 -0
  317. package/package.json +4 -4
  318. package/skills/track-changes/SKILL.md +18 -0
  319. package/skills/write-migration/SKILL.md +14 -0
@@ -1 +1 @@
1
- {"version":3,"file":"key-conventions.mjs","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/key-conventions.ts"],"sourcesContent":["/**\n * @fileoverview Foreign-key naming conventions for relation defaults.\n *\n * Single source of truth for default FK / pivot column names when a relation\n * definition omits an explicit `foreignKey`. Centralised so the three\n * relation-resolution code paths (RelationLoader, applyJoinRelations,\n * applyCountRelations) stay consistent — historically each path defaulted\n * differently, producing silent wrong-column bugs.\n *\n * Convention: snake_case + `_id` suffix. Matches PostgreSQL idiom and the\n * existing column convention used throughout `src/app/**` (`organization_id`,\n * `chat_id`, `image_id`, …).\n *\n * @module @warlock.js/cascade/relations/key-conventions\n */\n\nimport { toSnakeCase } from \"@mongez/reinforcements\";\nimport type { RelationDefaults } from \"../types\";\n\nconst DEFAULT_FK_SUFFIX = \"_id\";\nconst DEFAULT_PIVOT_ORDER: NonNullable<RelationDefaults[\"pivotTableNamingOrder\"]> = \"alphabetical\";\n\n// ============================================================================\n// PRIVATE — STRING NORMALISATION\n// ============================================================================\n\n/**\n * Convert a model class name or relation name to snake_case.\n *\n * Wraps `toSnakeCase` from `@mongez/reinforcements` because the lib's regex\n * loses the leading letters in runs of consecutive uppercase\n * (`toSnakeCase(\"AIModel\")` → `\"imodel\"`). The two pre-normalisation passes\n * here insert separators at run boundaries before delegating, so acronym-\n * prefixed model names (`AIModel`, `AIUsage`, `AITrip`, `HTTPSConnection`)\n * snake correctly.\n *\n * Once an upstream fix lands in `@mongez/reinforcements`, this wrapper can\n * be deleted and `toSnakeCase` called directly at the consumer sites.\n *\n * @example\n * snake(\"User\") // \"user\"\n * snake(\"BlogPost\") // \"blog_post\"\n * snake(\"AIModel\") // \"ai_model\"\n * snake(\"HTTPSConnection\") // \"https_connection\"\n * snake(\"organization\") // \"organization\" (idempotent)\n * snake(\"organizationId\") // \"organization_id\"\n */\nfunction snake(input: string): string {\n const normalised = input\n // Split a run of caps from a final cap+lower: \"AIModel\" → \"AI_Model\"\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\")\n // Split a lower/digit boundary from the next cap: \"BlogPost\" → \"Blog_Post\"\n .replace(/([a-z\\d])([A-Z])/g, \"$1_$2\");\n\n return toSnakeCase(normalised).toLowerCase();\n}\n\n// ============================================================================\n// PUBLIC — DEFAULT FOREIGN-KEY INFERENCE\n// ============================================================================\n\n/**\n * Default foreign-key column name for a `belongsTo` relation.\n *\n * The FK lives on THIS model (the owner of the `belongsTo` def) and is\n * named after the *role* the relation plays — not the target model class.\n * Matches Laravel/Rails convention: `Post.author_id` (not `Post.user_id`)\n * because the relation is named \"author\".\n *\n * @param relationName - The relation key as declared in `static relations`\n *\n * @example\n * inferBelongsToForeignKey(\"author\") // \"author_id\"\n * inferBelongsToForeignKey(\"organization\") // \"organization_id\"\n * inferBelongsToForeignKey(\"parentItem\") // \"parent_item_id\"\n */\nexport function inferBelongsToForeignKey(\n relationName: string,\n options?: RelationDefaults,\n): string {\n return `${snake(relationName)}${options?.foreignKeySuffix ?? DEFAULT_FK_SUFFIX}`;\n}\n\n/**\n * Default foreign-key column name for a `hasMany` or `hasOne` relation.\n *\n * The FK lives on the RELATED model's table and points back to THIS model,\n * so the column is named after the self model class.\n *\n * @param selfModelName - The owning model's class name (e.g. `User`)\n *\n * @example\n * inferHasForeignKey(\"User\") // \"user_id\" — used as posts.user_id\n * inferHasForeignKey(\"AIModel\") // \"ai_model_id\" — used as ai_trips.ai_model_id\n */\nexport function inferHasForeignKey(\n selfModelName: string,\n options?: RelationDefaults,\n): string {\n return `${snake(selfModelName)}${options?.foreignKeySuffix ?? DEFAULT_FK_SUFFIX}`;\n}\n\n/**\n * Default column name in a many-to-many pivot table for either side\n * (the side referenced determines the model name passed in).\n *\n * - For the self-side pivot column: pass the self model name.\n * - For the related-side pivot column: pass the related model name.\n *\n * @param modelName - The model class name being referenced from the pivot\n *\n * @example\n * // For `Post.belongsToMany(\"Tag\", { pivot: \"post_tags\" })`:\n * inferPivotKey(\"Post\") // \"post_id\" — pivot column post_tags.post_id\n * inferPivotKey(\"Tag\") // \"tag_id\" — pivot column post_tags.tag_id\n */\nexport function inferPivotKey(modelName: string, options?: RelationDefaults): string {\n return `${snake(modelName)}${options?.foreignKeySuffix ?? DEFAULT_FK_SUFFIX}`;\n}\n\n/**\n * Default pivot-table name for a many-to-many relation when none is\n * explicitly configured. Joins the two model snake-names alphabetically\n * with an underscore — matches Laravel/Rails convention.\n *\n * Alphabetical ordering means the same pivot name resolves regardless of\n * which side of the relation declares the `@BelongsToMany`, so\n * `Post.belongsToMany(\"Tag\")` and `Tag.belongsToMany(\"Post\")` both pick\n * the table `post_tag`.\n *\n * @example\n * inferPivotTable(\"Post\", \"Tag\") // \"post_tag\"\n * inferPivotTable(\"User\", \"Group\") // \"group_user\"\n * inferPivotTable(\"AIModel\", \"Tenant\") // \"ai_model_tenant\"\n */\nexport function inferPivotTable(\n selfModelName: string,\n relatedModelName: string,\n options?: RelationDefaults,\n): string {\n const selfSnake = snake(selfModelName);\n const relatedSnake = snake(relatedModelName);\n const order = options?.pivotTableNamingOrder ?? DEFAULT_PIVOT_ORDER;\n\n if (order === \"owner_first\") {\n return `${selfSnake}_${relatedSnake}`;\n }\n\n return [selfSnake, relatedSnake].sort().join(\"_\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,MAAM,oBAAoB;AAC1B,MAAM,sBAA8E;;;;;;;;;;;;;;;;;;;;;;AA2BpF,SAAS,MAAM,OAAuB;CAOpC,OAAO,YANY,MAEhB,QAAQ,yBAAyB,OAAO,CAAC,CAEzC,QAAQ,qBAAqB,OAEJ,CAAC,CAAC,CAAC,YAAY;AAC7C;;;;;;;;;;;;;;;;AAqBA,SAAgB,yBACd,cACA,SACQ;CACR,OAAO,GAAG,MAAM,YAAY,IAAI,SAAS,oBAAoB;AAC/D;;;;;;;;;;;;;AAcA,SAAgB,mBACd,eACA,SACQ;CACR,OAAO,GAAG,MAAM,aAAa,IAAI,SAAS,oBAAoB;AAChE;;;;;;;;;;;;;;;AAgBA,SAAgB,cAAc,WAAmB,SAAoC;CACnF,OAAO,GAAG,MAAM,SAAS,IAAI,SAAS,oBAAoB;AAC5D;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBACd,eACA,kBACA,SACQ;CACR,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,eAAe,MAAM,gBAAgB;CAG3C,KAFc,SAAS,yBAAyB,yBAElC,eACZ,OAAO,GAAG,UAAU,GAAG;CAGzB,OAAO,CAAC,WAAW,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;AAClD"}
1
+ {"version":3,"file":"key-conventions.mjs","names":[],"sources":["../../../../../../../cascade/src/relations/key-conventions.ts"],"sourcesContent":["/**\n * @fileoverview Foreign-key naming conventions for relation defaults.\n *\n * Single source of truth for default FK / pivot column names when a relation\n * definition omits an explicit `foreignKey`. Centralised so the three\n * relation-resolution code paths (RelationLoader, applyJoinRelations,\n * applyCountRelations) stay consistent — historically each path defaulted\n * differently, producing silent wrong-column bugs.\n *\n * Convention: snake_case + `_id` suffix. Matches PostgreSQL idiom and the\n * existing column convention used throughout `src/app/**` (`organization_id`,\n * `chat_id`, `image_id`, …).\n *\n * @module @warlock.js/cascade/relations/key-conventions\n */\n\nimport { toSnakeCase } from \"@mongez/reinforcements\";\nimport type { RelationDefaults } from \"../types\";\n\nconst DEFAULT_FK_SUFFIX = \"_id\";\nconst DEFAULT_PIVOT_ORDER: NonNullable<RelationDefaults[\"pivotTableNamingOrder\"]> = \"alphabetical\";\n\n// ============================================================================\n// PRIVATE — STRING NORMALISATION\n// ============================================================================\n\n/**\n * Convert a model class name or relation name to snake_case.\n *\n * Wraps `toSnakeCase` from `@mongez/reinforcements` because the lib's regex\n * loses the leading letters in runs of consecutive uppercase\n * (`toSnakeCase(\"AIModel\")` → `\"imodel\"`). The two pre-normalisation passes\n * here insert separators at run boundaries before delegating, so acronym-\n * prefixed model names (`AIModel`, `AIUsage`, `AITrip`, `HTTPSConnection`)\n * snake correctly.\n *\n * Once an upstream fix lands in `@mongez/reinforcements`, this wrapper can\n * be deleted and `toSnakeCase` called directly at the consumer sites.\n *\n * @example\n * snake(\"User\") // \"user\"\n * snake(\"BlogPost\") // \"blog_post\"\n * snake(\"AIModel\") // \"ai_model\"\n * snake(\"HTTPSConnection\") // \"https_connection\"\n * snake(\"organization\") // \"organization\" (idempotent)\n * snake(\"organizationId\") // \"organization_id\"\n */\nfunction snake(input: string): string {\n const normalised = input\n // Split a run of caps from a final cap+lower: \"AIModel\" → \"AI_Model\"\n .replace(/([A-Z]+)([A-Z][a-z])/g, \"$1_$2\")\n // Split a lower/digit boundary from the next cap: \"BlogPost\" → \"Blog_Post\"\n .replace(/([a-z\\d])([A-Z])/g, \"$1_$2\");\n\n return toSnakeCase(normalised).toLowerCase();\n}\n\n// ============================================================================\n// PUBLIC — DEFAULT FOREIGN-KEY INFERENCE\n// ============================================================================\n\n/**\n * Default foreign-key column name for a `belongsTo` relation.\n *\n * The FK lives on THIS model (the owner of the `belongsTo` def) and is\n * named after the *role* the relation plays — not the target model class.\n * Matches Laravel/Rails convention: `Post.author_id` (not `Post.user_id`)\n * because the relation is named \"author\".\n *\n * @param relationName - The relation key as declared in `static relations`\n *\n * @example\n * inferBelongsToForeignKey(\"author\") // \"author_id\"\n * inferBelongsToForeignKey(\"organization\") // \"organization_id\"\n * inferBelongsToForeignKey(\"parentItem\") // \"parent_item_id\"\n */\nexport function inferBelongsToForeignKey(\n relationName: string,\n options?: RelationDefaults,\n): string {\n return `${snake(relationName)}${options?.foreignKeySuffix ?? DEFAULT_FK_SUFFIX}`;\n}\n\n/**\n * Default foreign-key column name for a `hasMany` or `hasOne` relation.\n *\n * The FK lives on the RELATED model's table and points back to THIS model,\n * so the column is named after the self model class.\n *\n * @param selfModelName - The owning model's class name (e.g. `User`)\n *\n * @example\n * inferHasForeignKey(\"User\") // \"user_id\" — used as posts.user_id\n * inferHasForeignKey(\"AIModel\") // \"ai_model_id\" — used as ai_trips.ai_model_id\n */\nexport function inferHasForeignKey(\n selfModelName: string,\n options?: RelationDefaults,\n): string {\n return `${snake(selfModelName)}${options?.foreignKeySuffix ?? DEFAULT_FK_SUFFIX}`;\n}\n\n/**\n * Default column name in a many-to-many pivot table for either side\n * (the side referenced determines the model name passed in).\n *\n * - For the self-side pivot column: pass the self model name.\n * - For the related-side pivot column: pass the related model name.\n *\n * @param modelName - The model class name being referenced from the pivot\n *\n * @example\n * // For `Post.belongsToMany(\"Tag\", { pivot: \"post_tags\" })`:\n * inferPivotKey(\"Post\") // \"post_id\" — pivot column post_tags.post_id\n * inferPivotKey(\"Tag\") // \"tag_id\" — pivot column post_tags.tag_id\n */\nexport function inferPivotKey(modelName: string, options?: RelationDefaults): string {\n return `${snake(modelName)}${options?.foreignKeySuffix ?? DEFAULT_FK_SUFFIX}`;\n}\n\n/**\n * Default pivot-table name for a many-to-many relation when none is\n * explicitly configured. Joins the two model snake-names alphabetically\n * with an underscore — matches Laravel/Rails convention.\n *\n * Alphabetical ordering means the same pivot name resolves regardless of\n * which side of the relation declares the `@BelongsToMany`, so\n * `Post.belongsToMany(\"Tag\")` and `Tag.belongsToMany(\"Post\")` both pick\n * the table `post_tag`.\n *\n * @example\n * inferPivotTable(\"Post\", \"Tag\") // \"post_tag\"\n * inferPivotTable(\"User\", \"Group\") // \"group_user\"\n * inferPivotTable(\"AIModel\", \"Tenant\") // \"ai_model_tenant\"\n */\nexport function inferPivotTable(\n selfModelName: string,\n relatedModelName: string,\n options?: RelationDefaults,\n): string {\n const selfSnake = snake(selfModelName);\n const relatedSnake = snake(relatedModelName);\n const order = options?.pivotTableNamingOrder ?? DEFAULT_PIVOT_ORDER;\n\n if (order === \"owner_first\") {\n return `${selfSnake}_${relatedSnake}`;\n }\n\n return [selfSnake, relatedSnake].sort().join(\"_\");\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,MAAM,oBAAoB;AAC1B,MAAM,sBAA8E;;;;;;;;;;;;;;;;;;;;;;AA2BpF,SAAS,MAAM,OAAuB;CAOpC,OAAO,YANY,MAEhB,QAAQ,yBAAyB,OAAO,CAAC,CAEzC,QAAQ,qBAAqB,OAEJ,CAAC,CAAC,CAAC,YAAY;AAC7C;;;;;;;;;;;;;;;;AAqBA,SAAgB,yBACd,cACA,SACQ;CACR,OAAO,GAAG,MAAM,YAAY,IAAI,SAAS,oBAAoB;AAC/D;;;;;;;;;;;;;AAcA,SAAgB,mBACd,eACA,SACQ;CACR,OAAO,GAAG,MAAM,aAAa,IAAI,SAAS,oBAAoB;AAChE;;;;;;;;;;;;;;;AAgBA,SAAgB,cAAc,WAAmB,SAAoC;CACnF,OAAO,GAAG,MAAM,SAAS,IAAI,SAAS,oBAAoB;AAC5D;;;;;;;;;;;;;;;;AAiBA,SAAgB,gBACd,eACA,kBACA,SACQ;CACR,MAAM,YAAY,MAAM,aAAa;CACrC,MAAM,eAAe,MAAM,gBAAgB;CAG3C,KAFc,SAAS,yBAAyB,yBAElC,eACZ,OAAO,GAAG,UAAU,GAAG;CAGzB,OAAO,CAAC,WAAW,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,GAAG;AAClD"}
@@ -2,7 +2,7 @@ import { ChildModel } from "../model/model.types.mjs";
2
2
  import { PivotData, PivotIds, RelationDefinition } from "./types.mjs";
3
3
  import { Model } from "../model/model.mjs";
4
4
 
5
- //#region ../@warlock.js/cascade/src/relations/pivot-operations.d.ts
5
+ //#region ../cascade/src/relations/pivot-operations.d.ts
6
6
  /**
7
7
  * Manages pivot table operations for many-to-many relationships.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"pivot-operations.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/pivot-operations.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4Ca,eAAA;EAyCT;;;EAAA,iBAjCe,KAAA;EAkFG;;;EAAA,iBA7EH,YAAA;EAoHJ;;;EAAA,iBA/GI,UAAA;EA8IJ;;;EAAA,iBAzII,UAAA;EAyIgB;;;;;;;;cA1H/B,KAAA,EAAO,KAAA,EACP,YAAA,UACA,UAAA,EAAY,kBAAA,EACZ,UAAA,EAAY,UAAA,CAAW,KAAA;EAmOX;;AAAmB;AAmCnC;EAnCgB,YAhNF,gBAAA;;;;;;;;AAmP4E;;;;;;;;;;;EArN3E,MAAA,CAAO,GAAA,EAAK,QAAA,EAAU,SAAA,GAAY,SAAA,GAAY,OAAA;;;;;;;;;;;;;;;;;;EAuC9C,MAAA,CAAO,GAAA,GAAM,QAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EA+BxB,IAAA,CAAK,GAAA,EAAK,QAAA,EAAU,SAAA,GAAY,SAAA,GAAY,OAAA;;;;;;;;;;;;;;;EAuC5C,MAAA,CAAO,GAAA,EAAK,QAAA,EAAU,SAAA,GAAY,SAAA,GAAY,OAAA;;;;;;UAkCnD,cAAA;;;;;;UAmCM,mBAAA;AAAA;;;;;;;;;iBAmCA,qBAAA,CAAsB,KAAA,EAAO,KAAA,EAAO,YAAA,WAAuB,eAAe"}
1
+ {"version":3,"file":"pivot-operations.d.mts","names":[],"sources":["../../../../../../../cascade/src/relations/pivot-operations.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4Ca,eAAA;EAyCT;;;EAAA,iBAjCe,KAAA;EAkFG;;;EAAA,iBA7EH,YAAA;EAoHJ;;;EAAA,iBA/GI,UAAA;EA8IJ;;;EAAA,iBAzII,UAAA;EAyIgB;;;;;;;;cA1H/B,KAAA,EAAO,KAAA,EACP,YAAA,UACA,UAAA,EAAY,kBAAA,EACZ,UAAA,EAAY,UAAA,CAAW,KAAA;EAmOX;;AAAmB;AAmCnC;EAnCgB,YAhNF,gBAAA;;;;;;;;AAmP4E;;;;;;;;;;;EArN3E,MAAA,CAAO,GAAA,EAAK,QAAA,EAAU,SAAA,GAAY,SAAA,GAAY,OAAA;;;;;;;;;;;;;;;;;;EAuC9C,MAAA,CAAO,GAAA,GAAM,QAAA,GAAW,OAAA;;;;;;;;;;;;;;;;EA+BxB,IAAA,CAAK,GAAA,EAAK,QAAA,EAAU,SAAA,GAAY,SAAA,GAAY,OAAA;;;;;;;;;;;;;;;EAuC5C,MAAA,CAAO,GAAA,EAAK,QAAA,EAAU,SAAA,GAAY,SAAA,GAAY,OAAA;;;;;;UAkCnD,cAAA;;;;;;UAmCM,mBAAA;AAAA;;;;;;;;;iBAmCA,qBAAA,CAAsB,KAAA,EAAO,KAAA,EAAO,YAAA,WAAuB,eAAe"}
@@ -1,7 +1,7 @@
1
1
  import { resolveModelClass, resolveModelName } from "../model/register-model.mjs";
2
2
  import { inferPivotKey, inferPivotTable } from "./key-conventions.mjs";
3
3
 
4
- //#region ../@warlock.js/cascade/src/relations/pivot-operations.ts
4
+ //#region ../cascade/src/relations/pivot-operations.ts
5
5
  /**
6
6
  * Manages pivot table operations for many-to-many relationships.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"pivot-operations.mjs","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/pivot-operations.ts"],"sourcesContent":["/**\n * @fileoverview Pivot table operations for many-to-many relationships.\n *\n * This module provides methods for managing the pivot table in\n * belongsToMany relationships: attach, detach, sync, and toggle.\n *\n * @module @warlock.js/cascade/relations/pivot-operations\n */\n\nimport type { ChildModel, Model } from \"../model/model\";\nimport { resolveModelClass, resolveModelName } from \"../model/register-model\";\nimport { inferPivotKey, inferPivotTable } from \"./key-conventions\";\nimport type { PivotData, PivotIds, RelationDefinition } from \"./types\";\n\n// ============================================================================\n// PIVOT OPERATIONS CLASS\n// ============================================================================\n\n/**\n * Manages pivot table operations for many-to-many relationships.\n *\n * Provides attach, detach, sync, and toggle operations for managing\n * the connections between two models through a pivot table.\n *\n * @example\n * ```typescript\n * const pivotOps = new PivotOperations(post, \"tags\", tagsDefinition);\n *\n * // Attach tags\n * await pivotOps.attach([1, 2, 3]);\n *\n * // Attach with pivot data\n * await pivotOps.attach([4], { addedBy: userId });\n *\n * // Detach specific tags\n * await pivotOps.detach([2]);\n *\n * // Sync (replace all)\n * await pivotOps.sync([1, 5, 6]);\n *\n * // Toggle (attach if missing, detach if present)\n * await pivotOps.toggle([1, 7]);\n * ```\n */\nexport class PivotOperations {\n // ==========================================================================\n // PRIVATE PROPERTIES\n // ==========================================================================\n\n /**\n * The model instance performing the pivot operation.\n */\n private readonly model: Model;\n\n /**\n * The name of the relation.\n */\n private readonly relationName: string;\n\n /**\n * The relation definition with pivot table configuration.\n */\n private readonly definition: RelationDefinition;\n\n /**\n * The model class of the source model.\n */\n private readonly modelClass: ChildModel<Model>;\n\n // ==========================================================================\n // CONSTRUCTOR\n // ==========================================================================\n\n /**\n * Creates a new PivotOperations instance.\n *\n * @param model - The model instance performing the operation\n * @param relationName - The name of the belongsToMany relation\n * @param definition - The relation definition\n * @param modelClass - The model class constructor\n */\n public constructor(\n model: Model,\n relationName: string,\n definition: RelationDefinition,\n modelClass: ChildModel<Model>,\n ) {\n if (definition.type !== \"belongsToMany\") {\n throw new Error(\n `Pivot operations are only available for belongsToMany relations. ` +\n `Relation \"${relationName}\" is of type \"${definition.type}\".`,\n );\n }\n\n this.model = model;\n this.relationName = relationName;\n this.definition = definition;\n this.modelClass = modelClass;\n }\n\n /**\n * Read the configured relation conventions from this pivot's owning\n * data source. Returns `undefined` when no overrides are set.\n */\n private get relationDefaults() {\n try {\n return this.modelClass.getDataSource()?.relationDefaults;\n } catch {\n return undefined;\n }\n }\n\n // ==========================================================================\n // PUBLIC METHODS\n // ==========================================================================\n\n /**\n * Attaches one or more related models via the pivot table.\n *\n * Creates new records in the pivot table linking this model to the\n * specified related model IDs. Existing attachments are not duplicated.\n *\n * @param ids - The IDs of the related models to attach\n * @param pivotData - Optional additional data to store in the pivot record\n *\n * @example\n * ```typescript\n * // Attach tags to a post\n * await post.attach(\"tags\", [1, 2, 3]);\n *\n * // Attach with additional pivot data\n * await post.attach(\"tags\", [4], { addedBy: currentUserId });\n * ```\n */\n public async attach(ids: PivotIds, pivotData?: PivotData): Promise<void> {\n if (ids.length === 0) return;\n\n const { pivotTable, localKeyValue, pivotLocalKey, pivotForeignKey } = this.getPivotConfig();\n\n // Get existing attachments to avoid duplicates\n const existingIds = await this.getExistingPivotIds();\n const newIds = ids.filter((id) => !existingIds.has(id));\n\n if (newIds.length === 0) return;\n\n // Create pivot records\n const records = newIds.map((id) => ({\n [pivotLocalKey]: localKeyValue,\n [pivotForeignKey]: id,\n ...pivotData,\n }));\n\n const dataSource = this.modelClass.getDataSource();\n await dataSource.driver.insertMany(pivotTable, records);\n }\n\n /**\n * Detaches one or more related models from the pivot table.\n *\n * Removes records from the pivot table. If no IDs are specified,\n * all attachments for this model are removed.\n *\n * @param ids - Optional IDs to detach. If omitted, detaches all.\n *\n * @example\n * ```typescript\n * // Detach specific tags\n * await post.detach(\"tags\", [2, 3]);\n *\n * // Detach all tags\n * await post.detach(\"tags\");\n * ```\n */\n public async detach(ids?: PivotIds): Promise<void> {\n const { pivotTable, localKeyValue, pivotLocalKey, pivotForeignKey } = this.getPivotConfig();\n\n const dataSource = this.modelClass.getDataSource();\n\n // Build filter based on local key and optionally foreign keys\n const filter: Record<string, unknown> = { [pivotLocalKey]: localKeyValue };\n\n if (ids && ids.length > 0) {\n // Use $in operator style for filtering by multiple IDs\n filter[pivotForeignKey] = { $in: ids };\n }\n\n await dataSource.driver.deleteMany(pivotTable, filter);\n }\n\n /**\n * Synchronizes the pivot table to match the specified IDs.\n *\n * Attaches any new IDs and detaches any IDs not in the list.\n * After sync, the pivot table will contain exactly the specified IDs.\n *\n * @param ids - The IDs that should be attached after sync\n * @param pivotData - Optional data for newly attached records\n *\n * @example\n * ```typescript\n * // Set tags to exactly [1, 3, 5], removing any others\n * await post.sync(\"tags\", [1, 3, 5]);\n * ```\n */\n public async sync(ids: PivotIds, pivotData?: PivotData): Promise<void> {\n const existingIds = await this.getExistingPivotIds();\n const newIdSet = new Set(ids);\n\n // Find IDs to detach (in existing but not in new)\n const toDetach: PivotIds = [];\n for (const existingId of existingIds) {\n if (!newIdSet.has(existingId)) {\n toDetach.push(existingId);\n }\n }\n\n // Find IDs to attach (in new but not in existing)\n const toAttach = ids.filter((id) => !existingIds.has(id));\n\n // Perform operations\n if (toDetach.length > 0) {\n await this.detach(toDetach);\n }\n\n if (toAttach.length > 0) {\n await this.attach(toAttach, pivotData);\n }\n }\n\n /**\n * Toggles the attachment status of the specified IDs.\n *\n * For each ID: if attached, detaches it; if not attached, attaches it.\n *\n * @param ids - The IDs to toggle\n * @param pivotData - Optional data for newly attached records\n *\n * @example\n * ```typescript\n * // Toggle tags - attached become detached, detached become attached\n * await post.toggle(\"tags\", [1, 4]);\n * ```\n */\n public async toggle(ids: PivotIds, pivotData?: PivotData): Promise<void> {\n if (ids.length === 0) return;\n\n const existingIds = await this.getExistingPivotIds();\n\n const toAttach: PivotIds = [];\n const toDetach: PivotIds = [];\n\n for (const id of ids) {\n if (existingIds.has(id)) {\n toDetach.push(id);\n } else {\n toAttach.push(id);\n }\n }\n\n if (toDetach.length > 0) {\n await this.detach(toDetach);\n }\n\n if (toAttach.length > 0) {\n await this.attach(toAttach, pivotData);\n }\n }\n\n // ==========================================================================\n // PRIVATE METHODS\n // ==========================================================================\n\n /**\n * Gets the pivot table configuration.\n *\n * @returns The pivot configuration object\n */\n private getPivotConfig(): {\n pivotTable: string;\n localKeyValue: unknown;\n pivotLocalKey: string;\n pivotForeignKey: string;\n relatedKey: string;\n } {\n const conventions = this.relationDefaults;\n const relatedModelName = resolveModelName(this.definition.model);\n const pivotTable =\n this.definition.pivot ??\n inferPivotTable(this.modelClass.name, relatedModelName, conventions);\n const RelatedModel = resolveModelClass(this.definition.model);\n const localKey = this.definition.pivotLocalKey ?? this.modelClass.primaryKey ?? \"id\";\n const pivotLocalKey =\n this.definition.localKey ?? inferPivotKey(this.modelClass.name, conventions);\n const pivotForeignKey =\n this.definition.foreignKey ?? inferPivotKey(relatedModelName, conventions);\n const relatedKey = this.definition.pivotForeignKey ?? RelatedModel?.primaryKey ?? \"id\";\n const localKeyValue = this.model.get(localKey);\n\n return {\n pivotTable,\n localKeyValue,\n pivotLocalKey,\n pivotForeignKey,\n relatedKey,\n };\n }\n\n /**\n * Gets all currently attached IDs from the pivot table.\n *\n * @returns A set of attached foreign key values\n */\n private async getExistingPivotIds(): Promise<Set<number | string>> {\n const { pivotTable, localKeyValue, pivotLocalKey, pivotForeignKey } = this.getPivotConfig();\n\n const dataSource = this.modelClass.getDataSource();\n const records = await dataSource.driver\n .queryBuilder(pivotTable)\n .select([pivotForeignKey])\n .where(pivotLocalKey, localKeyValue)\n .get();\n\n const ids = new Set<number | string>();\n for (const record of records as Record<string, unknown>[]) {\n const id = record[pivotForeignKey];\n if (id !== undefined && id !== null) {\n ids.add(id as number | string);\n }\n }\n\n return ids;\n }\n\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates a PivotOperations instance for a model and relation.\n *\n * @param model - The model instance\n * @param relationName - The name of the belongsToMany relation\n * @returns A PivotOperations instance\n * @throws Error if the relation is not a belongsToMany or not defined\n */\nexport function createPivotOperations(model: Model, relationName: string): PivotOperations {\n const ModelClass = model.constructor as ChildModel<Model>;\n const relations = (ModelClass as unknown as { relations?: Record<string, RelationDefinition> })\n .relations;\n\n if (!relations || !relations[relationName]) {\n throw new Error(`Relation \"${relationName}\" is not defined on model \"${ModelClass.name}\".`);\n }\n\n return new PivotOperations(model, relationName, relations[relationName], ModelClass);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4CA,IAAa,kBAAb,MAA6B;;;;CAQ3B,AAAiB;;;;CAKjB,AAAiB;;;;CAKjB,AAAiB;;;;CAKjB,AAAiB;;;;;;;;;CAcjB,AAAO,YACL,OACA,cACA,YACA,YACA;EACA,IAAI,WAAW,SAAS,iBACtB,MAAM,IAAI,MACR,8EACe,aAAa,gBAAgB,WAAW,KAAK,GAC9D;EAGF,KAAK,QAAQ;EACb,KAAK,eAAe;EACpB,KAAK,aAAa;EAClB,KAAK,aAAa;CACpB;;;;;CAMA,IAAY,mBAAmB;EAC7B,IAAI;GACF,OAAO,KAAK,WAAW,cAAc,CAAC,EAAE;EAC1C,QAAQ;GACN;EACF;CACF;;;;;;;;;;;;;;;;;;;CAwBA,MAAa,OAAO,KAAe,WAAsC;EACvE,IAAI,IAAI,WAAW,GAAG;EAEtB,MAAM,EAAE,YAAY,eAAe,eAAe,oBAAoB,KAAK,eAAe;EAG1F,MAAM,cAAc,MAAM,KAAK,oBAAoB;EACnD,MAAM,SAAS,IAAI,QAAQ,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;EAEtD,IAAI,OAAO,WAAW,GAAG;EAGzB,MAAM,UAAU,OAAO,KAAK,QAAQ;IACjC,gBAAgB;IAChB,kBAAkB;GACnB,GAAG;EACL,EAAE;EAGF,MADmB,KAAK,WAAW,cACpB,CAAC,CAAC,OAAO,WAAW,YAAY,OAAO;CACxD;;;;;;;;;;;;;;;;;;CAmBA,MAAa,OAAO,KAA+B;EACjD,MAAM,EAAE,YAAY,eAAe,eAAe,oBAAoB,KAAK,eAAe;EAE1F,MAAM,aAAa,KAAK,WAAW,cAAc;EAGjD,MAAM,SAAkC,GAAG,gBAAgB,cAAc;EAEzE,IAAI,OAAO,IAAI,SAAS,GAEtB,OAAO,mBAAmB,EAAE,KAAK,IAAI;EAGvC,MAAM,WAAW,OAAO,WAAW,YAAY,MAAM;CACvD;;;;;;;;;;;;;;;;CAiBA,MAAa,KAAK,KAAe,WAAsC;EACrE,MAAM,cAAc,MAAM,KAAK,oBAAoB;EACnD,MAAM,WAAW,IAAI,IAAI,GAAG;EAG5B,MAAM,WAAqB,CAAC;EAC5B,KAAK,MAAM,cAAc,aACvB,IAAI,CAAC,SAAS,IAAI,UAAU,GAC1B,SAAS,KAAK,UAAU;EAK5B,MAAM,WAAW,IAAI,QAAQ,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;EAGxD,IAAI,SAAS,SAAS,GACpB,MAAM,KAAK,OAAO,QAAQ;EAG5B,IAAI,SAAS,SAAS,GACpB,MAAM,KAAK,OAAO,UAAU,SAAS;CAEzC;;;;;;;;;;;;;;;CAgBA,MAAa,OAAO,KAAe,WAAsC;EACvE,IAAI,IAAI,WAAW,GAAG;EAEtB,MAAM,cAAc,MAAM,KAAK,oBAAoB;EAEnD,MAAM,WAAqB,CAAC;EAC5B,MAAM,WAAqB,CAAC;EAE5B,KAAK,MAAM,MAAM,KACf,IAAI,YAAY,IAAI,EAAE,GACpB,SAAS,KAAK,EAAE;OAEhB,SAAS,KAAK,EAAE;EAIpB,IAAI,SAAS,SAAS,GACpB,MAAM,KAAK,OAAO,QAAQ;EAG5B,IAAI,SAAS,SAAS,GACpB,MAAM,KAAK,OAAO,UAAU,SAAS;CAEzC;;;;;;CAWA,AAAQ,iBAMN;EACA,MAAM,cAAc,KAAK;EACzB,MAAM,mBAAmB,iBAAiB,KAAK,WAAW,KAAK;EAC/D,MAAM,aACJ,KAAK,WAAW,SAChB,gBAAgB,KAAK,WAAW,MAAM,kBAAkB,WAAW;EACrE,MAAM,eAAe,kBAAkB,KAAK,WAAW,KAAK;EAC5D,MAAM,WAAW,KAAK,WAAW,iBAAiB,KAAK,WAAW,cAAc;EAChF,MAAM,gBACJ,KAAK,WAAW,YAAY,cAAc,KAAK,WAAW,MAAM,WAAW;EAC7E,MAAM,kBACJ,KAAK,WAAW,cAAc,cAAc,kBAAkB,WAAW;EAC3E,MAAM,aAAa,KAAK,WAAW,mBAAmB,cAAc,cAAc;EAGlF,OAAO;GACL;GACA,eAJoB,KAAK,MAAM,IAAI,QAIvB;GACZ;GACA;GACA;EACF;CACF;;;;;;CAOA,MAAc,sBAAqD;EACjE,MAAM,EAAE,YAAY,eAAe,eAAe,oBAAoB,KAAK,eAAe;EAG1F,MAAM,UAAU,MADG,KAAK,WAAW,cACJ,CAAC,CAAC,OAC9B,aAAa,UAAU,CAAC,CACxB,OAAO,CAAC,eAAe,CAAC,CAAC,CACzB,MAAM,eAAe,aAAa,CAAC,CACnC,IAAI;EAEP,MAAM,sBAAM,IAAI,IAAqB;EACrC,KAAK,MAAM,UAAU,SAAsC;GACzD,MAAM,KAAK,OAAO;GAClB,IAAI,OAAO,UAAa,OAAO,MAC7B,IAAI,IAAI,EAAqB;EAEjC;EAEA,OAAO;CACT;AAEF;;;;;;;;;AAcA,SAAgB,sBAAsB,OAAc,cAAuC;CACzF,MAAM,aAAa,MAAM;CACzB,MAAM,YAAa,WAChB;CAEH,IAAI,CAAC,aAAa,CAAC,UAAU,eAC3B,MAAM,IAAI,MAAM,aAAa,aAAa,6BAA6B,WAAW,KAAK,GAAG;CAG5F,OAAO,IAAI,gBAAgB,OAAO,cAAc,UAAU,eAAe,UAAU;AACrF"}
1
+ {"version":3,"file":"pivot-operations.mjs","names":[],"sources":["../../../../../../../cascade/src/relations/pivot-operations.ts"],"sourcesContent":["/**\n * @fileoverview Pivot table operations for many-to-many relationships.\n *\n * This module provides methods for managing the pivot table in\n * belongsToMany relationships: attach, detach, sync, and toggle.\n *\n * @module @warlock.js/cascade/relations/pivot-operations\n */\n\nimport type { ChildModel, Model } from \"../model/model\";\nimport { resolveModelClass, resolveModelName } from \"../model/register-model\";\nimport { inferPivotKey, inferPivotTable } from \"./key-conventions\";\nimport type { PivotData, PivotIds, RelationDefinition } from \"./types\";\n\n// ============================================================================\n// PIVOT OPERATIONS CLASS\n// ============================================================================\n\n/**\n * Manages pivot table operations for many-to-many relationships.\n *\n * Provides attach, detach, sync, and toggle operations for managing\n * the connections between two models through a pivot table.\n *\n * @example\n * ```typescript\n * const pivotOps = new PivotOperations(post, \"tags\", tagsDefinition);\n *\n * // Attach tags\n * await pivotOps.attach([1, 2, 3]);\n *\n * // Attach with pivot data\n * await pivotOps.attach([4], { addedBy: userId });\n *\n * // Detach specific tags\n * await pivotOps.detach([2]);\n *\n * // Sync (replace all)\n * await pivotOps.sync([1, 5, 6]);\n *\n * // Toggle (attach if missing, detach if present)\n * await pivotOps.toggle([1, 7]);\n * ```\n */\nexport class PivotOperations {\n // ==========================================================================\n // PRIVATE PROPERTIES\n // ==========================================================================\n\n /**\n * The model instance performing the pivot operation.\n */\n private readonly model: Model;\n\n /**\n * The name of the relation.\n */\n private readonly relationName: string;\n\n /**\n * The relation definition with pivot table configuration.\n */\n private readonly definition: RelationDefinition;\n\n /**\n * The model class of the source model.\n */\n private readonly modelClass: ChildModel<Model>;\n\n // ==========================================================================\n // CONSTRUCTOR\n // ==========================================================================\n\n /**\n * Creates a new PivotOperations instance.\n *\n * @param model - The model instance performing the operation\n * @param relationName - The name of the belongsToMany relation\n * @param definition - The relation definition\n * @param modelClass - The model class constructor\n */\n public constructor(\n model: Model,\n relationName: string,\n definition: RelationDefinition,\n modelClass: ChildModel<Model>,\n ) {\n if (definition.type !== \"belongsToMany\") {\n throw new Error(\n `Pivot operations are only available for belongsToMany relations. ` +\n `Relation \"${relationName}\" is of type \"${definition.type}\".`,\n );\n }\n\n this.model = model;\n this.relationName = relationName;\n this.definition = definition;\n this.modelClass = modelClass;\n }\n\n /**\n * Read the configured relation conventions from this pivot's owning\n * data source. Returns `undefined` when no overrides are set.\n */\n private get relationDefaults() {\n try {\n return this.modelClass.getDataSource()?.relationDefaults;\n } catch {\n return undefined;\n }\n }\n\n // ==========================================================================\n // PUBLIC METHODS\n // ==========================================================================\n\n /**\n * Attaches one or more related models via the pivot table.\n *\n * Creates new records in the pivot table linking this model to the\n * specified related model IDs. Existing attachments are not duplicated.\n *\n * @param ids - The IDs of the related models to attach\n * @param pivotData - Optional additional data to store in the pivot record\n *\n * @example\n * ```typescript\n * // Attach tags to a post\n * await post.attach(\"tags\", [1, 2, 3]);\n *\n * // Attach with additional pivot data\n * await post.attach(\"tags\", [4], { addedBy: currentUserId });\n * ```\n */\n public async attach(ids: PivotIds, pivotData?: PivotData): Promise<void> {\n if (ids.length === 0) return;\n\n const { pivotTable, localKeyValue, pivotLocalKey, pivotForeignKey } = this.getPivotConfig();\n\n // Get existing attachments to avoid duplicates\n const existingIds = await this.getExistingPivotIds();\n const newIds = ids.filter((id) => !existingIds.has(id));\n\n if (newIds.length === 0) return;\n\n // Create pivot records\n const records = newIds.map((id) => ({\n [pivotLocalKey]: localKeyValue,\n [pivotForeignKey]: id,\n ...pivotData,\n }));\n\n const dataSource = this.modelClass.getDataSource();\n await dataSource.driver.insertMany(pivotTable, records);\n }\n\n /**\n * Detaches one or more related models from the pivot table.\n *\n * Removes records from the pivot table. If no IDs are specified,\n * all attachments for this model are removed.\n *\n * @param ids - Optional IDs to detach. If omitted, detaches all.\n *\n * @example\n * ```typescript\n * // Detach specific tags\n * await post.detach(\"tags\", [2, 3]);\n *\n * // Detach all tags\n * await post.detach(\"tags\");\n * ```\n */\n public async detach(ids?: PivotIds): Promise<void> {\n const { pivotTable, localKeyValue, pivotLocalKey, pivotForeignKey } = this.getPivotConfig();\n\n const dataSource = this.modelClass.getDataSource();\n\n // Build filter based on local key and optionally foreign keys\n const filter: Record<string, unknown> = { [pivotLocalKey]: localKeyValue };\n\n if (ids && ids.length > 0) {\n // Use $in operator style for filtering by multiple IDs\n filter[pivotForeignKey] = { $in: ids };\n }\n\n await dataSource.driver.deleteMany(pivotTable, filter);\n }\n\n /**\n * Synchronizes the pivot table to match the specified IDs.\n *\n * Attaches any new IDs and detaches any IDs not in the list.\n * After sync, the pivot table will contain exactly the specified IDs.\n *\n * @param ids - The IDs that should be attached after sync\n * @param pivotData - Optional data for newly attached records\n *\n * @example\n * ```typescript\n * // Set tags to exactly [1, 3, 5], removing any others\n * await post.sync(\"tags\", [1, 3, 5]);\n * ```\n */\n public async sync(ids: PivotIds, pivotData?: PivotData): Promise<void> {\n const existingIds = await this.getExistingPivotIds();\n const newIdSet = new Set(ids);\n\n // Find IDs to detach (in existing but not in new)\n const toDetach: PivotIds = [];\n for (const existingId of existingIds) {\n if (!newIdSet.has(existingId)) {\n toDetach.push(existingId);\n }\n }\n\n // Find IDs to attach (in new but not in existing)\n const toAttach = ids.filter((id) => !existingIds.has(id));\n\n // Perform operations\n if (toDetach.length > 0) {\n await this.detach(toDetach);\n }\n\n if (toAttach.length > 0) {\n await this.attach(toAttach, pivotData);\n }\n }\n\n /**\n * Toggles the attachment status of the specified IDs.\n *\n * For each ID: if attached, detaches it; if not attached, attaches it.\n *\n * @param ids - The IDs to toggle\n * @param pivotData - Optional data for newly attached records\n *\n * @example\n * ```typescript\n * // Toggle tags - attached become detached, detached become attached\n * await post.toggle(\"tags\", [1, 4]);\n * ```\n */\n public async toggle(ids: PivotIds, pivotData?: PivotData): Promise<void> {\n if (ids.length === 0) return;\n\n const existingIds = await this.getExistingPivotIds();\n\n const toAttach: PivotIds = [];\n const toDetach: PivotIds = [];\n\n for (const id of ids) {\n if (existingIds.has(id)) {\n toDetach.push(id);\n } else {\n toAttach.push(id);\n }\n }\n\n if (toDetach.length > 0) {\n await this.detach(toDetach);\n }\n\n if (toAttach.length > 0) {\n await this.attach(toAttach, pivotData);\n }\n }\n\n // ==========================================================================\n // PRIVATE METHODS\n // ==========================================================================\n\n /**\n * Gets the pivot table configuration.\n *\n * @returns The pivot configuration object\n */\n private getPivotConfig(): {\n pivotTable: string;\n localKeyValue: unknown;\n pivotLocalKey: string;\n pivotForeignKey: string;\n relatedKey: string;\n } {\n const conventions = this.relationDefaults;\n const relatedModelName = resolveModelName(this.definition.model);\n const pivotTable =\n this.definition.pivot ??\n inferPivotTable(this.modelClass.name, relatedModelName, conventions);\n const RelatedModel = resolveModelClass(this.definition.model);\n const localKey = this.definition.pivotLocalKey ?? this.modelClass.primaryKey ?? \"id\";\n const pivotLocalKey =\n this.definition.localKey ?? inferPivotKey(this.modelClass.name, conventions);\n const pivotForeignKey =\n this.definition.foreignKey ?? inferPivotKey(relatedModelName, conventions);\n const relatedKey = this.definition.pivotForeignKey ?? RelatedModel?.primaryKey ?? \"id\";\n const localKeyValue = this.model.get(localKey);\n\n return {\n pivotTable,\n localKeyValue,\n pivotLocalKey,\n pivotForeignKey,\n relatedKey,\n };\n }\n\n /**\n * Gets all currently attached IDs from the pivot table.\n *\n * @returns A set of attached foreign key values\n */\n private async getExistingPivotIds(): Promise<Set<number | string>> {\n const { pivotTable, localKeyValue, pivotLocalKey, pivotForeignKey } = this.getPivotConfig();\n\n const dataSource = this.modelClass.getDataSource();\n const records = await dataSource.driver\n .queryBuilder(pivotTable)\n .select([pivotForeignKey])\n .where(pivotLocalKey, localKeyValue)\n .get();\n\n const ids = new Set<number | string>();\n for (const record of records as Record<string, unknown>[]) {\n const id = record[pivotForeignKey];\n if (id !== undefined && id !== null) {\n ids.add(id as number | string);\n }\n }\n\n return ids;\n }\n\n}\n\n// ============================================================================\n// FACTORY FUNCTION\n// ============================================================================\n\n/**\n * Creates a PivotOperations instance for a model and relation.\n *\n * @param model - The model instance\n * @param relationName - The name of the belongsToMany relation\n * @returns A PivotOperations instance\n * @throws Error if the relation is not a belongsToMany or not defined\n */\nexport function createPivotOperations(model: Model, relationName: string): PivotOperations {\n const ModelClass = model.constructor as ChildModel<Model>;\n const relations = (ModelClass as unknown as { relations?: Record<string, RelationDefinition> })\n .relations;\n\n if (!relations || !relations[relationName]) {\n throw new Error(`Relation \"${relationName}\" is not defined on model \"${ModelClass.name}\".`);\n }\n\n return new PivotOperations(model, relationName, relations[relationName], ModelClass);\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { RelationDefinition } from "./types.mjs";
2
2
  import { Model } from "../model/model.mjs";
3
3
 
4
- //#region ../@warlock.js/cascade/src/relations/relation-hydrator.d.ts
4
+ //#region ../cascade/src/relations/relation-hydrator.d.ts
5
5
  /**
6
6
  * The serialized shape of a single relation entry inside a model snapshot.
7
7
  * Each value is either:
@@ -1 +1 @@
1
- {"version":3,"file":"relation-hydrator.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/relation-hydrator.ts"],"mappings":";;;;;;;;AA0C8C;AAoB9C;;KA7BY,kBAAA,UAA4B,aAAA,GAAgB,aAAa;;;;;;KAOzD,aAAA;EACV,IAAA,EAAM,MAAA;EACN,SAAA,EAAW,MAAA,SAAe,kBAAA;AAAA;;;;;;;;;AAuCyC;;;;;cAnBxD,gBAAA;;;;;;;;;;;;;;;;SAgBG,OAAA,CACZ,KAAA,EAAO,KAAA,EACP,YAAA,EAAc,MAAA,SAAe,kBAAA,GAC7B,iBAAA,EAAmB,MAAA,SAAe,kBAAA;AAAA"}
1
+ {"version":3,"file":"relation-hydrator.d.mts","names":[],"sources":["../../../../../../../cascade/src/relations/relation-hydrator.ts"],"mappings":";;;;;;;;AA0C8C;AAoB9C;;KA7BY,kBAAA,UAA4B,aAAA,GAAgB,aAAa;;;;;;KAOzD,aAAA;EACV,IAAA,EAAM,MAAA;EACN,SAAA,EAAW,MAAA,SAAe,kBAAA;AAAA;;;;;;;;;AAuCyC;;;;;cAnBxD,gBAAA;;;;;;;;;;;;;;;;SAgBG,OAAA,CACZ,KAAA,EAAO,KAAA,EACP,YAAA,EAAc,MAAA,SAAe,kBAAA,GAC7B,iBAAA,EAAmB,MAAA,SAAe,kBAAA;AAAA"}
@@ -1,6 +1,6 @@
1
1
  import { resolveModelClass } from "../model/register-model.mjs";
2
2
 
3
- //#region ../@warlock.js/cascade/src/relations/relation-hydrator.ts
3
+ //#region ../cascade/src/relations/relation-hydrator.ts
4
4
  /**
5
5
  * Restores eager-loaded relations from a plain snapshot onto a model instance.
6
6
  *
@@ -1 +1 @@
1
- {"version":3,"file":"relation-hydrator.mjs","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/relation-hydrator.ts"],"sourcesContent":["/**\n * @fileoverview Restores relation data onto model instances from a plain object snapshot.\n *\n * This is the cache-restoration counterpart to RelationLoader (which loads from the DB).\n * It is intentionally decoupled from query execution — it only deals with instantiating\n * related models from already-serialized data.\n *\n * @module @warlock.js/cascade/relations/relation-hydrator\n *\n * @example\n * ```typescript\n * // Restoring a model + its relations from a cache snapshot:\n * const snapshot = await cache.get(key);\n * const chat = Chat.fromSnapshot(snapshot);\n * // chat.unit, chat.organization etc. are fully hydrated Model instances.\n * ```\n */\n\nimport type { ChildModel, Model } from \"../model/model\";\nimport { resolveModelClass } from \"../model/register-model\";\nimport type { RelationDefinition } from \"./types\";\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\n/**\n * The serialized shape of a single relation entry inside a model snapshot.\n * Each value is either:\n * - `null` — the relation was loaded and resolved to nothing (e.g. belongsTo with no match)\n * - A nested snapshot object `{ data, relations }` — a single related model\n * - An array of nested snapshot objects — a collection relation\n */\nexport type SerializedRelation = null | ModelSnapshot | ModelSnapshot[];\n\n/**\n * The plain-object shape produced by `model.toSnapshot()` and consumed by\n * `Model.fromSnapshot()`. The `relations` map uses the same relation names\n * defined in the model's static `relations` property.\n */\nexport type ModelSnapshot = {\n data: Record<string, unknown>;\n relations: Record<string, SerializedRelation>;\n};\n\n// ============================================================================\n// RELATION HYDRATOR CLASS\n// ============================================================================\n\n/**\n * Restores eager-loaded relations from a plain snapshot onto a model instance.\n *\n * Mirrors the interface of RelationLoader but instead of issuing DB queries,\n * it recursively instantiates related models from serialized snapshot data.\n *\n * @example\n * ```typescript\n * // Used internally by Model.fromSnapshot():\n * const model = new Chat(snapshot.data);\n * RelationHydrator.hydrate(model, Chat.relations, snapshot.relations);\n * ```\n */\nexport class RelationHydrator {\n /**\n * Hydrate all relations from a snapshot onto `model`.\n *\n * - Looks up each relation name in `relationDefs` to find the target model class.\n * - Recursively calls `fromSnapshot` on nested snapshots so deeply nested\n * relations are fully hydrated as well.\n * - Sets each relation on both `model.loadedRelations` (Map) and as a direct\n * property (`model[name]`) to match the behaviour of RelationLoader.\n * - Intentionally preserves `null` entries — a null relation was explicitly\n * loaded and resolved to nothing; this is different from a missing relation.\n *\n * @param model - The model instance to attach relations to\n * @param relationDefs - The static `relations` map from the model class\n * @param relationsSnapshot - The `relations` portion of a `ModelSnapshot`\n */\n public static hydrate(\n model: Model,\n relationDefs: Record<string, RelationDefinition>,\n relationsSnapshot: Record<string, SerializedRelation> | undefined,\n ): void {\n if (!relationsSnapshot) return;\n\n for (const [name, snapshot] of Object.entries(relationsSnapshot)) {\n const def = relationDefs[name];\n\n // Relation name not in definition — skip gracefully rather than throwing,\n // since the snapshot could be from an older schema version.\n if (!def) continue;\n\n const RelModel = resolveModelClass(def.model) as ChildModel<Model> | undefined;\n if (!RelModel) continue;\n\n let hydrated: Model | Model[] | null;\n\n if (snapshot === null) {\n // Explicitly loaded relation that resolved to nothing — keep as null.\n hydrated = null;\n } else if (Array.isArray(snapshot)) {\n // Collection relation (hasMany / belongsToMany)\n hydrated = snapshot.map((item) => RelModel.fromSnapshot(item));\n } else {\n // Single relation (belongsTo / hasOne)\n hydrated = RelModel.fromSnapshot(snapshot);\n }\n\n // Set on the Map for getRelation() / isLoaded() API\n model.loadedRelations.set(name, hydrated);\n\n // Set as direct property for convenient access (model.unit, model.posts, …)\n (model as unknown as Record<string, unknown>)[name] = hydrated;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8DA,IAAa,mBAAb,MAA8B;;;;;;;;;;;;;;;;CAgB5B,OAAc,QACZ,OACA,cACA,mBACM;EACN,IAAI,CAAC,mBAAmB;EAExB,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,iBAAiB,GAAG;GAChE,MAAM,MAAM,aAAa;GAIzB,IAAI,CAAC,KAAK;GAEV,MAAM,WAAW,kBAAkB,IAAI,KAAK;GAC5C,IAAI,CAAC,UAAU;GAEf,IAAI;GAEJ,IAAI,aAAa,MAEf,WAAW;QACN,IAAI,MAAM,QAAQ,QAAQ,GAE/B,WAAW,SAAS,KAAK,SAAS,SAAS,aAAa,IAAI,CAAC;QAG7D,WAAW,SAAS,aAAa,QAAQ;GAI3C,MAAM,gBAAgB,IAAI,MAAM,QAAQ;GAGxC,AAAC,MAA6C,QAAQ;EACxD;CACF;AACF"}
1
+ {"version":3,"file":"relation-hydrator.mjs","names":[],"sources":["../../../../../../../cascade/src/relations/relation-hydrator.ts"],"sourcesContent":["/**\n * @fileoverview Restores relation data onto model instances from a plain object snapshot.\n *\n * This is the cache-restoration counterpart to RelationLoader (which loads from the DB).\n * It is intentionally decoupled from query execution — it only deals with instantiating\n * related models from already-serialized data.\n *\n * @module @warlock.js/cascade/relations/relation-hydrator\n *\n * @example\n * ```typescript\n * // Restoring a model + its relations from a cache snapshot:\n * const snapshot = await cache.get(key);\n * const chat = Chat.fromSnapshot(snapshot);\n * // chat.unit, chat.organization etc. are fully hydrated Model instances.\n * ```\n */\n\nimport type { ChildModel, Model } from \"../model/model\";\nimport { resolveModelClass } from \"../model/register-model\";\nimport type { RelationDefinition } from \"./types\";\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\n/**\n * The serialized shape of a single relation entry inside a model snapshot.\n * Each value is either:\n * - `null` — the relation was loaded and resolved to nothing (e.g. belongsTo with no match)\n * - A nested snapshot object `{ data, relations }` — a single related model\n * - An array of nested snapshot objects — a collection relation\n */\nexport type SerializedRelation = null | ModelSnapshot | ModelSnapshot[];\n\n/**\n * The plain-object shape produced by `model.toSnapshot()` and consumed by\n * `Model.fromSnapshot()`. The `relations` map uses the same relation names\n * defined in the model's static `relations` property.\n */\nexport type ModelSnapshot = {\n data: Record<string, unknown>;\n relations: Record<string, SerializedRelation>;\n};\n\n// ============================================================================\n// RELATION HYDRATOR CLASS\n// ============================================================================\n\n/**\n * Restores eager-loaded relations from a plain snapshot onto a model instance.\n *\n * Mirrors the interface of RelationLoader but instead of issuing DB queries,\n * it recursively instantiates related models from serialized snapshot data.\n *\n * @example\n * ```typescript\n * // Used internally by Model.fromSnapshot():\n * const model = new Chat(snapshot.data);\n * RelationHydrator.hydrate(model, Chat.relations, snapshot.relations);\n * ```\n */\nexport class RelationHydrator {\n /**\n * Hydrate all relations from a snapshot onto `model`.\n *\n * - Looks up each relation name in `relationDefs` to find the target model class.\n * - Recursively calls `fromSnapshot` on nested snapshots so deeply nested\n * relations are fully hydrated as well.\n * - Sets each relation on both `model.loadedRelations` (Map) and as a direct\n * property (`model[name]`) to match the behaviour of RelationLoader.\n * - Intentionally preserves `null` entries — a null relation was explicitly\n * loaded and resolved to nothing; this is different from a missing relation.\n *\n * @param model - The model instance to attach relations to\n * @param relationDefs - The static `relations` map from the model class\n * @param relationsSnapshot - The `relations` portion of a `ModelSnapshot`\n */\n public static hydrate(\n model: Model,\n relationDefs: Record<string, RelationDefinition>,\n relationsSnapshot: Record<string, SerializedRelation> | undefined,\n ): void {\n if (!relationsSnapshot) return;\n\n for (const [name, snapshot] of Object.entries(relationsSnapshot)) {\n const def = relationDefs[name];\n\n // Relation name not in definition — skip gracefully rather than throwing,\n // since the snapshot could be from an older schema version.\n if (!def) continue;\n\n const RelModel = resolveModelClass(def.model) as ChildModel<Model> | undefined;\n if (!RelModel) continue;\n\n let hydrated: Model | Model[] | null;\n\n if (snapshot === null) {\n // Explicitly loaded relation that resolved to nothing — keep as null.\n hydrated = null;\n } else if (Array.isArray(snapshot)) {\n // Collection relation (hasMany / belongsToMany)\n hydrated = snapshot.map((item) => RelModel.fromSnapshot(item));\n } else {\n // Single relation (belongsTo / hasOne)\n hydrated = RelModel.fromSnapshot(snapshot);\n }\n\n // Set on the Map for getRelation() / isLoaded() API\n model.loadedRelations.set(name, hydrated);\n\n // Set as direct property for convenient access (model.unit, model.posts, …)\n (model as unknown as Record<string, unknown>)[name] = hydrated;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8DA,IAAa,mBAAb,MAA8B;;;;;;;;;;;;;;;;CAgB5B,OAAc,QACZ,OACA,cACA,mBACM;EACN,IAAI,CAAC,mBAAmB;EAExB,KAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,iBAAiB,GAAG;GAChE,MAAM,MAAM,aAAa;GAIzB,IAAI,CAAC,KAAK;GAEV,MAAM,WAAW,kBAAkB,IAAI,KAAK;GAC5C,IAAI,CAAC,UAAU;GAEf,IAAI;GAEJ,IAAI,aAAa,MAEf,WAAW;QACN,IAAI,MAAM,QAAQ,QAAQ,GAE/B,WAAW,SAAS,KAAK,SAAS,SAAS,aAAa,IAAI,CAAC;QAG7D,WAAW,SAAS,aAAa,QAAQ;GAI3C,MAAM,gBAAgB,IAAI,MAAM,QAAQ;GAGxC,AAAC,MAA6C,QAAQ;EACxD;CACF;AACF"}
@@ -2,7 +2,7 @@ import { ChildModel } from "../model/model.types.mjs";
2
2
  import { RelationConstraints } from "./types.mjs";
3
3
  import { Model } from "../model/model.mjs";
4
4
 
5
- //#region ../@warlock.js/cascade/src/relations/relation-loader.d.ts
5
+ //#region ../cascade/src/relations/relation-loader.d.ts
6
6
  /**
7
7
  * Efficiently loads relationships for one or more model instances.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"relation-loader.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/relation-loader.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAkpB2B;;;;;;;;;;cAjiBd,cAAA,gBAA8B,KAAA,GAAQ,KAAA;;;;mBAQhC,MAAA;;;;mBAKA,UAAA;;;;;;;cAYE,MAAA,EAAQ,MAAA,IAAU,UAAA,EAAY,UAAA,CAAW,MAAA;;;;;;cAUhD,gBAAA;;;;;;;;;;;;;;;;;;;;;EAgCC,IAAA,CACX,SAAA,qBACA,WAAA,GAAc,mBAAA,GACb,OAAA;;;;;;;UA4BW,YAAA;;;;;;;;UA8CA,WAAA;;;;;;;;UA2CA,UAAA;;;;;;;;UAiDA,aAAA;;;;;;;;UA8CA,iBAAA;;;;;;;;UAyFA,mBAAA;;;;;;;UA8CN,mBAAA;;;;;;;;UAWA,iBAAA;;;;;;;UAkCA,qBAAA;;;;;;;UAaA,gBAAA;;;;;;;;UAoBA,OAAA;;;;;;;UAsBA,mBAAA;;;;;;;;UAcA,iBAAA;;;;;;;;UAcA,iBAAA;AAAA"}
1
+ {"version":3,"file":"relation-loader.d.mts","names":[],"sources":["../../../../../../../cascade/src/relations/relation-loader.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAkpB2B;;;;;;;;;;cAjiBd,cAAA,gBAA8B,KAAA,GAAQ,KAAA;;;;mBAQhC,MAAA;;;;mBAKA,UAAA;;;;;;;cAYE,MAAA,EAAQ,MAAA,IAAU,UAAA,EAAY,UAAA,CAAW,MAAA;;;;;;cAUhD,gBAAA;;;;;;;;;;;;;;;;;;;;;EAgCC,IAAA,CACX,SAAA,qBACA,WAAA,GAAc,mBAAA,GACb,OAAA;;;;;;;UA4BW,YAAA;;;;;;;;UA8CA,WAAA;;;;;;;;UA2CA,UAAA;;;;;;;;UAiDA,aAAA;;;;;;;;UA8CA,iBAAA;;;;;;;;UAyFA,mBAAA;;;;;;;UA8CN,mBAAA;;;;;;;;UAWA,iBAAA;;;;;;;UAkCA,qBAAA;;;;;;;UAaA,gBAAA;;;;;;;;UAoBA,OAAA;;;;;;;UAsBA,mBAAA;;;;;;;;UAcA,iBAAA;;;;;;;;UAcA,iBAAA;AAAA"}
@@ -2,7 +2,7 @@ import { getModelFromRegistry, resolveModelName } from "../model/register-model.
2
2
  import { inferBelongsToForeignKey, inferHasForeignKey, inferPivotKey, inferPivotTable } from "./key-conventions.mjs";
3
3
  import { isLazy } from "@mongez/reinforcements";
4
4
 
5
- //#region ../@warlock.js/cascade/src/relations/relation-loader.ts
5
+ //#region ../cascade/src/relations/relation-loader.ts
6
6
  /**
7
7
  * @fileoverview Core relation loading logic for the Cascade ORM.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"relation-loader.mjs","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/relation-loader.ts"],"sourcesContent":["/**\n * @fileoverview Core relation loading logic for the Cascade ORM.\n *\n * The RelationLoader is responsible for efficiently loading related models\n * while preventing N+1 query problems through batch loading strategies.\n *\n * @module @warlock.js/cascade/relations/relation-loader\n */\n\nimport { isLazy, type Lazy } from \"@mongez/reinforcements\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelFromRegistry, resolveModelName } from \"../model/register-model\";\nimport {\n inferBelongsToForeignKey,\n inferHasForeignKey,\n inferPivotKey,\n inferPivotTable,\n} from \"./key-conventions\";\nimport type {\n LoadedRelationResult,\n RelationConstraintCallback,\n RelationConstraints,\n RelationDefinition,\n} from \"./types\";\n\n// ============================================================================\n// SHARED HELPER — SINGLE SOURCE OF TRUTH FOR LOADED RELATIONS\n// ============================================================================\n\n/**\n * Attach a loaded relation onto a model instance, keeping the\n * `loadedRelations` Map and the direct property access in sync.\n *\n * The property is installed as a `defineProperty` getter/setter façade over\n * the Map — `model.posts` reads from `loadedRelations.get(\"posts\")`, and\n * assigning `model.posts = newPosts` writes back to the Map. Eliminates the\n * historical drift between the two storage slots: any mutation visible via\n * one path is visible via the other.\n *\n * Used by both `RelationLoader` (the `with()` path) and the Postgres\n * driver's `attachJoinedRelations` (the `joinWith()` path).\n *\n * @example\n * attachLoadedRelation(user, \"posts\", postsArray);\n * user.posts === user.loadedRelations.get(\"posts\"); // always true\n */\nexport function attachLoadedRelation(\n model: object,\n name: string,\n value: LoadedRelationResult,\n): void {\n const modelWithRelations = model as {\n loadedRelations?: Map<string, LoadedRelationResult>;\n };\n\n if (!modelWithRelations.loadedRelations) {\n modelWithRelations.loadedRelations = new Map();\n }\n\n const relations = modelWithRelations.loadedRelations;\n relations.set(name, value);\n\n // Install a getter/setter on the instance so direct property access reads\n // from (and writes to) the Map. `configurable: true` lets a later reload\n // re-define the property; `enumerable: true` so JSON.stringify and\n // Object.keys see the relation.\n Object.defineProperty(model, name, {\n configurable: true,\n enumerable: true,\n get(): LoadedRelationResult | undefined {\n return relations.get(name);\n },\n set(next: LoadedRelationResult): void {\n relations.set(name, next);\n },\n });\n}\n\n// ============================================================================\n// RELATION LOADER CLASS\n// ============================================================================\n\n/**\n * Efficiently loads relationships for one or more model instances.\n *\n * The RelationLoader handles:\n * - Batch loading to prevent N+1 query problems\n * - Nested relation loading via dot notation\n * - Constrained loading with query callbacks\n * - All relation types (hasOne, hasMany, belongsTo, belongsToMany)\n *\n * @template TModel - The type of model being loaded\n *\n * @example\n * ```typescript\n * const users = await User.query().get();\n * const loader = new RelationLoader(users, User);\n *\n * // Load single relation\n * await loader.load(\"posts\");\n *\n * // Load multiple relations\n * await loader.load([\"posts\", \"organization\"]);\n *\n * // Load nested relations\n * await loader.load(\"posts.comments.author\");\n *\n * // Load with constraints\n * await loader.load(\"posts\", {\n * posts: (query) => query.where(\"isPublished\", true),\n * });\n * ```\n */\nexport class RelationLoader<TModel extends Model = Model> {\n // ==========================================================================\n // PRIVATE PROPERTIES\n // ==========================================================================\n\n /**\n * The model instances to load relations for.\n */\n private readonly models: TModel[];\n\n /**\n * The model class constructor.\n */\n private readonly modelClass: ChildModel<TModel>;\n\n // ==========================================================================\n // CONSTRUCTOR\n // ==========================================================================\n\n /**\n * Creates a new RelationLoader instance.\n *\n * @param models - The model instances to load relations for\n * @param modelClass - The model class constructor\n */\n public constructor(models: TModel[], modelClass: ChildModel<TModel>) {\n this.models = models;\n this.modelClass = modelClass;\n }\n\n /**\n * Read the configured relation conventions from this model's data\n * source. Returns `undefined` when no overrides are set — the inference\n * helpers fall back to framework defaults in that case.\n */\n private get relationDefaults() {\n try {\n return this.modelClass.getDataSource()?.relationDefaults;\n } catch {\n return undefined;\n }\n }\n\n // ==========================================================================\n // PUBLIC METHODS\n // ==========================================================================\n\n /**\n * Loads one or more relations for all model instances.\n *\n * @param relations - A single relation name, array of names, or constraints object\n * @param constraints - Optional constraints to apply to the loading query\n *\n * @example\n * ```typescript\n * // Single relation\n * await loader.load(\"posts\");\n *\n * // Multiple relations\n * await loader.load([\"posts\", \"organization\"]);\n *\n * // With constraints\n * await loader.load(\"posts\", {\n * posts: (query) => query.where(\"status\", \"active\"),\n * });\n * ```\n */\n public async load(\n relations: string | string[],\n constraints?: RelationConstraints,\n ): Promise<void> {\n // If no models, nothing to load\n if (this.models.length === 0) {\n return;\n }\n\n // Normalize to array\n const relationNames = Array.isArray(relations) ? relations : [relations];\n\n // Load each relation\n for (const relationName of relationNames) {\n const constraint = constraints?.[relationName];\n const callbackConstraint = typeof constraint === \"function\" ? constraint : undefined;\n\n await this.loadRelation(relationName, callbackConstraint);\n }\n }\n\n // ==========================================================================\n // PRIVATE METHODS - RELATION LOADING\n // ==========================================================================\n\n /**\n * Loads a single relation, handling nested relations via dot notation.\n *\n * @param name - The relation name, possibly with dot notation for nesting\n * @param constraint - Optional constraint callback\n */\n private async loadRelation(name: string, constraint?: RelationConstraintCallback): Promise<void> {\n const path = this.parseNestedRelation(name);\n const rootRelation = path[0];\n\n // Get the relation definition from the model class\n const definition = this.getRelationDefinition(rootRelation);\n\n if (!definition) {\n throw new Error(\n `Relation \"${rootRelation}\" is not defined on model \"${this.modelClass.name}\". ` +\n `Make sure to define it in the static 'relations' property.`,\n );\n }\n\n // Load based on relation type\n switch (definition.type) {\n case \"hasMany\":\n await this.loadHasMany(rootRelation, definition, constraint);\n break;\n\n case \"hasOne\":\n await this.loadHasOne(rootRelation, definition, constraint);\n break;\n\n case \"belongsTo\":\n await this.loadBelongsTo(rootRelation, definition, constraint);\n break;\n\n case \"belongsToMany\":\n await this.loadBelongsToMany(rootRelation, definition, constraint);\n break;\n }\n\n // If there are nested relations, load them recursively\n if (path.length > 1) {\n await this.loadNestedRelations(rootRelation, path.slice(1), constraint);\n }\n }\n\n /**\n * Loads a hasMany relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadHasMany(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const localKey = definition.localKey ?? this.modelClass.primaryKey ?? \"id\";\n const foreignKey = definition.foreignKey ?? inferHasForeignKey(this.modelClass.name, this.relationDefaults);\n\n // Collect all local key values\n const localKeyValues = this.collectKeyValues(localKey);\n\n if (localKeyValues.length === 0) {\n // No values to query, set empty arrays\n this.setRelationOnModels(name, () => []);\n return;\n }\n\n // Build and execute query\n const query = RelatedModel.query().whereIn(foreignKey, localKeyValues);\n\n if (constraint) {\n constraint(query);\n }\n\n const relatedRecords = await query.get();\n\n // Group by foreign key and assign to models\n const recordsByForeignKey = this.groupBy(relatedRecords as Model[], foreignKey);\n\n this.setRelationOnModels(name, (model) => {\n const keyValue = model.get(localKey);\n return recordsByForeignKey.get(keyValue) ?? [];\n });\n }\n\n /**\n * Loads a hasOne relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadHasOne(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const localKey = definition.localKey ?? this.modelClass.primaryKey ?? \"id\";\n const foreignKey = definition.foreignKey ?? inferHasForeignKey(this.modelClass.name, this.relationDefaults);\n\n // Collect all local key values\n const localKeyValues = this.collectKeyValues(localKey);\n\n if (localKeyValues.length === 0) {\n this.setRelationOnModels(name, () => null);\n return;\n }\n\n // Build and execute query\n const query = RelatedModel.query().whereIn(foreignKey, localKeyValues);\n\n if (constraint) {\n constraint(query);\n }\n\n const relatedRecords = await query.get();\n\n // Index by foreign key\n const recordsByForeignKey = new Map<unknown, Model>();\n for (const record of relatedRecords as Model[]) {\n const fkValue = record.get(foreignKey);\n // For hasOne, take the first match only\n if (!recordsByForeignKey.has(fkValue)) {\n recordsByForeignKey.set(fkValue, record);\n }\n }\n\n this.setRelationOnModels(name, (model) => {\n const keyValue = model.get(localKey);\n return recordsByForeignKey.get(keyValue) ?? null;\n });\n }\n\n /**\n * Loads a belongsTo relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadBelongsTo(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const foreignKey = definition.foreignKey ?? inferBelongsToForeignKey(name, this.relationDefaults);\n const ownerKey = definition.localKey ?? RelatedModel.primaryKey ?? \"id\";\n\n // Collect all foreign key values from the models\n const foreignKeyValues = this.collectKeyValues(foreignKey);\n\n if (foreignKeyValues.length === 0) {\n this.setRelationOnModels(name, () => null);\n return;\n }\n\n // Build and execute query\n const query = RelatedModel.query().whereIn(ownerKey, foreignKeyValues);\n\n if (constraint) {\n constraint(query);\n }\n\n const relatedRecords = await query.get();\n\n // Index by owner key\n const recordsByOwnerKey = new Map<unknown, Model>();\n for (const record of relatedRecords as Model[]) {\n const keyValue = record.get(ownerKey);\n recordsByOwnerKey.set(keyValue, record);\n }\n\n this.setRelationOnModels(name, (model) => {\n const fkValue = model.get(foreignKey);\n return recordsByOwnerKey.get(fkValue) ?? null;\n });\n }\n\n /**\n * Loads a belongsToMany relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadBelongsToMany(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const relatedModelName = resolveModelName(definition.model);\n const pivotTable =\n definition.pivot ??\n inferPivotTable(this.modelClass.name, relatedModelName, this.relationDefaults);\n const localKey = definition.pivotLocalKey ?? this.modelClass.primaryKey ?? \"id\";\n const pivotLocalKey =\n definition.localKey ?? inferPivotKey(this.modelClass.name, this.relationDefaults);\n const pivotForeignKey =\n definition.foreignKey ?? inferPivotKey(relatedModelName, this.relationDefaults);\n const relatedKey = definition.pivotForeignKey ?? RelatedModel.primaryKey ?? \"id\";\n\n // Collect all local key values\n const localKeyValues = this.collectKeyValues(localKey);\n\n if (localKeyValues.length === 0) {\n this.setRelationOnModels(name, () => []);\n return;\n }\n\n // Step 1: Query the pivot table to get the relationships\n const dataSource = this.modelClass.getDataSource();\n const pivotQuery = dataSource.driver\n .queryBuilder(pivotTable)\n .whereIn(pivotLocalKey, localKeyValues);\n\n const pivotRecords = (await pivotQuery.get()) as Record<string, unknown>[];\n\n if (pivotRecords.length === 0) {\n this.setRelationOnModels(name, () => []);\n return;\n }\n\n // Step 2: Collect related model IDs from pivot\n const relatedIds = [...new Set(pivotRecords.map((p) => p[pivotForeignKey]))];\n\n // Step 3: Query the related model\n const relatedQuery = RelatedModel.query().whereIn(relatedKey, relatedIds);\n\n if (constraint) {\n constraint(relatedQuery);\n }\n\n const relatedRecords = await relatedQuery.get();\n\n // Step 4: Index related records by their key\n const relatedById = new Map<unknown, Model>();\n for (const record of relatedRecords as Model[]) {\n relatedById.set(record.get(relatedKey), record);\n }\n\n // Step 5: Build the relationship map from pivot data\n const relationshipMap = new Map<unknown, Model[]>();\n\n for (const pivot of pivotRecords) {\n const localValue = pivot[pivotLocalKey];\n const foreignValue = pivot[pivotForeignKey];\n const relatedRecord = relatedById.get(foreignValue);\n\n if (relatedRecord) {\n if (!relationshipMap.has(localValue)) {\n relationshipMap.set(localValue, []);\n }\n relationshipMap.get(localValue)!.push(relatedRecord);\n }\n }\n\n this.setRelationOnModels(name, (model) => {\n const keyValue = model.get(localKey);\n return relationshipMap.get(keyValue) ?? [];\n });\n }\n\n // ==========================================================================\n // PRIVATE METHODS - NESTED RELATIONS\n // ==========================================================================\n\n /**\n * Loads nested relations recursively.\n *\n * @param parentRelation - The name of the parent relation\n * @param remainingPath - The remaining path segments to load\n * @param constraint - Optional constraint callback\n */\n private async loadNestedRelations(\n parentRelation: string,\n remainingPath: string[],\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n // Collect all loaded related models from the parent relation\n const relatedModels: Model[] = [];\n\n for (const model of this.models) {\n const loaded = this.getLoadedRelation(model, parentRelation);\n\n if (Array.isArray(loaded)) {\n relatedModels.push(...loaded);\n } else if (loaded) {\n relatedModels.push(loaded);\n }\n }\n\n if (relatedModels.length === 0) {\n return;\n }\n\n // Get the related model class\n const parentDefinition = this.getRelationDefinition(parentRelation);\n if (!parentDefinition) return;\n\n const RelatedModelClass = this.resolveModelClass(parentDefinition.model);\n\n // Create a new loader for the nested relation\n const nestedLoader = new RelationLoader(relatedModels, RelatedModelClass as ChildModel<Model>);\n\n // Load the next level\n const nextRelation = remainingPath.join(\".\");\n await nestedLoader.load(nextRelation, constraint ? { [nextRelation]: constraint } : undefined);\n }\n\n // ==========================================================================\n // PRIVATE METHODS - UTILITIES\n // ==========================================================================\n\n /**\n * Parses a dot-notation relation path into segments.\n *\n * @param path - The relation path (e.g., \"posts.comments.author\")\n * @returns An array of relation names\n */\n private parseNestedRelation(path: string): string[] {\n return path.split(\".\");\n }\n\n /**\n * Resolves a model class from the registry by name.\n *\n * @param name - The registered model name\n * @returns The model class constructor\n * @throws Error if the model is not found in the registry\n */\n private resolveModelClass(\n model: string | ChildModel<Model> | Lazy<unknown>,\n ): ChildModel<Model> {\n if (typeof model === \"function\") return model;\n if (isLazy(model)) return model.resolve() as ChildModel<Model>;\n\n const ModelClass = getModelFromRegistry(model);\n\n if (!ModelClass) {\n const callerName = this.modelClass.name || \"unknown\";\n throw new Error(\n `Cannot resolve relation target — model \"${model}\" is not registered.\\n` +\n ` Caller: ${callerName} (relation target reference)\\n` +\n ` Common causes:\\n` +\n ` - The target model is missing the @RegisterModel() decorator\\n` +\n ` - The target model's module is not imported anywhere at startup\\n` +\n ` - Circular import between the caller and target — one of them sees\\n` +\n ` the other as undefined during module load\\n` +\n ` Fix: add an explicit \\`import \"<path-to-${model}-model>\";\\` to your app's\\n` +\n ` entry point so the decorator runs before any query consults this relation.\\n` +\n ` Alternative: declare the relation with \\`lazy(() => ${model})\\` instead of a string\\n` +\n ` to bind directly to the class (no registry lookup needed).`,\n );\n }\n\n return ModelClass;\n }\n\n /**\n * Gets the relation definition from the model class.\n *\n * @param name - The relation name\n * @returns The relation definition or undefined\n */\n private getRelationDefinition(name: string): RelationDefinition | undefined {\n const relations = (\n this.modelClass as unknown as { relations?: Record<string, RelationDefinition> }\n ).relations;\n return relations?.[name];\n }\n\n /**\n * Collects unique key values from all models.\n *\n * @param key - The key to collect values for\n * @returns An array of unique key values\n */\n private collectKeyValues(key: string): unknown[] {\n const values = new Set<unknown>();\n\n for (const model of this.models) {\n const value = model.get(key);\n if (value !== undefined && value !== null) {\n values.add(value);\n }\n }\n\n return [...values];\n }\n\n /**\n * Groups records by a key value.\n *\n * @param records - The records to group\n * @param key - The key to group by\n * @returns A map of key values to records\n */\n private groupBy(records: Model[], key: string): Map<unknown, Model[]> {\n const groups = new Map<unknown, Model[]>();\n\n for (const record of records) {\n const keyValue = record.get(key);\n\n if (!groups.has(keyValue)) {\n groups.set(keyValue, []);\n }\n\n groups.get(keyValue)!.push(record);\n }\n\n return groups;\n }\n\n /**\n * Sets a relation value on all models using a getter function.\n *\n * @param name - The relation name\n * @param getter - Function that returns the relation value for each model\n */\n private setRelationOnModels(name: string, getter: (model: TModel) => LoadedRelationResult): void {\n for (const model of this.models) {\n const value = getter(model);\n this.setLoadedRelation(model, name, value);\n }\n }\n\n /**\n * Gets a loaded relation from a model instance.\n *\n * @param model - The model instance\n * @param name - The relation name\n * @returns The loaded relation value or undefined\n */\n private getLoadedRelation(model: TModel, name: string): LoadedRelationResult | undefined {\n const loadedRelations = (\n model as unknown as { loadedRelations?: Map<string, LoadedRelationResult> }\n ).loadedRelations;\n return loadedRelations?.get(name);\n }\n\n /**\n * Sets a loaded relation on a model instance.\n *\n * @param model - The model instance\n * @param name - The relation name\n * @param value - The relation value\n */\n private setLoadedRelation(model: TModel, name: string, value: LoadedRelationResult): void {\n attachLoadedRelation(model as object, name, value);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"relation-loader.mjs","names":[],"sources":["../../../../../../../cascade/src/relations/relation-loader.ts"],"sourcesContent":["/**\n * @fileoverview Core relation loading logic for the Cascade ORM.\n *\n * The RelationLoader is responsible for efficiently loading related models\n * while preventing N+1 query problems through batch loading strategies.\n *\n * @module @warlock.js/cascade/relations/relation-loader\n */\n\nimport { isLazy, type Lazy } from \"@mongez/reinforcements\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelFromRegistry, resolveModelName } from \"../model/register-model\";\nimport {\n inferBelongsToForeignKey,\n inferHasForeignKey,\n inferPivotKey,\n inferPivotTable,\n} from \"./key-conventions\";\nimport type {\n LoadedRelationResult,\n RelationConstraintCallback,\n RelationConstraints,\n RelationDefinition,\n} from \"./types\";\n\n// ============================================================================\n// SHARED HELPER — SINGLE SOURCE OF TRUTH FOR LOADED RELATIONS\n// ============================================================================\n\n/**\n * Attach a loaded relation onto a model instance, keeping the\n * `loadedRelations` Map and the direct property access in sync.\n *\n * The property is installed as a `defineProperty` getter/setter façade over\n * the Map — `model.posts` reads from `loadedRelations.get(\"posts\")`, and\n * assigning `model.posts = newPosts` writes back to the Map. Eliminates the\n * historical drift between the two storage slots: any mutation visible via\n * one path is visible via the other.\n *\n * Used by both `RelationLoader` (the `with()` path) and the Postgres\n * driver's `attachJoinedRelations` (the `joinWith()` path).\n *\n * @example\n * attachLoadedRelation(user, \"posts\", postsArray);\n * user.posts === user.loadedRelations.get(\"posts\"); // always true\n */\nexport function attachLoadedRelation(\n model: object,\n name: string,\n value: LoadedRelationResult,\n): void {\n const modelWithRelations = model as {\n loadedRelations?: Map<string, LoadedRelationResult>;\n };\n\n if (!modelWithRelations.loadedRelations) {\n modelWithRelations.loadedRelations = new Map();\n }\n\n const relations = modelWithRelations.loadedRelations;\n relations.set(name, value);\n\n // Install a getter/setter on the instance so direct property access reads\n // from (and writes to) the Map. `configurable: true` lets a later reload\n // re-define the property; `enumerable: true` so JSON.stringify and\n // Object.keys see the relation.\n Object.defineProperty(model, name, {\n configurable: true,\n enumerable: true,\n get(): LoadedRelationResult | undefined {\n return relations.get(name);\n },\n set(next: LoadedRelationResult): void {\n relations.set(name, next);\n },\n });\n}\n\n// ============================================================================\n// RELATION LOADER CLASS\n// ============================================================================\n\n/**\n * Efficiently loads relationships for one or more model instances.\n *\n * The RelationLoader handles:\n * - Batch loading to prevent N+1 query problems\n * - Nested relation loading via dot notation\n * - Constrained loading with query callbacks\n * - All relation types (hasOne, hasMany, belongsTo, belongsToMany)\n *\n * @template TModel - The type of model being loaded\n *\n * @example\n * ```typescript\n * const users = await User.query().get();\n * const loader = new RelationLoader(users, User);\n *\n * // Load single relation\n * await loader.load(\"posts\");\n *\n * // Load multiple relations\n * await loader.load([\"posts\", \"organization\"]);\n *\n * // Load nested relations\n * await loader.load(\"posts.comments.author\");\n *\n * // Load with constraints\n * await loader.load(\"posts\", {\n * posts: (query) => query.where(\"isPublished\", true),\n * });\n * ```\n */\nexport class RelationLoader<TModel extends Model = Model> {\n // ==========================================================================\n // PRIVATE PROPERTIES\n // ==========================================================================\n\n /**\n * The model instances to load relations for.\n */\n private readonly models: TModel[];\n\n /**\n * The model class constructor.\n */\n private readonly modelClass: ChildModel<TModel>;\n\n // ==========================================================================\n // CONSTRUCTOR\n // ==========================================================================\n\n /**\n * Creates a new RelationLoader instance.\n *\n * @param models - The model instances to load relations for\n * @param modelClass - The model class constructor\n */\n public constructor(models: TModel[], modelClass: ChildModel<TModel>) {\n this.models = models;\n this.modelClass = modelClass;\n }\n\n /**\n * Read the configured relation conventions from this model's data\n * source. Returns `undefined` when no overrides are set — the inference\n * helpers fall back to framework defaults in that case.\n */\n private get relationDefaults() {\n try {\n return this.modelClass.getDataSource()?.relationDefaults;\n } catch {\n return undefined;\n }\n }\n\n // ==========================================================================\n // PUBLIC METHODS\n // ==========================================================================\n\n /**\n * Loads one or more relations for all model instances.\n *\n * @param relations - A single relation name, array of names, or constraints object\n * @param constraints - Optional constraints to apply to the loading query\n *\n * @example\n * ```typescript\n * // Single relation\n * await loader.load(\"posts\");\n *\n * // Multiple relations\n * await loader.load([\"posts\", \"organization\"]);\n *\n * // With constraints\n * await loader.load(\"posts\", {\n * posts: (query) => query.where(\"status\", \"active\"),\n * });\n * ```\n */\n public async load(\n relations: string | string[],\n constraints?: RelationConstraints,\n ): Promise<void> {\n // If no models, nothing to load\n if (this.models.length === 0) {\n return;\n }\n\n // Normalize to array\n const relationNames = Array.isArray(relations) ? relations : [relations];\n\n // Load each relation\n for (const relationName of relationNames) {\n const constraint = constraints?.[relationName];\n const callbackConstraint = typeof constraint === \"function\" ? constraint : undefined;\n\n await this.loadRelation(relationName, callbackConstraint);\n }\n }\n\n // ==========================================================================\n // PRIVATE METHODS - RELATION LOADING\n // ==========================================================================\n\n /**\n * Loads a single relation, handling nested relations via dot notation.\n *\n * @param name - The relation name, possibly with dot notation for nesting\n * @param constraint - Optional constraint callback\n */\n private async loadRelation(name: string, constraint?: RelationConstraintCallback): Promise<void> {\n const path = this.parseNestedRelation(name);\n const rootRelation = path[0];\n\n // Get the relation definition from the model class\n const definition = this.getRelationDefinition(rootRelation);\n\n if (!definition) {\n throw new Error(\n `Relation \"${rootRelation}\" is not defined on model \"${this.modelClass.name}\". ` +\n `Make sure to define it in the static 'relations' property.`,\n );\n }\n\n // Load based on relation type\n switch (definition.type) {\n case \"hasMany\":\n await this.loadHasMany(rootRelation, definition, constraint);\n break;\n\n case \"hasOne\":\n await this.loadHasOne(rootRelation, definition, constraint);\n break;\n\n case \"belongsTo\":\n await this.loadBelongsTo(rootRelation, definition, constraint);\n break;\n\n case \"belongsToMany\":\n await this.loadBelongsToMany(rootRelation, definition, constraint);\n break;\n }\n\n // If there are nested relations, load them recursively\n if (path.length > 1) {\n await this.loadNestedRelations(rootRelation, path.slice(1), constraint);\n }\n }\n\n /**\n * Loads a hasMany relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadHasMany(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const localKey = definition.localKey ?? this.modelClass.primaryKey ?? \"id\";\n const foreignKey = definition.foreignKey ?? inferHasForeignKey(this.modelClass.name, this.relationDefaults);\n\n // Collect all local key values\n const localKeyValues = this.collectKeyValues(localKey);\n\n if (localKeyValues.length === 0) {\n // No values to query, set empty arrays\n this.setRelationOnModels(name, () => []);\n return;\n }\n\n // Build and execute query\n const query = RelatedModel.query().whereIn(foreignKey, localKeyValues);\n\n if (constraint) {\n constraint(query);\n }\n\n const relatedRecords = await query.get();\n\n // Group by foreign key and assign to models\n const recordsByForeignKey = this.groupBy(relatedRecords as Model[], foreignKey);\n\n this.setRelationOnModels(name, (model) => {\n const keyValue = model.get(localKey);\n return recordsByForeignKey.get(keyValue) ?? [];\n });\n }\n\n /**\n * Loads a hasOne relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadHasOne(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const localKey = definition.localKey ?? this.modelClass.primaryKey ?? \"id\";\n const foreignKey = definition.foreignKey ?? inferHasForeignKey(this.modelClass.name, this.relationDefaults);\n\n // Collect all local key values\n const localKeyValues = this.collectKeyValues(localKey);\n\n if (localKeyValues.length === 0) {\n this.setRelationOnModels(name, () => null);\n return;\n }\n\n // Build and execute query\n const query = RelatedModel.query().whereIn(foreignKey, localKeyValues);\n\n if (constraint) {\n constraint(query);\n }\n\n const relatedRecords = await query.get();\n\n // Index by foreign key\n const recordsByForeignKey = new Map<unknown, Model>();\n for (const record of relatedRecords as Model[]) {\n const fkValue = record.get(foreignKey);\n // For hasOne, take the first match only\n if (!recordsByForeignKey.has(fkValue)) {\n recordsByForeignKey.set(fkValue, record);\n }\n }\n\n this.setRelationOnModels(name, (model) => {\n const keyValue = model.get(localKey);\n return recordsByForeignKey.get(keyValue) ?? null;\n });\n }\n\n /**\n * Loads a belongsTo relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadBelongsTo(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const foreignKey = definition.foreignKey ?? inferBelongsToForeignKey(name, this.relationDefaults);\n const ownerKey = definition.localKey ?? RelatedModel.primaryKey ?? \"id\";\n\n // Collect all foreign key values from the models\n const foreignKeyValues = this.collectKeyValues(foreignKey);\n\n if (foreignKeyValues.length === 0) {\n this.setRelationOnModels(name, () => null);\n return;\n }\n\n // Build and execute query\n const query = RelatedModel.query().whereIn(ownerKey, foreignKeyValues);\n\n if (constraint) {\n constraint(query);\n }\n\n const relatedRecords = await query.get();\n\n // Index by owner key\n const recordsByOwnerKey = new Map<unknown, Model>();\n for (const record of relatedRecords as Model[]) {\n const keyValue = record.get(ownerKey);\n recordsByOwnerKey.set(keyValue, record);\n }\n\n this.setRelationOnModels(name, (model) => {\n const fkValue = model.get(foreignKey);\n return recordsByOwnerKey.get(fkValue) ?? null;\n });\n }\n\n /**\n * Loads a belongsToMany relation for all models.\n *\n * @param name - The relation name\n * @param definition - The relation definition\n * @param constraint - Optional constraint callback\n */\n private async loadBelongsToMany(\n name: string,\n definition: RelationDefinition,\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n const RelatedModel = this.resolveModelClass(definition.model);\n const relatedModelName = resolveModelName(definition.model);\n const pivotTable =\n definition.pivot ??\n inferPivotTable(this.modelClass.name, relatedModelName, this.relationDefaults);\n const localKey = definition.pivotLocalKey ?? this.modelClass.primaryKey ?? \"id\";\n const pivotLocalKey =\n definition.localKey ?? inferPivotKey(this.modelClass.name, this.relationDefaults);\n const pivotForeignKey =\n definition.foreignKey ?? inferPivotKey(relatedModelName, this.relationDefaults);\n const relatedKey = definition.pivotForeignKey ?? RelatedModel.primaryKey ?? \"id\";\n\n // Collect all local key values\n const localKeyValues = this.collectKeyValues(localKey);\n\n if (localKeyValues.length === 0) {\n this.setRelationOnModels(name, () => []);\n return;\n }\n\n // Step 1: Query the pivot table to get the relationships\n const dataSource = this.modelClass.getDataSource();\n const pivotQuery = dataSource.driver\n .queryBuilder(pivotTable)\n .whereIn(pivotLocalKey, localKeyValues);\n\n const pivotRecords = (await pivotQuery.get()) as Record<string, unknown>[];\n\n if (pivotRecords.length === 0) {\n this.setRelationOnModels(name, () => []);\n return;\n }\n\n // Step 2: Collect related model IDs from pivot\n const relatedIds = [...new Set(pivotRecords.map((p) => p[pivotForeignKey]))];\n\n // Step 3: Query the related model\n const relatedQuery = RelatedModel.query().whereIn(relatedKey, relatedIds);\n\n if (constraint) {\n constraint(relatedQuery);\n }\n\n const relatedRecords = await relatedQuery.get();\n\n // Step 4: Index related records by their key\n const relatedById = new Map<unknown, Model>();\n for (const record of relatedRecords as Model[]) {\n relatedById.set(record.get(relatedKey), record);\n }\n\n // Step 5: Build the relationship map from pivot data\n const relationshipMap = new Map<unknown, Model[]>();\n\n for (const pivot of pivotRecords) {\n const localValue = pivot[pivotLocalKey];\n const foreignValue = pivot[pivotForeignKey];\n const relatedRecord = relatedById.get(foreignValue);\n\n if (relatedRecord) {\n if (!relationshipMap.has(localValue)) {\n relationshipMap.set(localValue, []);\n }\n relationshipMap.get(localValue)!.push(relatedRecord);\n }\n }\n\n this.setRelationOnModels(name, (model) => {\n const keyValue = model.get(localKey);\n return relationshipMap.get(keyValue) ?? [];\n });\n }\n\n // ==========================================================================\n // PRIVATE METHODS - NESTED RELATIONS\n // ==========================================================================\n\n /**\n * Loads nested relations recursively.\n *\n * @param parentRelation - The name of the parent relation\n * @param remainingPath - The remaining path segments to load\n * @param constraint - Optional constraint callback\n */\n private async loadNestedRelations(\n parentRelation: string,\n remainingPath: string[],\n constraint?: RelationConstraintCallback,\n ): Promise<void> {\n // Collect all loaded related models from the parent relation\n const relatedModels: Model[] = [];\n\n for (const model of this.models) {\n const loaded = this.getLoadedRelation(model, parentRelation);\n\n if (Array.isArray(loaded)) {\n relatedModels.push(...loaded);\n } else if (loaded) {\n relatedModels.push(loaded);\n }\n }\n\n if (relatedModels.length === 0) {\n return;\n }\n\n // Get the related model class\n const parentDefinition = this.getRelationDefinition(parentRelation);\n if (!parentDefinition) return;\n\n const RelatedModelClass = this.resolveModelClass(parentDefinition.model);\n\n // Create a new loader for the nested relation\n const nestedLoader = new RelationLoader(relatedModels, RelatedModelClass as ChildModel<Model>);\n\n // Load the next level\n const nextRelation = remainingPath.join(\".\");\n await nestedLoader.load(nextRelation, constraint ? { [nextRelation]: constraint } : undefined);\n }\n\n // ==========================================================================\n // PRIVATE METHODS - UTILITIES\n // ==========================================================================\n\n /**\n * Parses a dot-notation relation path into segments.\n *\n * @param path - The relation path (e.g., \"posts.comments.author\")\n * @returns An array of relation names\n */\n private parseNestedRelation(path: string): string[] {\n return path.split(\".\");\n }\n\n /**\n * Resolves a model class from the registry by name.\n *\n * @param name - The registered model name\n * @returns The model class constructor\n * @throws Error if the model is not found in the registry\n */\n private resolveModelClass(\n model: string | ChildModel<Model> | Lazy<unknown>,\n ): ChildModel<Model> {\n if (typeof model === \"function\") return model;\n if (isLazy(model)) return model.resolve() as ChildModel<Model>;\n\n const ModelClass = getModelFromRegistry(model);\n\n if (!ModelClass) {\n const callerName = this.modelClass.name || \"unknown\";\n throw new Error(\n `Cannot resolve relation target — model \"${model}\" is not registered.\\n` +\n ` Caller: ${callerName} (relation target reference)\\n` +\n ` Common causes:\\n` +\n ` - The target model is missing the @RegisterModel() decorator\\n` +\n ` - The target model's module is not imported anywhere at startup\\n` +\n ` - Circular import between the caller and target — one of them sees\\n` +\n ` the other as undefined during module load\\n` +\n ` Fix: add an explicit \\`import \"<path-to-${model}-model>\";\\` to your app's\\n` +\n ` entry point so the decorator runs before any query consults this relation.\\n` +\n ` Alternative: declare the relation with \\`lazy(() => ${model})\\` instead of a string\\n` +\n ` to bind directly to the class (no registry lookup needed).`,\n );\n }\n\n return ModelClass;\n }\n\n /**\n * Gets the relation definition from the model class.\n *\n * @param name - The relation name\n * @returns The relation definition or undefined\n */\n private getRelationDefinition(name: string): RelationDefinition | undefined {\n const relations = (\n this.modelClass as unknown as { relations?: Record<string, RelationDefinition> }\n ).relations;\n return relations?.[name];\n }\n\n /**\n * Collects unique key values from all models.\n *\n * @param key - The key to collect values for\n * @returns An array of unique key values\n */\n private collectKeyValues(key: string): unknown[] {\n const values = new Set<unknown>();\n\n for (const model of this.models) {\n const value = model.get(key);\n if (value !== undefined && value !== null) {\n values.add(value);\n }\n }\n\n return [...values];\n }\n\n /**\n * Groups records by a key value.\n *\n * @param records - The records to group\n * @param key - The key to group by\n * @returns A map of key values to records\n */\n private groupBy(records: Model[], key: string): Map<unknown, Model[]> {\n const groups = new Map<unknown, Model[]>();\n\n for (const record of records) {\n const keyValue = record.get(key);\n\n if (!groups.has(keyValue)) {\n groups.set(keyValue, []);\n }\n\n groups.get(keyValue)!.push(record);\n }\n\n return groups;\n }\n\n /**\n * Sets a relation value on all models using a getter function.\n *\n * @param name - The relation name\n * @param getter - Function that returns the relation value for each model\n */\n private setRelationOnModels(name: string, getter: (model: TModel) => LoadedRelationResult): void {\n for (const model of this.models) {\n const value = getter(model);\n this.setLoadedRelation(model, name, value);\n }\n }\n\n /**\n * Gets a loaded relation from a model instance.\n *\n * @param model - The model instance\n * @param name - The relation name\n * @returns The loaded relation value or undefined\n */\n private getLoadedRelation(model: TModel, name: string): LoadedRelationResult | undefined {\n const loadedRelations = (\n model as unknown as { loadedRelations?: Map<string, LoadedRelationResult> }\n ).loadedRelations;\n return loadedRelations?.get(name);\n }\n\n /**\n * Sets a loaded relation on a model instance.\n *\n * @param model - The model instance\n * @param name - The relation name\n * @param value - The relation value\n */\n private setLoadedRelation(model: TModel, name: string, value: LoadedRelationResult): void {\n attachLoadedRelation(model as object, name, value);\n }\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { Model } from "../model/model.mjs";
3
3
  import { QueryBuilderContract } from "../contracts/query-builder.contract.mjs";
4
4
  import { Lazy } from "@mongez/reinforcements";
5
5
 
6
- //#region ../@warlock.js/cascade/src/relations/types.d.ts
6
+ //#region ../cascade/src/relations/types.d.ts
7
7
  /**
8
8
  * The type of relationship between models.
9
9
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/relations/types.ts"],"mappings":";;;;;;AAiCwB;AAsBxB;;;;;;;;;;;;;;;AAtBwB,KAAZ,YAAA;;;;;;;AAkFK;AAkBjB;;;;;;;;AAoBiB;KAlGL,kBAAA;EAgHa;;;EAAA,SA5Gd,IAAA,EAAM,YAAA;EAyHN;;;AAOM;AAcjB;;;;;;;;AAkBiB;AAkBjB;;EAzDW,SAxGA,KAAA,WAAgB,UAAA,CAAW,KAAA,IAAS,IAAA;EAiKf;;;;;;EAAA,SAzJrB,UAAA;EAiMM;AAAA;AAiBjB;;;EAjBiB,SA1LN,QAAA;EA2M0D;AAkBrE;;EAlBqE,SAtM1D,KAAA;EAwNuB;AAAmD;AAYrF;;EAZkC,SAlNvB,aAAA;EA8NwB;AAAa;AAYhD;;EAZmC,SAxNxB,eAAA;EAoOsB;AAAgC;AAsBjE;;;EAtBiC,SA7NtB,MAAA;AAAA;AAiQX;;;;AAA8B;AAM9B;;;;AAAoB;;AANpB,KA/OY,cAAA;;;;;;WAMD,UAAA;;;;;;WAOA,QAAA;;;;;;WAOA,MAAA;AAAA;;;;;;;;;;;;KAcC,aAAA;;;;;;WAMD,UAAA;;;;;;WAOA,QAAA;;;;;;WAOA,MAAA;AAAA;;;;;;;;;;;;KAcC,gBAAA;;;;;;WAMD,UAAA;;;;;;WAOA,QAAA;;;;WAKA,MAAA;AAAA;;;;;;;;;;;;;;;;KAkBC,oBAAA;;;;;;;WAOD,KAAA;;;;;;WAOA,QAAA;;;;;;WAOA,UAAA;;;;;;WAOA,aAAA;;;;;;WAOA,eAAA;;;;WAKA,MAAA;AAAA;;;;;;;;;;;KAiBC,0BAAA,IAA8B,KAA2B,EAApB,oBAAoB;;;;;;;;;;;;;;;;;KAkBzD,mBAAA,GAAsB,MAAM,mBAAmB,0BAAA;;;;;;;KAY/C,oBAAA,GAAuB,KAAA,GAAQ,KAAK;;;;;;;;;;;KAYpC,kBAAA,GAAqB,GAAG,SAAS,oBAAA;;;;;;;;;;;;;;;;;KAsBjC,mBAAA,GAAsB,MAAM,SAAS,kBAAA;;;;;;;;;KAcrC,SAAA,GAAY,MAAM;;;;;KAMlB,QAAA"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../cascade/src/relations/types.ts"],"mappings":";;;;;;AAiCwB;AAsBxB;;;;;;;;;;;;;;;AAtBwB,KAAZ,YAAA;;;;;;;AAkFK;AAkBjB;;;;;;;;AAoBiB;KAlGL,kBAAA;EAgHa;;;EAAA,SA5Gd,IAAA,EAAM,YAAA;EAyHN;;;AAOM;AAcjB;;;;;;;;AAkBiB;AAkBjB;;EAzDW,SAxGA,KAAA,WAAgB,UAAA,CAAW,KAAA,IAAS,IAAA;EAiKf;;;;;;EAAA,SAzJrB,UAAA;EAiMM;AAAA;AAiBjB;;;EAjBiB,SA1LN,QAAA;EA2M0D;AAkBrE;;EAlBqE,SAtM1D,KAAA;EAwNuB;AAAmD;AAYrF;;EAZkC,SAlNvB,aAAA;EA8NwB;AAAa;AAYhD;;EAZmC,SAxNxB,eAAA;EAoOsB;AAAgC;AAsBjE;;;EAtBiC,SA7NtB,MAAA;AAAA;AAiQX;;;;AAA8B;AAM9B;;;;AAAoB;;AANpB,KA/OY,cAAA;;;;;;WAMD,UAAA;;;;;;WAOA,QAAA;;;;;;WAOA,MAAA;AAAA;;;;;;;;;;;;KAcC,aAAA;;;;;;WAMD,UAAA;;;;;;WAOA,QAAA;;;;;;WAOA,MAAA;AAAA;;;;;;;;;;;;KAcC,gBAAA;;;;;;WAMD,UAAA;;;;;;WAOA,QAAA;;;;WAKA,MAAA;AAAA;;;;;;;;;;;;;;;;KAkBC,oBAAA;;;;;;;WAOD,KAAA;;;;;;WAOA,QAAA;;;;;;WAOA,UAAA;;;;;;WAOA,aAAA;;;;;;WAOA,eAAA;;;;WAKA,MAAA;AAAA;;;;;;;;;;;KAiBC,0BAAA,IAA8B,KAA2B,EAApB,oBAAoB;;;;;;;;;;;;;;;;;KAkBzD,mBAAA,GAAsB,MAAM,mBAAmB,0BAAA;;;;;;;KAY/C,oBAAA,GAAuB,KAAA,GAAQ,KAAK;;;;;;;;;;;KAYpC,kBAAA,GAAqB,GAAG,SAAS,oBAAA;;;;;;;;;;;;;;;;;KAsBjC,mBAAA,GAAsB,MAAM,SAAS,kBAAA;;;;;;;;;KAcrC,SAAA,GAAY,MAAM;;;;;KAMlB,QAAA"}
@@ -1,7 +1,7 @@
1
1
  import { Model } from "../model/model.mjs";
2
2
  import { RemoverContract, RemoverOptions, RemoverResult } from "../contracts/database-remover.contract.mjs";
3
3
 
4
- //#region ../@warlock.js/cascade/src/remover/database-remover.d.ts
4
+ //#region ../cascade/src/remover/database-remover.d.ts
5
5
  /**
6
6
  * Database remover service that orchestrates model deletion.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database-remover.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/remover/database-remover.ts"],"mappings":";;;;;;AAmCA;;;;;;;;;;;;;;;;;;cAAa,eAAA,YAA2B,eAAA;EA+BnB;EAAA,iBA7BF,KAAA;EA6Ca;EAAA,iBA1Cb,IAAA;EA0CmC;EAAA,iBAvCnC,UAAA;EAmMT;EAAA,iBAhMS,MAAA;EA0OH;EAAA,iBAvOG,KAAA;EAuOQ;EAAA,iBApOR,UAAA;;;;;;;;;;;;;cAcE,KAAA,EAAO,KAAA;;;;;;;;EAgBb,OAAA,CAAQ,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,aAAA;;;;;;;;;;;;;;;;;;;;;UA4JpD,kBAAA;;;;;;;;;;;;UAsBA,iBAAA;;;;;;;;;UAoBM,WAAA;AAAA"}
1
+ {"version":3,"file":"database-remover.d.mts","names":[],"sources":["../../../../../../../cascade/src/remover/database-remover.ts"],"mappings":";;;;;;AAmCA;;;;;;;;;;;;;;;;;;cAAa,eAAA,YAA2B,eAAA;EA+BnB;EAAA,iBA7BF,KAAA;EA6Ca;EAAA,iBA1Cb,IAAA;EA0CmC;EAAA,iBAvCnC,UAAA;EAmMT;EAAA,iBAhMS,MAAA;EA0OH;EAAA,iBAvOG,KAAA;EAuOQ;EAAA,iBApOR,UAAA;;;;;;;;;;;;;cAcE,KAAA,EAAO,KAAA;;;;;;;;EAgBb,OAAA,CAAQ,OAAA,GAAS,cAAA,GAAsB,OAAA,CAAQ,aAAA;;;;;;;;;;;;;;;;;;;;;UA4JpD,kBAAA;;;;;;;;;;;;UAsBA,iBAAA;;;;;;;;;UAoBM,WAAA;AAAA"}
@@ -1,7 +1,7 @@
1
1
  import { getModelDeletedEvent } from "../sync/model-events.mjs";
2
2
  import events from "@mongez/events";
3
3
 
4
- //#region ../@warlock.js/cascade/src/remover/database-remover.ts
4
+ //#region ../cascade/src/remover/database-remover.ts
5
5
  /**
6
6
  * Database remover service that orchestrates model deletion.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database-remover.mjs","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/remover/database-remover.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport type {\n DriverContract,\n UpdateOperations,\n} from \"../contracts/database-driver.contract\";\nimport type {\n RemoverContract,\n RemoverOptions,\n RemoverResult,\n} from \"../contracts/database-remover.contract\";\nimport type { OnDeletedEventContext } from \"../events/model-events\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelDeletedEvent } from \"../sync/model-events\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database remover service that orchestrates model deletion.\n *\n * Handles the complete deletion pipeline:\n * 1. Strategy resolution (options → model static → data source default)\n * 2. Validation (check if model is new, has primary key)\n * 3. Event emission (deleting, deleted)\n * 4. Driver execution (based on strategy: trash, permanent, or soft)\n * 5. Post-deletion cleanup (mark as new, reset state)\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * const result = await remover.destroy();\n *\n * console.log(result.success); // true\n * console.log(result.strategy); // \"trash\" | \"permanent\" | \"soft\"\n * ```\n */\nexport class DatabaseRemover implements RemoverContract {\n /** The model instance being deleted */\n private readonly model: Model;\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver */\n private readonly dataSource: DataSource;\n\n /** Database driver for executing queries */\n private readonly driver: DriverContract;\n\n /** Table/collection name */\n private readonly table: string;\n\n /** Primary key field name */\n private readonly primaryKey: string;\n\n /**\n * Create a new remover instance for a model.\n *\n * @param model - The model instance to delete\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * await remover.destroy();\n * ```\n */\n public constructor(model: Model) {\n this.model = model;\n this.ctor = model.constructor as ChildModel<Model>;\n this.dataSource = this.ctor.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = this.ctor.table;\n this.primaryKey = this.ctor.primaryKey;\n }\n\n /**\n * Destroy (delete) the model instance from the database.\n *\n * @param options - Remover options\n * @returns Result containing success status, strategy used, and metadata\n * @throws {Error} If model is new (not saved) or if deletion fails\n */\n public async destroy(options: RemoverOptions = {}): Promise<RemoverResult> {\n // 1. Resolve strategy (options → model static → data source default → permanent)\n const strategy =\n options.strategy ??\n this.ctor.deleteStrategy ??\n this.dataSource.defaultDeleteStrategy ??\n \"permanent\";\n\n // 2. Validate model is not new and has primary key\n if (this.model.isNew) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance that hasn't been saved to the database.`,\n );\n }\n\n const primaryKeyValue = this.model.get(this.primaryKey);\n if (!primaryKeyValue) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance: primary key (${this.primaryKey}) is missing.`,\n );\n }\n\n // 3. Emit deleting event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleting\", {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n });\n }\n\n // 4. Execute deletion based on strategy\n let deletedCount = 0;\n let trashRecord: Record<string, unknown> | undefined;\n\n const filter = { [this.primaryKey]: primaryKeyValue };\n\n const context: Partial<OnDeletedEventContext> = {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n };\n\n switch (strategy) {\n case \"trash\": {\n // Move to trash table, then delete\n const trashTable = this.resolveTrashTable();\n const documentData = { ...this.model.data };\n\n // Prepare trash record with metadata and handle ID conflicts\n const trashData = this.prepareTrashRecord(documentData);\n\n // Insert into trash table\n const insertResult = await this.driver.insert(trashTable, trashData);\n trashRecord = insertResult.document as Record<string, unknown>;\n\n context.trashRecord = trashRecord;\n\n // Delete original\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"permanent\": {\n // Direct deletion\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"soft\": {\n // Set deletedAt timestamp (using resolved column name)\n const deletedAtColumn = this.ctor.deletedAtColumn;\n\n // Only proceed if deletedAtColumn is configured (not false or undefined)\n if (deletedAtColumn === false || deletedAtColumn === undefined) {\n throw new Error(\n `Cannot perform soft delete on ${this.ctor.name}: deletedAtColumn is not configured. ` +\n `Set a column name or use a different delete strategy.`,\n );\n }\n\n const deletedAt = new Date();\n const updateOperations: UpdateOperations = {\n $set: { [deletedAtColumn]: deletedAt },\n };\n const updateResult = await this.driver.update(\n this.table,\n filter,\n updateOperations,\n );\n deletedCount = updateResult.modifiedCount > 0 ? 1 : 0;\n\n // The row stays (unlike trash/permanent), so reflect the persisted\n // timestamp on the in-memory model — otherwise the instance is stale\n // and `model.get(deletedAtColumn)` stays undefined after destroy().\n if (deletedCount > 0) {\n this.model.set(deletedAtColumn, deletedAt);\n }\n break;\n }\n }\n\n if (deletedCount === 0) {\n throw new Error(\n `Failed to destroy ${this.ctor.name} instance: record not found.`,\n );\n }\n\n context.deletedCount = deletedCount;\n\n // 5. Post-deletion cleanup\n // Only mark as new for permanent and trash (soft delete keeps the record)\n if (strategy !== \"soft\") {\n this.model.isNew = true;\n }\n\n // 6. Emit deleted event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleted\", context);\n }\n\n // 7. Trigger sync operations (fire-and-forget, non-blocking)\n if (!options.skipSync) {\n void this.triggerSync();\n }\n\n return {\n success: true,\n deletedCount,\n strategy,\n trashRecord,\n };\n }\n\n /**\n * Prepare the trash record by preserving all original fields and adding deletion metadata.\n *\n * Keeps all original fields intact for easy restoration and adds:\n * - `deletedAt`: Timestamp when the record was deleted\n * - `originalTable`: The table/collection the record came from (for filtering in restoreAll)\n *\n * **ID Handling:**\n * - MongoDB with `_id`: Keeps `_id` as-is (unique across database)\n * - MongoDB with auto-increment `id`: Keeps `id` as a regular field (not primary key)\n * - SQL: Keeps original `id` as a regular field (trash table uses its own auto-increment primary key)\n *\n * The trash table should use its own primary key structure:\n * - MongoDB: Uses `_id` (ObjectId) as primary key, original `id` is just a field\n * - SQL: Uses auto-increment `trashId` as primary key, original `id` is just a field\n *\n * @param documentData - The original document data\n * @returns Prepared trash record data with all original fields + deletedAt + originalTable\n * @private\n */\n private prepareTrashRecord(\n documentData: Record<string, unknown>,\n ): Record<string, unknown> {\n // Preserve all original fields and add deletion metadata\n return {\n ...documentData,\n deletedAt: new Date(),\n originalTable: this.table,\n };\n }\n\n /**\n * Resolve the trash table/collection name.\n *\n * Priority:\n * 1. Model.trashTable (if set)\n * 2. Data source defaultTrashTable (e.g., \"RecycleBin\" for MongoDB)\n * 3. Default pattern: `{table}Trash`\n *\n * @returns The trash table/collection name\n * @private\n */\n private resolveTrashTable(): string {\n if (this.ctor.trashTable) {\n return this.ctor.trashTable;\n }\n\n if (this.dataSource.defaultTrashTable) {\n return this.dataSource.defaultTrashTable;\n }\n\n return `${this.table}Trash`;\n }\n\n /**\n * Trigger sync operations after successful deletion.\n *\n * Emits a model.deleted event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @private\n */\n private async triggerSync(): Promise<void> {\n // Emit model.deleted event - ModelSyncOperation listens to these\n await events.triggerAll(getModelDeletedEvent(this.ctor), this.model);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"database-remover.mjs","names":[],"sources":["../../../../../../../cascade/src/remover/database-remover.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport type {\n DriverContract,\n UpdateOperations,\n} from \"../contracts/database-driver.contract\";\nimport type {\n RemoverContract,\n RemoverOptions,\n RemoverResult,\n} from \"../contracts/database-remover.contract\";\nimport type { OnDeletedEventContext } from \"../events/model-events\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelDeletedEvent } from \"../sync/model-events\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database remover service that orchestrates model deletion.\n *\n * Handles the complete deletion pipeline:\n * 1. Strategy resolution (options → model static → data source default)\n * 2. Validation (check if model is new, has primary key)\n * 3. Event emission (deleting, deleted)\n * 4. Driver execution (based on strategy: trash, permanent, or soft)\n * 5. Post-deletion cleanup (mark as new, reset state)\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * const result = await remover.destroy();\n *\n * console.log(result.success); // true\n * console.log(result.strategy); // \"trash\" | \"permanent\" | \"soft\"\n * ```\n */\nexport class DatabaseRemover implements RemoverContract {\n /** The model instance being deleted */\n private readonly model: Model;\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver */\n private readonly dataSource: DataSource;\n\n /** Database driver for executing queries */\n private readonly driver: DriverContract;\n\n /** Table/collection name */\n private readonly table: string;\n\n /** Primary key field name */\n private readonly primaryKey: string;\n\n /**\n * Create a new remover instance for a model.\n *\n * @param model - The model instance to delete\n *\n * @example\n * ```typescript\n * const user = await User.find(1);\n * const remover = new DatabaseRemover(user);\n * await remover.destroy();\n * ```\n */\n public constructor(model: Model) {\n this.model = model;\n this.ctor = model.constructor as ChildModel<Model>;\n this.dataSource = this.ctor.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = this.ctor.table;\n this.primaryKey = this.ctor.primaryKey;\n }\n\n /**\n * Destroy (delete) the model instance from the database.\n *\n * @param options - Remover options\n * @returns Result containing success status, strategy used, and metadata\n * @throws {Error} If model is new (not saved) or if deletion fails\n */\n public async destroy(options: RemoverOptions = {}): Promise<RemoverResult> {\n // 1. Resolve strategy (options → model static → data source default → permanent)\n const strategy =\n options.strategy ??\n this.ctor.deleteStrategy ??\n this.dataSource.defaultDeleteStrategy ??\n \"permanent\";\n\n // 2. Validate model is not new and has primary key\n if (this.model.isNew) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance that hasn't been saved to the database.`,\n );\n }\n\n const primaryKeyValue = this.model.get(this.primaryKey);\n if (!primaryKeyValue) {\n throw new Error(\n `Cannot destroy ${this.ctor.name} instance: primary key (${this.primaryKey}) is missing.`,\n );\n }\n\n // 3. Emit deleting event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleting\", {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n });\n }\n\n // 4. Execute deletion based on strategy\n let deletedCount = 0;\n let trashRecord: Record<string, unknown> | undefined;\n\n const filter = { [this.primaryKey]: primaryKeyValue };\n\n const context: Partial<OnDeletedEventContext> = {\n strategy,\n primaryKeyValue,\n primaryKey: this.primaryKey,\n };\n\n switch (strategy) {\n case \"trash\": {\n // Move to trash table, then delete\n const trashTable = this.resolveTrashTable();\n const documentData = { ...this.model.data };\n\n // Prepare trash record with metadata and handle ID conflicts\n const trashData = this.prepareTrashRecord(documentData);\n\n // Insert into trash table\n const insertResult = await this.driver.insert(trashTable, trashData);\n trashRecord = insertResult.document as Record<string, unknown>;\n\n context.trashRecord = trashRecord;\n\n // Delete original\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"permanent\": {\n // Direct deletion\n const result = await this.driver.delete(this.table, filter);\n deletedCount = result > 0 ? 1 : 0;\n break;\n }\n\n case \"soft\": {\n // Set deletedAt timestamp (using resolved column name)\n const deletedAtColumn = this.ctor.deletedAtColumn;\n\n // Only proceed if deletedAtColumn is configured (not false or undefined)\n if (deletedAtColumn === false || deletedAtColumn === undefined) {\n throw new Error(\n `Cannot perform soft delete on ${this.ctor.name}: deletedAtColumn is not configured. ` +\n `Set a column name or use a different delete strategy.`,\n );\n }\n\n const deletedAt = new Date();\n const updateOperations: UpdateOperations = {\n $set: { [deletedAtColumn]: deletedAt },\n };\n const updateResult = await this.driver.update(\n this.table,\n filter,\n updateOperations,\n );\n deletedCount = updateResult.modifiedCount > 0 ? 1 : 0;\n\n // The row stays (unlike trash/permanent), so reflect the persisted\n // timestamp on the in-memory model — otherwise the instance is stale\n // and `model.get(deletedAtColumn)` stays undefined after destroy().\n if (deletedCount > 0) {\n this.model.set(deletedAtColumn, deletedAt);\n }\n break;\n }\n }\n\n if (deletedCount === 0) {\n throw new Error(\n `Failed to destroy ${this.ctor.name} instance: record not found.`,\n );\n }\n\n context.deletedCount = deletedCount;\n\n // 5. Post-deletion cleanup\n // Only mark as new for permanent and trash (soft delete keeps the record)\n if (strategy !== \"soft\") {\n this.model.isNew = true;\n }\n\n // 6. Emit deleted event (unless skipEvents)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"deleted\", context);\n }\n\n // 7. Trigger sync operations (fire-and-forget, non-blocking)\n if (!options.skipSync) {\n void this.triggerSync();\n }\n\n return {\n success: true,\n deletedCount,\n strategy,\n trashRecord,\n };\n }\n\n /**\n * Prepare the trash record by preserving all original fields and adding deletion metadata.\n *\n * Keeps all original fields intact for easy restoration and adds:\n * - `deletedAt`: Timestamp when the record was deleted\n * - `originalTable`: The table/collection the record came from (for filtering in restoreAll)\n *\n * **ID Handling:**\n * - MongoDB with `_id`: Keeps `_id` as-is (unique across database)\n * - MongoDB with auto-increment `id`: Keeps `id` as a regular field (not primary key)\n * - SQL: Keeps original `id` as a regular field (trash table uses its own auto-increment primary key)\n *\n * The trash table should use its own primary key structure:\n * - MongoDB: Uses `_id` (ObjectId) as primary key, original `id` is just a field\n * - SQL: Uses auto-increment `trashId` as primary key, original `id` is just a field\n *\n * @param documentData - The original document data\n * @returns Prepared trash record data with all original fields + deletedAt + originalTable\n * @private\n */\n private prepareTrashRecord(\n documentData: Record<string, unknown>,\n ): Record<string, unknown> {\n // Preserve all original fields and add deletion metadata\n return {\n ...documentData,\n deletedAt: new Date(),\n originalTable: this.table,\n };\n }\n\n /**\n * Resolve the trash table/collection name.\n *\n * Priority:\n * 1. Model.trashTable (if set)\n * 2. Data source defaultTrashTable (e.g., \"RecycleBin\" for MongoDB)\n * 3. Default pattern: `{table}Trash`\n *\n * @returns The trash table/collection name\n * @private\n */\n private resolveTrashTable(): string {\n if (this.ctor.trashTable) {\n return this.ctor.trashTable;\n }\n\n if (this.dataSource.defaultTrashTable) {\n return this.dataSource.defaultTrashTable;\n }\n\n return `${this.table}Trash`;\n }\n\n /**\n * Trigger sync operations after successful deletion.\n *\n * Emits a model.deleted event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @private\n */\n private async triggerSync(): Promise<void> {\n // Emit model.deleted event - ModelSyncOperation listens to these\n await events.triggerAll(getModelDeletedEvent(this.ctor), this.model);\n }\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { Model } from "../model/model.mjs";
2
2
  import { RestorerContract, RestorerOptions, RestorerResult } from "../contracts/database-restorer.contract.mjs";
3
3
 
4
- //#region ../@warlock.js/cascade/src/restorer/database-restorer.d.ts
4
+ //#region ../cascade/src/restorer/database-restorer.d.ts
5
5
  /**
6
6
  * Database restorer service that orchestrates model restoration.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database-restorer.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/restorer/database-restorer.ts"],"mappings":";;;;;;AA6BA;;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,gBAAA;EActB;EAAA,iBAZA,IAAA;EAyBqB;EAAA,iBAtBrB,UAAA;EAuCJ;EAAA,iBApCI,MAAA;EAsCN;EAAA,iBAnCM,KAAA;EAoCd;EAAA,iBAjCc,UAAA;EAoHJ;;;;;;;;;;;cAvGM,UAAA,SAAmB,KAAA;EAgcb;AAAA;;;;;;;;EA/aZ,OAAA,CACX,EAAA,mBACA,OAAA,GAAS,eAAA,GACR,OAAA,CAAQ,cAAA;;;;;;;EAmFE,UAAA,CAAW,OAAA,GAAS,eAAA,GAAuB,OAAA,CAAQ,cAAA;;;;;;;;;;UAiKxD,eAAA;;;;;;;;;UAiBM,qBAAA;;;;;;;;UA0CA,yBAAA;;;;;;;;;;UA6CA,gBAAA;;;;;;;;UA4BA,aAAA;;;;;;;;;;;UAoBA,WAAA;;;;;;;;;;;;UAgCN,iBAAA;AAAA"}
1
+ {"version":3,"file":"database-restorer.d.mts","names":[],"sources":["../../../../../../../cascade/src/restorer/database-restorer.ts"],"mappings":";;;;;;AA6BA;;;;;;;;;;;;;;;;;cAAa,gBAAA,YAA4B,gBAAA;EActB;EAAA,iBAZA,IAAA;EAyBqB;EAAA,iBAtBrB,UAAA;EAuCJ;EAAA,iBApCI,MAAA;EAsCN;EAAA,iBAnCM,KAAA;EAoCd;EAAA,iBAjCc,UAAA;EAoHJ;;;;;;;;;;;cAvGM,UAAA,SAAmB,KAAA;EAgcb;AAAA;;;;;;;;EA/aZ,OAAA,CACX,EAAA,mBACA,OAAA,GAAS,eAAA,GACR,OAAA,CAAQ,cAAA;;;;;;;EAmFE,UAAA,CAAW,OAAA,GAAS,eAAA,GAAuB,OAAA,CAAQ,cAAA;;;;;;;;;;UAiKxD,eAAA;;;;;;;;;UAiBM,qBAAA;;;;;;;;UA0CA,yBAAA;;;;;;;;;;UA6CA,gBAAA;;;;;;;;UA4BA,aAAA;;;;;;;;;;;UAoBA,WAAA;;;;;;;;;;;;UAgCN,iBAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- //#region ../@warlock.js/cascade/src/restorer/database-restorer.ts
1
+ //#region ../cascade/src/restorer/database-restorer.ts
2
2
  /**
3
3
  * Database restorer service that orchestrates model restoration.
4
4
  *