@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":"exists-rule.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/validation/rules/exists-rule.ts"],"sourcesContent":["import { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { resolveModelClass } from \"../../model/register-model\";\nimport type { ExistsRuleOptions } from \"../types\";\n\n/**\n * Validates that a record exists in the database for the given column/value.\n *\n * @example\n * v.string().exists(Organization, { column: \"id\" });\n */\nexport const existsRule: SchemaRule<ExistsRuleOptions> = {\n name: \"exists\",\n defaultErrorMessage: \"The :input must exist\",\n async validate(value: any, context) {\n const { Model, query, column = context.key } = this.context.options;\n\n const ResolvedModelClass = resolveModelClass(Model);\n\n const dbQuery = ResolvedModelClass.query();\n\n dbQuery.where(column, value);\n\n if (query) {\n await query({\n query: dbQuery,\n value,\n allValues: context.allValues,\n });\n }\n\n const document = await dbQuery.first();\n\n return document ? VALID_RULE : invalidRule(this, context);\n },\n};\n"],"mappings":";;;;;;;;;;AAUA,MAAa,aAA4C;CACvD,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAClC,MAAM,EAAE,OAAO,OAAO,SAAS,QAAQ,QAAQ,KAAK,QAAQ;EAI5D,MAAM,UAFqB,kBAAkB,KAEZ,CAAC,CAAC,MAAM;EAEzC,QAAQ,MAAM,QAAQ,KAAK;EAE3B,IAAI,OACF,MAAM,MAAM;GACV,OAAO;GACP;GACA,WAAW,QAAQ;EACrB,CAAC;EAKH,OAAO,MAFgB,QAAQ,MAAM,IAEnB,aAAa,YAAY,MAAM,OAAO;CAC1D;AACF"}
1
+ {"version":3,"file":"exists-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/exists-rule.ts"],"sourcesContent":["import { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { resolveModelClass } from \"../../model/register-model\";\nimport type { ExistsRuleOptions } from \"../types\";\n\n/**\n * Validates that a record exists in the database for the given column/value.\n *\n * @example\n * v.string().exists(Organization, { column: \"id\" });\n */\nexport const existsRule: SchemaRule<ExistsRuleOptions> = {\n name: \"exists\",\n defaultErrorMessage: \"The :input must exist\",\n async validate(value: any, context) {\n const { Model, query, column = context.key } = this.context.options;\n\n const ResolvedModelClass = resolveModelClass(Model);\n\n const dbQuery = ResolvedModelClass.query();\n\n dbQuery.where(column, value);\n\n if (query) {\n await query({\n query: dbQuery,\n value,\n allValues: context.allValues,\n });\n }\n\n const document = await dbQuery.first();\n\n return document ? VALID_RULE : invalidRule(this, context);\n },\n};\n"],"mappings":";;;;;;;;;;AAUA,MAAa,aAA4C;CACvD,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAClC,MAAM,EAAE,OAAO,OAAO,SAAS,QAAQ,QAAQ,KAAK,QAAQ;EAI5D,MAAM,UAFqB,kBAAkB,KAEZ,CAAC,CAAC,MAAM;EAEzC,QAAQ,MAAM,QAAQ,KAAK;EAE3B,IAAI,OACF,MAAM,MAAM;GACV,OAAO;GACP;GACA,WAAW,QAAQ;EACrB,CAAC;EAKH,OAAO,MAFgB,QAAQ,MAAM,IAEnB,aAAa,YAAY,MAAM,OAAO;CAC1D;AACF"}
@@ -2,7 +2,7 @@ import { resolveModelClass } from "../../model/register-model.mjs";
2
2
  import { get } from "@mongez/reinforcements";
3
3
  import { VALID_RULE, invalidRule } from "@warlock.js/seal";
4
4
 
5
- //#region ../@warlock.js/cascade/src/validation/rules/unique-rule.ts
5
+ //#region ../cascade/src/validation/rules/unique-rule.ts
6
6
  /**
7
7
  * Validates that a value is unique against a database column.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"unique-rule.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/validation/rules/unique-rule.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { resolveModelClass } from \"../../model/register-model\";\nimport type { UniqueRuleOptions } from \"../types\";\n\n/**\n * Validates that a value is unique against a database column.\n *\n * Supports three exclusion modes:\n * - `except`: read a sibling input's value and exclude rows where that\n * column equals the sibling's value\n * - `exceptColumnName` + `exceptValue`: exclude rows where the named\n * column equals the supplied value (used by request-aware wrappers)\n * - Custom `query` callback for anything more involved\n *\n * @example\n * v.email().unique(\"User\");\n * v.string().unique(\"User\", { except: \"id\" });\n */\nexport const uniqueRule: SchemaRule<UniqueRuleOptions> = {\n name: \"unique\",\n defaultErrorMessage: \"The :input must be unique\",\n async validate(value: any, context) {\n const {\n Model,\n except,\n column = context.key,\n exceptColumnName,\n exceptValue,\n query,\n } = this.context.options;\n\n const ResolvedModelClass = resolveModelClass(Model);\n\n const dbQuery = ResolvedModelClass.query();\n\n dbQuery.where(column, value);\n\n if (except) {\n const exceptVal = get(context.allValues, except);\n\n if (exceptVal !== undefined) {\n dbQuery.where(except, \"!=\", exceptVal);\n }\n }\n\n if (exceptColumnName !== undefined) {\n dbQuery.where(exceptColumnName, \"!=\", exceptValue);\n }\n\n if (query) {\n await query({\n query: dbQuery,\n value,\n allValues: context.allValues,\n });\n }\n\n const document = await dbQuery.first();\n\n return document ? invalidRule(this, context) : VALID_RULE;\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,MAAa,aAA4C;CACvD,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAClC,MAAM,EACJ,OACA,QACA,SAAS,QAAQ,KACjB,kBACA,aACA,UACE,KAAK,QAAQ;EAIjB,MAAM,UAFqB,kBAAkB,KAEZ,CAAC,CAAC,MAAM;EAEzC,QAAQ,MAAM,QAAQ,KAAK;EAE3B,IAAI,QAAQ;GACV,MAAM,YAAY,IAAI,QAAQ,WAAW,MAAM;GAE/C,IAAI,cAAc,QAChB,QAAQ,MAAM,QAAQ,MAAM,SAAS;EAEzC;EAEA,IAAI,qBAAqB,QACvB,QAAQ,MAAM,kBAAkB,MAAM,WAAW;EAGnD,IAAI,OACF,MAAM,MAAM;GACV,OAAO;GACP;GACA,WAAW,QAAQ;EACrB,CAAC;EAKH,OAAO,MAFgB,QAAQ,MAAM,IAEnB,YAAY,MAAM,OAAO,IAAI;CACjD;AACF"}
1
+ {"version":3,"file":"unique-rule.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/rules/unique-rule.ts"],"sourcesContent":["import { get } from \"@mongez/reinforcements\";\nimport { invalidRule, VALID_RULE, type SchemaRule } from \"@warlock.js/seal\";\nimport { resolveModelClass } from \"../../model/register-model\";\nimport type { UniqueRuleOptions } from \"../types\";\n\n/**\n * Validates that a value is unique against a database column.\n *\n * Supports three exclusion modes:\n * - `except`: read a sibling input's value and exclude rows where that\n * column equals the sibling's value\n * - `exceptColumnName` + `exceptValue`: exclude rows where the named\n * column equals the supplied value (used by request-aware wrappers)\n * - Custom `query` callback for anything more involved\n *\n * @example\n * v.email().unique(\"User\");\n * v.string().unique(\"User\", { except: \"id\" });\n */\nexport const uniqueRule: SchemaRule<UniqueRuleOptions> = {\n name: \"unique\",\n defaultErrorMessage: \"The :input must be unique\",\n async validate(value: any, context) {\n const {\n Model,\n except,\n column = context.key,\n exceptColumnName,\n exceptValue,\n query,\n } = this.context.options;\n\n const ResolvedModelClass = resolveModelClass(Model);\n\n const dbQuery = ResolvedModelClass.query();\n\n dbQuery.where(column, value);\n\n if (except) {\n const exceptVal = get(context.allValues, except);\n\n if (exceptVal !== undefined) {\n dbQuery.where(except, \"!=\", exceptVal);\n }\n }\n\n if (exceptColumnName !== undefined) {\n dbQuery.where(exceptColumnName, \"!=\", exceptValue);\n }\n\n if (query) {\n await query({\n query: dbQuery,\n value,\n allValues: context.allValues,\n });\n }\n\n const document = await dbQuery.first();\n\n return document ? invalidRule(this, context) : VALID_RULE;\n },\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,MAAa,aAA4C;CACvD,MAAM;CACN,qBAAqB;CACrB,MAAM,SAAS,OAAY,SAAS;EAClC,MAAM,EACJ,OACA,QACA,SAAS,QAAQ,KACjB,kBACA,aACA,UACE,KAAK,QAAQ;EAIjB,MAAM,UAFqB,kBAAkB,KAEZ,CAAC,CAAC,MAAM;EAEzC,QAAQ,MAAM,QAAQ,KAAK;EAE3B,IAAI,QAAQ;GACV,MAAM,YAAY,IAAI,QAAQ,WAAW,MAAM;GAE/C,IAAI,cAAc,QAChB,QAAQ,MAAM,QAAQ,MAAM,SAAS;EAEzC;EAEA,IAAI,qBAAqB,QACvB,QAAQ,MAAM,kBAAkB,MAAM,WAAW;EAGnD,IAAI,OACF,MAAM,MAAM;GACV,OAAO;GACP;GACA,WAAW,QAAQ;EACrB,CAAC;EAKH,OAAO,MAFgB,QAAQ,MAAM,IAEnB,YAAY,MAAM,OAAO,IAAI;CACjD;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { Model } from "../../model/model.mjs";
2
2
 
3
- //#region ../@warlock.js/cascade/src/validation/transformers/embed-model-transformer.ts
3
+ //#region ../cascade/src/validation/transformers/embed-model-transformer.ts
4
4
  const databaseModelTransformer = (value, context) => {
5
5
  const embed = context.options.embed ?? "embedData";
6
6
  if (value instanceof Model === false && !Array.isArray(value)) return value;
@@ -1 +1 @@
1
- {"version":3,"file":"embed-model-transformer.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/validation/transformers/embed-model-transformer.ts"],"sourcesContent":["import { type TransformerCallback } from \"@warlock.js/seal\";\nimport { Model } from \"../../model/model\";\n\nexport const databaseModelTransformer: TransformerCallback = (value, context) => {\n const embed = context.options.embed ?? \"embedData\";\n\n if (value instanceof Model === false && !Array.isArray(value)) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => {\n if (typeof embed === \"string\") {\n return item[embed];\n }\n\n return item.only(embed);\n });\n }\n\n if (typeof embed === \"string\") {\n return (value as any)[embed];\n }\n\n return value.only(embed);\n};\n"],"mappings":";;;AAGA,MAAa,4BAAiD,OAAO,YAAY;CAC/E,MAAM,QAAQ,QAAQ,QAAQ,SAAS;CAEvC,IAAI,iBAAiB,UAAU,SAAS,CAAC,MAAM,QAAQ,KAAK,GAC1D,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,SAAS;EACzB,IAAI,OAAO,UAAU,UACnB,OAAO,KAAK;EAGd,OAAO,KAAK,KAAK,KAAK;CACxB,CAAC;CAGH,IAAI,OAAO,UAAU,UACnB,OAAQ,MAAc;CAGxB,OAAO,MAAM,KAAK,KAAK;AACzB"}
1
+ {"version":3,"file":"embed-model-transformer.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/transformers/embed-model-transformer.ts"],"sourcesContent":["import { type TransformerCallback } from \"@warlock.js/seal\";\nimport { Model } from \"../../model/model\";\n\nexport const databaseModelTransformer: TransformerCallback = (value, context) => {\n const embed = context.options.embed ?? \"embedData\";\n\n if (value instanceof Model === false && !Array.isArray(value)) {\n return value;\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => {\n if (typeof embed === \"string\") {\n return item[embed];\n }\n\n return item.only(embed);\n });\n }\n\n if (typeof embed === \"string\") {\n return (value as any)[embed];\n }\n\n return value.only(embed);\n};\n"],"mappings":";;;AAGA,MAAa,4BAAiD,OAAO,YAAY;CAC/E,MAAM,QAAQ,QAAQ,QAAQ,SAAS;CAEvC,IAAI,iBAAiB,UAAU,SAAS,CAAC,MAAM,QAAQ,KAAK,GAC1D,OAAO;CAGT,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,SAAS;EACzB,IAAI,OAAO,UAAU,UACnB,OAAO,KAAK;EAGd,OAAO,KAAK,KAAK,KAAK;CACxB,CAAC;CAGH,IAAI,OAAO,UAAU,UACnB,OAAQ,MAAc;CAGxB,OAAO,MAAM,KAAK,KAAK;AACzB"}
@@ -2,7 +2,7 @@ import { ChildModel } from "../model/model.types.mjs";
2
2
  import { Model } from "../model/model.mjs";
3
3
  import { QueryBuilderContract } from "../contracts/query-builder.contract.mjs";
4
4
 
5
- //#region ../@warlock.js/cascade/src/validation/types.d.ts
5
+ //#region ../cascade/src/validation/types.d.ts
6
6
  /**
7
7
  * Base options shared by all database-backed validation rules.
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/validation/types.ts"],"mappings":";;;;;;;;AASA;;;KAAY,oBAAA;EAEH,iCAAP,KAAA,EAAO,UAAA,CAAW,KAAA,YAML;EAJb,KAAA,IAAS,OAAA;IACP,KAAA,EAAO,oBAAA;IACP,KAAA;IACA,SAAA;EAAA,aACW,OAAA,QAJb;EAMA,MAAA;AAAA;;;;KAMU,qBAAA,GAAwB,oBAAoB;;;AANhD;AAMR;;;;KASY,iBAAA,GAAoB,qBAAqB;EAAzC,6DAEV,MAAA;EAEA,gBAAA,WAJ8B;EAM9B,WAAA;AAAA;;;AAAW;KAMD,iBAAA,GAAoB,oBAAoB"}
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../../../../../../../cascade/src/validation/types.ts"],"mappings":";;;;;;;;AASA;;;KAAY,oBAAA;EAEH,iCAAP,KAAA,EAAO,UAAA,CAAW,KAAA,YAML;EAJb,KAAA,IAAS,OAAA;IACP,KAAA,EAAO,oBAAA;IACP,KAAA;IACA,SAAA;EAAA,aACW,OAAA,QAJb;EAMA,MAAA;AAAA;;;;KAMU,qBAAA,GAAwB,oBAAoB;;;AANhD;AAMR;;;;KASY,iBAAA,GAAoB,qBAAqB;EAAzC,6DAEV,MAAA;EAEA,gBAAA,WAJ8B;EAM9B,WAAA;AAAA;;;AAAW;KAMD,iBAAA,GAAoB,oBAAoB"}
@@ -1,7 +1,7 @@
1
1
  import { ChildModel } from "../../model/model.types.mjs";
2
2
  import { BaseValidator } from "@warlock.js/seal";
3
3
 
4
- //#region ../@warlock.js/cascade/src/validation/validators/embed-validator.d.ts
4
+ //#region ../cascade/src/validation/validators/embed-validator.d.ts
5
5
  declare class EmbedModelValidator extends BaseValidator {
6
6
  /**
7
7
  * Check if value is a string type
@@ -1 +1 @@
1
- {"version":3,"file":"embed-validator.d.mts","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/validation/validators/embed-validator.ts"],"mappings":";;;;cAMa,mBAAA,SAA4B,aAAA;;;AAAzC;EAIS,WAAA,CAAY,KAAA;;;;EAWZ,KAAA,CAAM,KAAA,EAAO,UAAA,gBAA0B,YAAA,YAAwB,mBAAA;EAaC;;;EAAhE,MAAA,CAAO,KAAA,EAAO,UAAA,gBAA0B,YAAA,YAAwB,mBAAA;EA5BnB;;;EA6C7C,KAAA,CAAM,KAAA,uBAA4B,mBAAA;AAAA"}
1
+ {"version":3,"file":"embed-validator.d.mts","names":[],"sources":["../../../../../../../../cascade/src/validation/validators/embed-validator.ts"],"mappings":";;;;cAMa,mBAAA,SAA4B,aAAA;;;AAAzC;EAIS,WAAA,CAAY,KAAA;;;;EAWZ,KAAA,CAAM,KAAA,EAAO,UAAA,gBAA0B,YAAA,YAAwB,mBAAA;EAaC;;;EAAhE,MAAA,CAAO,KAAA,EAAO,UAAA,gBAA0B,YAAA,YAAwB,mBAAA;EA5BnB;;;EA6C7C,KAAA,CAAM,KAAA,uBAA4B,mBAAA;AAAA"}
@@ -4,7 +4,7 @@ import { databaseModelTransformer } from "../transformers/embed-model-transforme
4
4
  import { Model } from "../../model/model.mjs";
5
5
  import { BaseValidator, arrayRule } from "@warlock.js/seal";
6
6
 
7
- //#region ../@warlock.js/cascade/src/validation/validators/embed-validator.ts
7
+ //#region ../cascade/src/validation/validators/embed-validator.ts
8
8
  var EmbedModelValidator = class extends BaseValidator {
9
9
  /**
10
10
  * Check if value is a string type
@@ -1 +1 @@
1
- {"version":3,"file":"embed-validator.mjs","names":[],"sources":["../../../../../../../../@warlock.js/cascade/src/validation/validators/embed-validator.ts"],"sourcesContent":["import { arrayRule, BaseValidator } from \"@warlock.js/seal\";\nimport { Model, type ChildModel } from \"../../model/model\";\nimport { databaseModelMutator, databaseModelsMutator } from \"../mutators/embed-mutator\";\nimport { databaseModelRule, databaseModelsRule } from \"../rules/database-model-rule\";\nimport { databaseModelTransformer } from \"../transformers/embed-model-transformer\";\n\nexport class EmbedModelValidator extends BaseValidator {\n /**\n * Check if value is a string type\n */\n public matchesType(value: any): boolean {\n return (\n value instanceof Model ||\n typeof value === \"number\" ||\n (Array.isArray(value) && value.every((item) => item instanceof Model))\n );\n }\n\n /**\n * Mutate the value to be a model, also fail if the mutated value is not a valid model\n */\n public model(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n this.addMutator(databaseModelMutator, {\n model,\n });\n\n return this.addRule(databaseModelRule, errorMessage, {\n model,\n });\n }\n\n /**\n * Validate the value is a list of models\n */\n public models(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n const instance = this.instance;\n instance.addMutator(databaseModelsMutator, {\n model,\n });\n\n instance.addMutableRule(arrayRule);\n instance.addMutableRule(databaseModelsRule, errorMessage, {\n model,\n });\n\n return instance;\n }\n\n /**\n * Determine how the data will be stored as an embedded document\n */\n public embed(embed?: string | string[]): EmbedModelValidator {\n return this.addTransformer(databaseModelTransformer, {\n embed,\n });\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,sBAAb,cAAyC,cAAc;;;;CAIrD,AAAO,YAAY,OAAqB;EACtC,OACE,iBAAiB,SACjB,OAAO,UAAU,YAChB,MAAM,QAAQ,KAAK,KAAK,MAAM,OAAO,SAAS,gBAAgB,KAAK;CAExE;;;;CAKA,AAAO,MAAM,OAAiC,cAA4C;EACxF,KAAK,WAAW,sBAAsB,EACpC,MACF,CAAC;EAED,OAAO,KAAK,QAAQ,mBAAmB,cAAc,EACnD,MACF,CAAC;CACH;;;;CAKA,AAAO,OAAO,OAAiC,cAA4C;EACzF,MAAM,WAAW,KAAK;EACtB,SAAS,WAAW,uBAAuB,EACzC,MACF,CAAC;EAED,SAAS,eAAe,SAAS;EACjC,SAAS,eAAe,oBAAoB,cAAc,EACxD,MACF,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,MAAM,OAAgD;EAC3D,OAAO,KAAK,eAAe,0BAA0B,EACnD,MACF,CAAC;CACH;AACF"}
1
+ {"version":3,"file":"embed-validator.mjs","names":[],"sources":["../../../../../../../../cascade/src/validation/validators/embed-validator.ts"],"sourcesContent":["import { arrayRule, BaseValidator } from \"@warlock.js/seal\";\nimport { Model, type ChildModel } from \"../../model/model\";\nimport { databaseModelMutator, databaseModelsMutator } from \"../mutators/embed-mutator\";\nimport { databaseModelRule, databaseModelsRule } from \"../rules/database-model-rule\";\nimport { databaseModelTransformer } from \"../transformers/embed-model-transformer\";\n\nexport class EmbedModelValidator extends BaseValidator {\n /**\n * Check if value is a string type\n */\n public matchesType(value: any): boolean {\n return (\n value instanceof Model ||\n typeof value === \"number\" ||\n (Array.isArray(value) && value.every((item) => item instanceof Model))\n );\n }\n\n /**\n * Mutate the value to be a model, also fail if the mutated value is not a valid model\n */\n public model(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n this.addMutator(databaseModelMutator, {\n model,\n });\n\n return this.addRule(databaseModelRule, errorMessage, {\n model,\n });\n }\n\n /**\n * Validate the value is a list of models\n */\n public models(model: ChildModel<any> | string, errorMessage?: string): EmbedModelValidator {\n const instance = this.instance;\n instance.addMutator(databaseModelsMutator, {\n model,\n });\n\n instance.addMutableRule(arrayRule);\n instance.addMutableRule(databaseModelsRule, errorMessage, {\n model,\n });\n\n return instance;\n }\n\n /**\n * Determine how the data will be stored as an embedded document\n */\n public embed(embed?: string | string[]): EmbedModelValidator {\n return this.addTransformer(databaseModelTransformer, {\n embed,\n });\n }\n}\n"],"mappings":";;;;;;;AAMA,IAAa,sBAAb,cAAyC,cAAc;;;;CAIrD,AAAO,YAAY,OAAqB;EACtC,OACE,iBAAiB,SACjB,OAAO,UAAU,YAChB,MAAM,QAAQ,KAAK,KAAK,MAAM,OAAO,SAAS,gBAAgB,KAAK;CAExE;;;;CAKA,AAAO,MAAM,OAAiC,cAA4C;EACxF,KAAK,WAAW,sBAAsB,EACpC,MACF,CAAC;EAED,OAAO,KAAK,QAAQ,mBAAmB,cAAc,EACnD,MACF,CAAC;CACH;;;;CAKA,AAAO,OAAO,OAAiC,cAA4C;EACzF,MAAM,WAAW,KAAK;EACtB,SAAS,WAAW,uBAAuB,EACzC,MACF,CAAC;EAED,SAAS,eAAe,SAAS;EACjC,SAAS,eAAe,oBAAoB,cAAc,EACxD,MACF,CAAC;EAED,OAAO;CACT;;;;CAKA,AAAO,MAAM,OAAgD;EAC3D,OAAO,KAAK,eAAe,0BAA0B,EACnD,MACF,CAAC;CACH;AACF"}
@@ -1,7 +1,7 @@
1
1
  import { Model } from "../model/model.mjs";
2
2
  import { WriterContract, WriterOptions, WriterResult } from "../contracts/database-writer.contract.mjs";
3
3
 
4
- //#region ../@warlock.js/cascade/src/writer/database-writer.d.ts
4
+ //#region ../cascade/src/writer/database-writer.d.ts
5
5
  /**
6
6
  * Database writer service that orchestrates model persistence.
7
7
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database-writer.d.mts","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/writer/database-writer.ts"],"mappings":";;;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,cAAA;EAwW7B;EAAA,iBAtWS,KAAA;EAsaT;EAAA,iBAnaS,IAAA;EAscH;EAAA,iBAncG,UAAA;EAmcQ;EAAA,iBAhcR,MAAA;;mBAGA,KAAA;;mBAGA,UAAA;;mBAGA,MAAA;;mBAGA,UAAA;;;;;;;;;;;;;cAcE,KAAA,EAAO,KAAA;;;;;;;;EAkBb,IAAA,CAAK,OAAA,GAAS,aAAA,GAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;;UAsE1C,eAAA;;;;;;;;UA8FA,aAAA;;;;;;;;UAqDA,aAAA;;;;;;EA2CD,cAAA,IAAkB,OAAA;;;;;;;;;;;;;;;;;;;;;;UA6CvB,qBAAA;;;;;;;;;;;;UAuCA,gBAAA;;;;;;;;;;;;UAyBA,kBAAA;;;;;;;;;UAsBA,SAAA;;;;;;;;;;UAaM,WAAA;AAAA"}
1
+ {"version":3,"file":"database-writer.d.mts","names":[],"sources":["../../../../../../../cascade/src/writer/database-writer.ts"],"mappings":";;;;;;AAsDA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAa,cAAA,YAA0B,cAAA;EAwW7B;EAAA,iBAtWS,KAAA;EAsaT;EAAA,iBAnaS,IAAA;EAscH;EAAA,iBAncG,UAAA;EAmcQ;EAAA,iBAhcR,MAAA;;mBAGA,KAAA;;mBAGA,UAAA;;mBAGA,MAAA;;mBAGA,UAAA;;;;;;;;;;;;;cAcE,KAAA,EAAO,KAAA;;;;;;;;EAkBb,IAAA,CAAK,OAAA,GAAS,aAAA,GAAqB,OAAA,CAAQ,YAAA;;;;;;;;;;;UAsE1C,eAAA;;;;;;;;UA8FA,aAAA;;;;;;;;UAqDA,aAAA;;;;;;EA2CD,cAAA,IAAkB,OAAA;;;;;;;;;;;;;;;;;;;;;;UA6CvB,qBAAA;;;;;;;;;;;;UAuCA,gBAAA;;;;;;;;;;;;UAyBA,kBAAA;;;;;;;;;UAsBA,SAAA;;;;;;;;;;UAaM,WAAA;AAAA"}
@@ -5,7 +5,7 @@ import { when } from "@mongez/reinforcements";
5
5
  import events from "@mongez/events";
6
6
  import { getSealConfig, v } from "@warlock.js/seal";
7
7
 
8
- //#region ../@warlock.js/cascade/src/writer/database-writer.ts
8
+ //#region ../cascade/src/writer/database-writer.ts
9
9
  /**
10
10
  * Database writer service that orchestrates model persistence.
11
11
  *
@@ -1 +1 @@
1
- {"version":3,"file":"database-writer.mjs","names":[],"sources":["../../../../../../../@warlock.js/cascade/src/writer/database-writer.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { when } from \"@mongez/reinforcements\";\nimport { getSealConfig, v, type ObjectValidator } from \"@warlock.js/seal\";\nimport type {\n DriverContract,\n InsertResult,\n UpdateOperations,\n UpdateResult,\n} from \"../contracts/database-driver.contract\";\nimport type {\n WriterContract,\n WriterOptions,\n WriterResult,\n} from \"../contracts/database-writer.contract\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelUpdatedEvent } from \"../sync/model-events\";\nimport type { StrictMode } from \"../types\";\nimport { DatabaseWriterValidationError } from \"../validation\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database writer service that orchestrates model persistence.\n *\n * Handles the complete save pipeline:\n * 1. Check for changes (skip if no changes and not new)\n * 2. Emit `saving` event (for data enrichment)\n * 3. Emit `validating` event\n * 4. Validate and cast data via @warlock.js/seal schema\n * 5. Emit `validated` event\n * 6. Generate ID (for new NoSQL records)\n * 7. Emit `creating`/`updating` events\n * 8. Execute insert or update via driver\n * 9. Merge returned data into model\n * 10. Reset dirty tracker and update `isNew` flag\n * 11. Emit `saved` and `created`/`updated` events\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\", email: \"alice@example.com\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\n *\n * console.log(user.get(\"id\")); // 1 (auto-generated)\n * console.log(user.get(\"_id\")); // ObjectId(\"...\")\n *\n * // Update existing record\n * user.set(\"name\", \"Alice Smith\");\n * await writer.save();\n * // Only updates the \"name\" field (partial update)\n *\n * // Silent save (no events)\n * await writer.save({ skipEvents: true });\n * ```\n */\nexport class DatabaseWriter implements WriterContract {\n /** The model instance being persisted */\n private readonly model: Model;\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver and ID generator */\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 /** Validation schema (if defined) */\n private readonly schema?: ObjectValidator;\n\n /** Strict mode configuration */\n private readonly strictMode: StrictMode;\n\n /**\n * Create a new writer instance for a model.\n *\n * @param model - The model instance to persist\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\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 this.schema = this.ctor.schema;\n this.strictMode = this.ctor.strictMode;\n }\n\n /**\n * Save the model instance to the database.\n *\n * @param options - Save options\n * @returns Result with success status, document, and metadata\n * @throws {ValidationError} If validation fails\n */\n public async save(options: WriterOptions = {}): Promise<WriterResult> {\n const isInsert = this.model.isNew;\n\n // 1. Check if model has changes (skip if no changes and not new)\n if (!isInsert && !this.model.hasChanges()) {\n return {\n success: true,\n document: this.model.data,\n isNew: false,\n modifiedCount: 0,\n };\n }\n\n // 2. Emit saving event (before validation for data enrichment)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saving\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // 3. Validate and cast data\n await this.validateAndCast(isInsert, options);\n\n // 4. Execute insert or update\n let result: InsertResult | UpdateResult;\n\n if (isInsert) {\n result = await this.performInsert(options);\n } else {\n result = await this.performUpdate(options);\n }\n\n // 5. Reset dirty tracker and update isNew flag\n const changedFields = isInsert ? [] : this.model.getDirtyColumns();\n this.model.dirtyTracker.reset();\n this.model.isNew = false;\n\n // 6. Emit post-save events\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saved\");\n await this.model.emitEvent(isInsert ? \"created\" : \"updated\");\n }\n\n // 7. Trigger sync operations (fire-and-forget, non-blocking)\n if (!options.skipSync && !isInsert) {\n void this.triggerSync(changedFields);\n }\n\n return {\n success: true,\n document: this.model.data,\n isNew: isInsert,\n modifiedCount: isInsert\n ? undefined\n : (result as UpdateResult).modifiedCount,\n };\n }\n\n /**\n * Validate and cast model data using the schema.\n *\n * Updates the model's data in-place with validated/casted values.\n *\n * @param isInsert - Whether this is an insert operation\n * @param options - Save options\n * @throws {ValidationError} If validation fails\n * @private\n */\n private async validateAndCast(\n isInsert: boolean,\n options: WriterOptions,\n ): Promise<void> {\n // Emit validating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validating\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // Skip validation if requested or no schema defined\n if (options.skipValidation || !this.schema) {\n return;\n }\n\n // Whitelist the framework-managed system columns so a model carrying them\n // (id / _id / timestamps / soft-delete deletedAt) validates cleanly instead\n // of being stripped (strictMode \"strip\") or rejected (strictMode \"fail\").\n // `when(...)` adds each timestamp/soft-delete column only when configured\n // (truthy) — the lazy factory means a disabled column (`false`) never even\n // builds a bogus schema key.\n //\n // The whitelist must apply to BOTH insert and update: on insert a caller may\n // supply a backdated createdAt (e.g. data migrations / imports), and without\n // the whitelist strictMode \"strip\" silently drops it before it reaches the\n // writer, while \"fail\" rejects the whole insert.\n const systemColumns = {\n id: v.scalar().optional(),\n _id: v.any().optional(),\n ...when(this.ctor.createdAtColumn, () => ({\n [this.ctor.createdAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.updatedAtColumn, () => ({\n [this.ctor.updatedAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.deletedAtColumn, () => ({\n [this.ctor.deletedAtColumn as string]: v.date().optional(),\n })),\n };\n\n // Clone full schema for insert, partial (dirty keys only) for updates.\n const validationSchema = isInsert\n ? this.schema.clone().extend(systemColumns)\n : this.schema.clone(Object.keys(this.model.data)).extend(systemColumns);\n\n // Apply strict mode\n if (this.strictMode === \"strip\") {\n validationSchema.stripUnknown();\n } else if (this.strictMode === \"fail\") {\n validationSchema.allowUnknown(false);\n } else if (this.strictMode === \"allow\") {\n validationSchema.allowUnknown(true);\n }\n\n // Run validation\n const result = await v.validate(validationSchema, this.model.data, {\n context: {\n model: this.model,\n },\n ...getSealConfig(),\n });\n\n if (!result.isValid) {\n console.trace(result.errors);\n\n const error = new DatabaseWriterValidationError(\n `[${this.model.constructor.name} Model] ${isInsert ? \"Insert\" : \"Update\"} Validation failed`,\n result.errors,\n );\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result, error });\n }\n throw error;\n }\n\n // Update model data with validated/casted data\n this.model.replaceData(result.data);\n\n // Emit validated event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result });\n }\n }\n\n /**\n * Perform an insert operation.\n *\n * @param options - Save options\n * @returns Insert result\n * @private\n */\n private async performInsert(options: WriterOptions): Promise<InsertResult> {\n // Generate ID if needed (NoSQL only)\n await this.generateNextId();\n\n // Get data to insert (already validated and casted)\n const dataToInsert = this.model.data;\n\n // Add createdAt and updatedAt to the data (using resolved column names)\n // The column names are already resolved through the hierarchy:\n // Model static property > Database config > Driver defaults > undefined\n //\n // Only stamp createdAt when the caller did NOT supply one, so a backdated\n // value (e.g. data migrations / imports) is honored instead of overwritten.\n // This mirrors the upsert path's guard. updatedAt is always stamped to\n // reflect the moment the record is persisted.\n const createdAtColumn = this.ctor.createdAtColumn;\n\n if (createdAtColumn && dataToInsert[createdAtColumn] == null) {\n dataToInsert[createdAtColumn] = new Date();\n }\n\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n dataToInsert[updatedAtColumn] = new Date();\n }\n\n // Emit creating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"creating\");\n }\n\n // INSERT: use full validated data\n const result = await this.driver.insert(this.table, dataToInsert);\n\n // Merge returned data (e.g., generated _id, timestamps)\n // Note: We use merge here because the result might not include all fields\n // (e.g., our generated 'id' field), and we don't want to lose them\n this.model.merge(result.document as Record<string, unknown>);\n\n // Reset dirty tracker immediately after merge to prevent\n // database-generated fields (like _id) from being marked as dirty\n this.model.dirtyTracker.reset();\n\n return result;\n }\n\n /**\n * Perform an update operation.\n *\n * @param options - Save options\n * @returns Update result\n * @private\n */\n private async performUpdate(options: WriterOptions): Promise<UpdateResult> {\n // Emit updating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"updating\");\n }\n\n // Update the updatedAt timestamp (using resolved column name)\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n this.model.set(updatedAtColumn, new Date());\n }\n\n if (options.replace) {\n const document = await this.driver.replace(\n this.table,\n {\n [this.primaryKey]: this.model.get(this.primaryKey),\n },\n this.model.data,\n );\n\n if (document) {\n this.model.replaceData(document as Record<string, unknown>);\n }\n\n return { modifiedCount: document ? 1 : 0 };\n }\n\n // Build operations from dirty tracker\n const operations = this.buildUpdateOperations();\n\n // Build filter using primary key\n const filter = { [this.primaryKey]: this.model.get(this.primaryKey) };\n\n // Execute update with operations\n return await this.driver.update(this.table, filter, operations);\n }\n\n /**\n * Generate ID for the model if auto-generation is enabled.\n *\n * @private\n */\n public async generateNextId(): Promise<void> {\n if (!this.ctor.autoGenerateId || this.model.get(\"id\")) {\n return;\n }\n\n const idGenerator = this.dataSource.idGenerator;\n if (!idGenerator) {\n return;\n }\n\n // Resolve ID generation options from model configuration\n const initialId = this.resolveInitialId();\n\n const incrementIdBy = this.resolveIncrementBy();\n\n const id = await idGenerator.generateNextId({\n table: this.table,\n initialId,\n incrementIdBy,\n });\n\n this.model.set(\"id\", id);\n }\n\n /**\n * Build update operations from the model's dirty tracker.\n *\n * Handles both modified fields ($set) and removed fields ($unset).\n *\n * @returns Update operations for the driver\n * @private\n *\n * @example\n * ```typescript\n * // Model with changes\n * user.set(\"name\", \"Alice\");\n * user.unset(\"tempField\");\n *\n * const operations = this.buildUpdateOperations();\n * // {\n * // $set: { name: \"Alice\" },\n * // $unset: { tempField: 1 }\n * // }\n * ```\n */\n private buildUpdateOperations(): UpdateOperations {\n const operations: UpdateOperations = {};\n\n // Get dirty columns (modified fields)\n const dirtyColumns = this.model.getDirtyColumns();\n\n if (dirtyColumns.length > 0) {\n operations.$set = {};\n for (const column of dirtyColumns) {\n const value = this.model.get(column);\n if (value === undefined) continue;\n\n operations.$set[column] = this.model.get(column);\n }\n }\n\n // Get removed columns\n const removedColumns = this.model.getRemovedColumns();\n if (removedColumns.length > 0) {\n operations.$unset = {};\n for (const column of removedColumns) {\n operations.$unset[column] = 1;\n }\n }\n\n return operations;\n }\n\n /**\n * Resolve the initial ID from model configuration.\n *\n * Priority:\n * 1. Model.initialId (explicit value)\n * 2. Model.randomInitialId (random or function)\n * 3. Default: 1\n *\n * @returns The initial ID value\n * @private\n */\n private resolveInitialId(): number {\n if (this.ctor.initialId) {\n return this.ctor.initialId;\n }\n\n if (this.ctor.randomInitialId) {\n return typeof this.ctor.randomInitialId === \"function\"\n ? this.ctor.randomInitialId()\n : this.randomInt(10000, 499999);\n }\n\n return 1; // Default initial ID\n }\n\n /**\n * Resolve the increment value from model configuration.\n *\n * Priority:\n * 1. Model.incrementIdBy (explicit value)\n * 2. Model.randomIncrement (random or function)\n * 3. Default: 1\n *\n * @returns The increment value\n * @private\n */\n private resolveIncrementBy(): number {\n if (this.ctor.incrementIdBy) {\n return this.ctor.incrementIdBy;\n }\n\n if (this.ctor.randomIncrement) {\n return typeof this.ctor.randomIncrement === \"function\"\n ? this.ctor.randomIncrement()\n : this.randomInt(1, 10);\n }\n\n return 1; // Default increment\n }\n\n /**\n * Generate a random integer between min and max (inclusive).\n *\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random integer\n * @private\n */\n private randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n }\n\n /**\n * Trigger sync operations after successful save.\n *\n * Emits a model.updated event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @param changedFields - Fields that were changed (for filtering)\n * @private\n */\n private async triggerSync(changedFields: string[]): Promise<void> {\n // Emit model.updated event - ModelSyncOperation listens to these\n await events.triggerAll(\n getModelUpdatedEvent(this.ctor),\n this.model,\n changedFields,\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"database-writer.mjs","names":[],"sources":["../../../../../../../cascade/src/writer/database-writer.ts"],"sourcesContent":["import events from \"@mongez/events\";\nimport { when } from \"@mongez/reinforcements\";\nimport { getSealConfig, v, type ObjectValidator } from \"@warlock.js/seal\";\nimport type {\n DriverContract,\n InsertResult,\n UpdateOperations,\n UpdateResult,\n} from \"../contracts/database-driver.contract\";\nimport type {\n WriterContract,\n WriterOptions,\n WriterResult,\n} from \"../contracts/database-writer.contract\";\nimport type { ChildModel, Model } from \"../model/model\";\nimport { getModelUpdatedEvent } from \"../sync/model-events\";\nimport type { StrictMode } from \"../types\";\nimport { DatabaseWriterValidationError } from \"../validation\";\nimport type { DataSource } from \"./../data-source/data-source\";\n\n/**\n * Database writer service that orchestrates model persistence.\n *\n * Handles the complete save pipeline:\n * 1. Check for changes (skip if no changes and not new)\n * 2. Emit `saving` event (for data enrichment)\n * 3. Emit `validating` event\n * 4. Validate and cast data via @warlock.js/seal schema\n * 5. Emit `validated` event\n * 6. Generate ID (for new NoSQL records)\n * 7. Emit `creating`/`updating` events\n * 8. Execute insert or update via driver\n * 9. Merge returned data into model\n * 10. Reset dirty tracker and update `isNew` flag\n * 11. Emit `saved` and `created`/`updated` events\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\", email: \"alice@example.com\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\n *\n * console.log(user.get(\"id\")); // 1 (auto-generated)\n * console.log(user.get(\"_id\")); // ObjectId(\"...\")\n *\n * // Update existing record\n * user.set(\"name\", \"Alice Smith\");\n * await writer.save();\n * // Only updates the \"name\" field (partial update)\n *\n * // Silent save (no events)\n * await writer.save({ skipEvents: true });\n * ```\n */\nexport class DatabaseWriter implements WriterContract {\n /** The model instance being persisted */\n private readonly model: Model;\n\n /** Model constructor reference */\n private readonly ctor: ChildModel<Model>;\n\n /** Data source containing driver and ID generator */\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 /** Validation schema (if defined) */\n private readonly schema?: ObjectValidator;\n\n /** Strict mode configuration */\n private readonly strictMode: StrictMode;\n\n /**\n * Create a new writer instance for a model.\n *\n * @param model - The model instance to persist\n *\n * @example\n * ```typescript\n * const user = new User({ name: \"Alice\" });\n * const writer = new DatabaseWriter(user);\n * await writer.save();\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 this.schema = this.ctor.schema;\n this.strictMode = this.ctor.strictMode;\n }\n\n /**\n * Save the model instance to the database.\n *\n * @param options - Save options\n * @returns Result with success status, document, and metadata\n * @throws {ValidationError} If validation fails\n */\n public async save(options: WriterOptions = {}): Promise<WriterResult> {\n const isInsert = this.model.isNew;\n\n // 1. Check if model has changes (skip if no changes and not new)\n if (!isInsert && !this.model.hasChanges()) {\n return {\n success: true,\n document: this.model.data,\n isNew: false,\n modifiedCount: 0,\n };\n }\n\n // 2. Emit saving event (before validation for data enrichment)\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saving\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // 3. Validate and cast data\n await this.validateAndCast(isInsert, options);\n\n // 4. Execute insert or update\n let result: InsertResult | UpdateResult;\n\n if (isInsert) {\n result = await this.performInsert(options);\n } else {\n result = await this.performUpdate(options);\n }\n\n // 5. Reset dirty tracker and update isNew flag\n const changedFields = isInsert ? [] : this.model.getDirtyColumns();\n this.model.dirtyTracker.reset();\n this.model.isNew = false;\n\n // 6. Emit post-save events\n if (!options.skipEvents) {\n await this.model.emitEvent(\"saved\");\n await this.model.emitEvent(isInsert ? \"created\" : \"updated\");\n }\n\n // 7. Trigger sync operations (fire-and-forget, non-blocking)\n if (!options.skipSync && !isInsert) {\n void this.triggerSync(changedFields);\n }\n\n return {\n success: true,\n document: this.model.data,\n isNew: isInsert,\n modifiedCount: isInsert\n ? undefined\n : (result as UpdateResult).modifiedCount,\n };\n }\n\n /**\n * Validate and cast model data using the schema.\n *\n * Updates the model's data in-place with validated/casted values.\n *\n * @param isInsert - Whether this is an insert operation\n * @param options - Save options\n * @throws {ValidationError} If validation fails\n * @private\n */\n private async validateAndCast(\n isInsert: boolean,\n options: WriterOptions,\n ): Promise<void> {\n // Emit validating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validating\", {\n isInsert,\n options,\n mode: isInsert ? \"insert\" : \"update\",\n });\n }\n\n // Skip validation if requested or no schema defined\n if (options.skipValidation || !this.schema) {\n return;\n }\n\n // Whitelist the framework-managed system columns so a model carrying them\n // (id / _id / timestamps / soft-delete deletedAt) validates cleanly instead\n // of being stripped (strictMode \"strip\") or rejected (strictMode \"fail\").\n // `when(...)` adds each timestamp/soft-delete column only when configured\n // (truthy) — the lazy factory means a disabled column (`false`) never even\n // builds a bogus schema key.\n //\n // The whitelist must apply to BOTH insert and update: on insert a caller may\n // supply a backdated createdAt (e.g. data migrations / imports), and without\n // the whitelist strictMode \"strip\" silently drops it before it reaches the\n // writer, while \"fail\" rejects the whole insert.\n const systemColumns = {\n id: v.scalar().optional(),\n _id: v.any().optional(),\n ...when(this.ctor.createdAtColumn, () => ({\n [this.ctor.createdAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.updatedAtColumn, () => ({\n [this.ctor.updatedAtColumn as string]: v.date().optional(),\n })),\n ...when(this.ctor.deletedAtColumn, () => ({\n [this.ctor.deletedAtColumn as string]: v.date().optional(),\n })),\n };\n\n // Clone full schema for insert, partial (dirty keys only) for updates.\n const validationSchema = isInsert\n ? this.schema.clone().extend(systemColumns)\n : this.schema.clone(Object.keys(this.model.data)).extend(systemColumns);\n\n // Apply strict mode\n if (this.strictMode === \"strip\") {\n validationSchema.stripUnknown();\n } else if (this.strictMode === \"fail\") {\n validationSchema.allowUnknown(false);\n } else if (this.strictMode === \"allow\") {\n validationSchema.allowUnknown(true);\n }\n\n // Run validation\n const result = await v.validate(validationSchema, this.model.data, {\n context: {\n model: this.model,\n },\n ...getSealConfig(),\n });\n\n if (!result.isValid) {\n console.trace(result.errors);\n\n const error = new DatabaseWriterValidationError(\n `[${this.model.constructor.name} Model] ${isInsert ? \"Insert\" : \"Update\"} Validation failed`,\n result.errors,\n );\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result, error });\n }\n throw error;\n }\n\n // Update model data with validated/casted data\n this.model.replaceData(result.data);\n\n // Emit validated event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"validated\", { result });\n }\n }\n\n /**\n * Perform an insert operation.\n *\n * @param options - Save options\n * @returns Insert result\n * @private\n */\n private async performInsert(options: WriterOptions): Promise<InsertResult> {\n // Generate ID if needed (NoSQL only)\n await this.generateNextId();\n\n // Get data to insert (already validated and casted)\n const dataToInsert = this.model.data;\n\n // Add createdAt and updatedAt to the data (using resolved column names)\n // The column names are already resolved through the hierarchy:\n // Model static property > Database config > Driver defaults > undefined\n //\n // Only stamp createdAt when the caller did NOT supply one, so a backdated\n // value (e.g. data migrations / imports) is honored instead of overwritten.\n // This mirrors the upsert path's guard. updatedAt is always stamped to\n // reflect the moment the record is persisted.\n const createdAtColumn = this.ctor.createdAtColumn;\n\n if (createdAtColumn && dataToInsert[createdAtColumn] == null) {\n dataToInsert[createdAtColumn] = new Date();\n }\n\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n dataToInsert[updatedAtColumn] = new Date();\n }\n\n // Emit creating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"creating\");\n }\n\n // INSERT: use full validated data\n const result = await this.driver.insert(this.table, dataToInsert);\n\n // Merge returned data (e.g., generated _id, timestamps)\n // Note: We use merge here because the result might not include all fields\n // (e.g., our generated 'id' field), and we don't want to lose them\n this.model.merge(result.document as Record<string, unknown>);\n\n // Reset dirty tracker immediately after merge to prevent\n // database-generated fields (like _id) from being marked as dirty\n this.model.dirtyTracker.reset();\n\n return result;\n }\n\n /**\n * Perform an update operation.\n *\n * @param options - Save options\n * @returns Update result\n * @private\n */\n private async performUpdate(options: WriterOptions): Promise<UpdateResult> {\n // Emit updating event\n if (!options.skipEvents) {\n await this.model.emitEvent(\"updating\");\n }\n\n // Update the updatedAt timestamp (using resolved column name)\n const updatedAtColumn = this.ctor.updatedAtColumn;\n if (updatedAtColumn) {\n this.model.set(updatedAtColumn, new Date());\n }\n\n if (options.replace) {\n const document = await this.driver.replace(\n this.table,\n {\n [this.primaryKey]: this.model.get(this.primaryKey),\n },\n this.model.data,\n );\n\n if (document) {\n this.model.replaceData(document as Record<string, unknown>);\n }\n\n return { modifiedCount: document ? 1 : 0 };\n }\n\n // Build operations from dirty tracker\n const operations = this.buildUpdateOperations();\n\n // Build filter using primary key\n const filter = { [this.primaryKey]: this.model.get(this.primaryKey) };\n\n // Execute update with operations\n return await this.driver.update(this.table, filter, operations);\n }\n\n /**\n * Generate ID for the model if auto-generation is enabled.\n *\n * @private\n */\n public async generateNextId(): Promise<void> {\n if (!this.ctor.autoGenerateId || this.model.get(\"id\")) {\n return;\n }\n\n const idGenerator = this.dataSource.idGenerator;\n if (!idGenerator) {\n return;\n }\n\n // Resolve ID generation options from model configuration\n const initialId = this.resolveInitialId();\n\n const incrementIdBy = this.resolveIncrementBy();\n\n const id = await idGenerator.generateNextId({\n table: this.table,\n initialId,\n incrementIdBy,\n });\n\n this.model.set(\"id\", id);\n }\n\n /**\n * Build update operations from the model's dirty tracker.\n *\n * Handles both modified fields ($set) and removed fields ($unset).\n *\n * @returns Update operations for the driver\n * @private\n *\n * @example\n * ```typescript\n * // Model with changes\n * user.set(\"name\", \"Alice\");\n * user.unset(\"tempField\");\n *\n * const operations = this.buildUpdateOperations();\n * // {\n * // $set: { name: \"Alice\" },\n * // $unset: { tempField: 1 }\n * // }\n * ```\n */\n private buildUpdateOperations(): UpdateOperations {\n const operations: UpdateOperations = {};\n\n // Get dirty columns (modified fields)\n const dirtyColumns = this.model.getDirtyColumns();\n\n if (dirtyColumns.length > 0) {\n operations.$set = {};\n for (const column of dirtyColumns) {\n const value = this.model.get(column);\n if (value === undefined) continue;\n\n operations.$set[column] = this.model.get(column);\n }\n }\n\n // Get removed columns\n const removedColumns = this.model.getRemovedColumns();\n if (removedColumns.length > 0) {\n operations.$unset = {};\n for (const column of removedColumns) {\n operations.$unset[column] = 1;\n }\n }\n\n return operations;\n }\n\n /**\n * Resolve the initial ID from model configuration.\n *\n * Priority:\n * 1. Model.initialId (explicit value)\n * 2. Model.randomInitialId (random or function)\n * 3. Default: 1\n *\n * @returns The initial ID value\n * @private\n */\n private resolveInitialId(): number {\n if (this.ctor.initialId) {\n return this.ctor.initialId;\n }\n\n if (this.ctor.randomInitialId) {\n return typeof this.ctor.randomInitialId === \"function\"\n ? this.ctor.randomInitialId()\n : this.randomInt(10000, 499999);\n }\n\n return 1; // Default initial ID\n }\n\n /**\n * Resolve the increment value from model configuration.\n *\n * Priority:\n * 1. Model.incrementIdBy (explicit value)\n * 2. Model.randomIncrement (random or function)\n * 3. Default: 1\n *\n * @returns The increment value\n * @private\n */\n private resolveIncrementBy(): number {\n if (this.ctor.incrementIdBy) {\n return this.ctor.incrementIdBy;\n }\n\n if (this.ctor.randomIncrement) {\n return typeof this.ctor.randomIncrement === \"function\"\n ? this.ctor.randomIncrement()\n : this.randomInt(1, 10);\n }\n\n return 1; // Default increment\n }\n\n /**\n * Generate a random integer between min and max (inclusive).\n *\n * @param min - Minimum value\n * @param max - Maximum value\n * @returns Random integer\n * @private\n */\n private randomInt(min: number, max: number): number {\n return Math.floor(Math.random() * (max - min + 1)) + min;\n }\n\n /**\n * Trigger sync operations after successful save.\n *\n * Emits a model.updated event that ModelSyncOperation listens to.\n * The sync is handled by registered sync operations, not directly here.\n *\n * @param changedFields - Fields that were changed (for filtering)\n * @private\n */\n private async triggerSync(changedFields: string[]): Promise<void> {\n // Emit model.updated event - ModelSyncOperation listens to these\n await events.triggerAll(\n getModelUpdatedEvent(this.ctor),\n this.model,\n changedFields,\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,IAAa,iBAAb,MAAsD;;CAEpD,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;CAGjB,AAAiB;;;;;;;;;;;;;CAcjB,AAAO,YAAY,OAAc;EAC/B,KAAK,QAAQ;EACb,KAAK,OAAO,MAAM;EAClB,KAAK,aAAa,KAAK,KAAK,cAAc;EAC1C,KAAK,SAAS,KAAK,WAAW;EAC9B,KAAK,QAAQ,KAAK,KAAK;EACvB,KAAK,aAAa,KAAK,KAAK;EAC5B,KAAK,SAAS,KAAK,KAAK;EACxB,KAAK,aAAa,KAAK,KAAK;CAC9B;;;;;;;;CASA,MAAa,KAAK,UAAyB,CAAC,GAA0B;EACpE,MAAM,WAAW,KAAK,MAAM;EAG5B,IAAI,CAAC,YAAY,CAAC,KAAK,MAAM,WAAW,GACtC,OAAO;GACL,SAAS;GACT,UAAU,KAAK,MAAM;GACrB,OAAO;GACP,eAAe;EACjB;EAIF,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,UAAU;GACnC;GACA;GACA,MAAM,WAAW,WAAW;EAC9B,CAAC;EAIH,MAAM,KAAK,gBAAgB,UAAU,OAAO;EAG5C,IAAI;EAEJ,IAAI,UACF,SAAS,MAAM,KAAK,cAAc,OAAO;OAEzC,SAAS,MAAM,KAAK,cAAc,OAAO;EAI3C,MAAM,gBAAgB,WAAW,CAAC,IAAI,KAAK,MAAM,gBAAgB;EACjE,KAAK,MAAM,aAAa,MAAM;EAC9B,KAAK,MAAM,QAAQ;EAGnB,IAAI,CAAC,QAAQ,YAAY;GACvB,MAAM,KAAK,MAAM,UAAU,OAAO;GAClC,MAAM,KAAK,MAAM,UAAU,WAAW,YAAY,SAAS;EAC7D;EAGA,IAAI,CAAC,QAAQ,YAAY,CAAC,UACxB,AAAK,KAAK,YAAY,aAAa;EAGrC,OAAO;GACL,SAAS;GACT,UAAU,KAAK,MAAM;GACrB,OAAO;GACP,eAAe,WACX,SACC,OAAwB;EAC/B;CACF;;;;;;;;;;;CAYA,MAAc,gBACZ,UACA,SACe;EAEf,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,cAAc;GACvC;GACA;GACA,MAAM,WAAW,WAAW;EAC9B,CAAC;EAIH,IAAI,QAAQ,kBAAkB,CAAC,KAAK,QAClC;EAcF,MAAM,gBAAgB;GACpB,IAAI,EAAE,OAAO,CAAC,CAAC,SAAS;GACxB,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS;GACtB,GAAG,KAAK,KAAK,KAAK,wBAAwB,GACvC,KAAK,KAAK,kBAA4B,EAAE,KAAK,CAAC,CAAC,SAAS,EAC3D,EAAE;GACF,GAAG,KAAK,KAAK,KAAK,wBAAwB,GACvC,KAAK,KAAK,kBAA4B,EAAE,KAAK,CAAC,CAAC,SAAS,EAC3D,EAAE;GACF,GAAG,KAAK,KAAK,KAAK,wBAAwB,GACvC,KAAK,KAAK,kBAA4B,EAAE,KAAK,CAAC,CAAC,SAAS,EAC3D,EAAE;EACJ;EAGA,MAAM,mBAAmB,WACrB,KAAK,OAAO,MAAM,CAAC,CAAC,OAAO,aAAa,IACxC,KAAK,OAAO,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI,CAAC,CAAC,CAAC,OAAO,aAAa;EAGxE,IAAI,KAAK,eAAe,SACtB,iBAAiB,aAAa;OACzB,IAAI,KAAK,eAAe,QAC7B,iBAAiB,aAAa,KAAK;OAC9B,IAAI,KAAK,eAAe,SAC7B,iBAAiB,aAAa,IAAI;EAIpC,MAAM,SAAS,MAAM,EAAE,SAAS,kBAAkB,KAAK,MAAM,MAAM;GACjE,SAAS,EACP,OAAO,KAAK,MACd;GACA,GAAG,cAAc;EACnB,CAAC;EAED,IAAI,CAAC,OAAO,SAAS;GACnB,QAAQ,MAAM,OAAO,MAAM;GAE3B,MAAM,QAAQ,IAAI,8BAChB,IAAI,KAAK,MAAM,YAAY,KAAK,UAAU,WAAW,WAAW,SAAS,qBACzE,OAAO,MACT;GACA,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,aAAa;IAAE;IAAQ;GAAM,CAAC;GAE3D,MAAM;EACR;EAGA,KAAK,MAAM,YAAY,OAAO,IAAI;EAGlC,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,aAAa,EAAE,OAAO,CAAC;CAEtD;;;;;;;;CASA,MAAc,cAAc,SAA+C;EAEzE,MAAM,KAAK,eAAe;EAG1B,MAAM,eAAe,KAAK,MAAM;EAUhC,MAAM,kBAAkB,KAAK,KAAK;EAElC,IAAI,mBAAmB,aAAa,oBAAoB,MACtD,aAAa,mCAAmB,IAAI,KAAK;EAG3C,MAAM,kBAAkB,KAAK,KAAK;EAClC,IAAI,iBACF,aAAa,mCAAmB,IAAI,KAAK;EAI3C,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,UAAU;EAIvC,MAAM,SAAS,MAAM,KAAK,OAAO,OAAO,KAAK,OAAO,YAAY;EAKhE,KAAK,MAAM,MAAM,OAAO,QAAmC;EAI3D,KAAK,MAAM,aAAa,MAAM;EAE9B,OAAO;CACT;;;;;;;;CASA,MAAc,cAAc,SAA+C;EAEzE,IAAI,CAAC,QAAQ,YACX,MAAM,KAAK,MAAM,UAAU,UAAU;EAIvC,MAAM,kBAAkB,KAAK,KAAK;EAClC,IAAI,iBACF,KAAK,MAAM,IAAI,iCAAiB,IAAI,KAAK,CAAC;EAG5C,IAAI,QAAQ,SAAS;GACnB,MAAM,WAAW,MAAM,KAAK,OAAO,QACjC,KAAK,OACL,GACG,KAAK,aAAa,KAAK,MAAM,IAAI,KAAK,UAAU,EACnD,GACA,KAAK,MAAM,IACb;GAEA,IAAI,UACF,KAAK,MAAM,YAAY,QAAmC;GAG5D,OAAO,EAAE,eAAe,WAAW,IAAI,EAAE;EAC3C;EAGA,MAAM,aAAa,KAAK,sBAAsB;EAG9C,MAAM,SAAS,GAAG,KAAK,aAAa,KAAK,MAAM,IAAI,KAAK,UAAU,EAAE;EAGpE,OAAO,MAAM,KAAK,OAAO,OAAO,KAAK,OAAO,QAAQ,UAAU;CAChE;;;;;;CAOA,MAAa,iBAAgC;EAC3C,IAAI,CAAC,KAAK,KAAK,kBAAkB,KAAK,MAAM,IAAI,IAAI,GAClD;EAGF,MAAM,cAAc,KAAK,WAAW;EACpC,IAAI,CAAC,aACH;EAIF,MAAM,YAAY,KAAK,iBAAiB;EAExC,MAAM,gBAAgB,KAAK,mBAAmB;EAE9C,MAAM,KAAK,MAAM,YAAY,eAAe;GAC1C,OAAO,KAAK;GACZ;GACA;EACF,CAAC;EAED,KAAK,MAAM,IAAI,MAAM,EAAE;CACzB;;;;;;;;;;;;;;;;;;;;;;CAuBA,AAAQ,wBAA0C;EAChD,MAAM,aAA+B,CAAC;EAGtC,MAAM,eAAe,KAAK,MAAM,gBAAgB;EAEhD,IAAI,aAAa,SAAS,GAAG;GAC3B,WAAW,OAAO,CAAC;GACnB,KAAK,MAAM,UAAU,cAAc;IAEjC,IADc,KAAK,MAAM,IAAI,MACrB,MAAM,QAAW;IAEzB,WAAW,KAAK,UAAU,KAAK,MAAM,IAAI,MAAM;GACjD;EACF;EAGA,MAAM,iBAAiB,KAAK,MAAM,kBAAkB;EACpD,IAAI,eAAe,SAAS,GAAG;GAC7B,WAAW,SAAS,CAAC;GACrB,KAAK,MAAM,UAAU,gBACnB,WAAW,OAAO,UAAU;EAEhC;EAEA,OAAO;CACT;;;;;;;;;;;;CAaA,AAAQ,mBAA2B;EACjC,IAAI,KAAK,KAAK,WACZ,OAAO,KAAK,KAAK;EAGnB,IAAI,KAAK,KAAK,iBACZ,OAAO,OAAO,KAAK,KAAK,oBAAoB,aACxC,KAAK,KAAK,gBAAgB,IAC1B,KAAK,UAAU,KAAO,MAAM;EAGlC,OAAO;CACT;;;;;;;;;;;;CAaA,AAAQ,qBAA6B;EACnC,IAAI,KAAK,KAAK,eACZ,OAAO,KAAK,KAAK;EAGnB,IAAI,KAAK,KAAK,iBACZ,OAAO,OAAO,KAAK,KAAK,oBAAoB,aACxC,KAAK,KAAK,gBAAgB,IAC1B,KAAK,UAAU,GAAG,EAAE;EAG1B,OAAO;CACT;;;;;;;;;CAUA,AAAQ,UAAU,KAAa,KAAqB;EAClD,OAAO,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM,MAAM,EAAE,IAAI;CACvD;;;;;;;;;;CAWA,MAAc,YAAY,eAAwC;EAEhE,MAAM,OAAO,WACX,qBAAqB,KAAK,IAAI,GAC9B,KAAK,OACL,aACF;CACF;AACF"}
package/llms-full.txt CHANGED
@@ -2556,6 +2556,24 @@ for (const [field, { oldValue, newValue }] of Object.entries(dirty)) {
2556
2556
  }
2557
2557
  ```
2558
2558
 
2559
+ ## How a merge decides what changed
2560
+
2561
+ `.merge()` deep-merges **plain objects only**. Every other value — primitives, arrays, and class instances such as `Date`, `Map`, `Set`, `RegExp` — **replaces** the target outright.
2562
+
2563
+ ```ts
2564
+ // plain object → merged key-by-key, siblings survive
2565
+ user.merge({ profile: { age: 26 } }); // profile.city untouched
2566
+ user.getDirtyColumns(); // ["profile.age"]
2567
+
2568
+ // Date → replaced, not merged
2569
+ conversation.merge({ lastOutboundAt: new Date() });
2570
+ conversation.getDirtyColumns(); // ["lastOutboundAt"]
2571
+ ```
2572
+
2573
+ :::note[Fixed in 4.9.1]
2574
+ Before 4.9.1 a `Date` merged over a column that **already held a `Date`** was silently dropped: the merge treated it as a mergeable container and recursed into it, and a `Date` has no own enumerable properties, so nothing was copied. The column never went dirty and `save()` returned `{ success: true, modifiedCount: 0 }` with no `UPDATE` issued. Writing into an empty column always worked, so only overwrites were affected. If you are on 4.9.0 or earlier, upgrade.
2575
+ :::
2576
+
2559
2577
  ## Things NOT to do
2560
2578
 
2561
2579
  - Don't use `hasChanges()` / `isDirty()` after `save()` to verify the save persisted. The tracker resets to clean on save — these become false regardless. Read back from the DB if you need verification.
@@ -2636,6 +2654,20 @@ yarn cascade migrate:export-sql # write .up.sql / .down.sql instead of executing
2636
2654
 
2637
2655
  Name files with a timestamp prefix (`MM-DD-YYYY_HH-MM-SS-<name>.migration.ts`) so Cascade orders runs deterministically and infers the migration name from the filename. A timestamp prefix prevents the "two devs picked the same number" merge conflict.
2638
2656
 
2657
+ ### How ordering actually works
2658
+
2659
+ Every pending migration's SQL is pooled and sorted **globally**, by three keys in order:
2660
+
2661
+ 1. **Phase** — the dependency-safe DDL order, derived from the statement itself: extensions/types (1), `CREATE TABLE` (2), `ADD COLUMN` (3), foreign keys (4), drops/alters (5). A `CREATE TABLE` from a newer migration therefore still runs before an older migration's `ALTER`.
2662
+ 2. **`createdAt`** — parsed from the filename prefix.
2663
+ 3. **Migration name** — alphabetical, only when the timestamps can't decide.
2664
+
2665
+ Because phase wins, cross-migration ordering matters most for statements that share a phase — two migrations both altering the same table, for instance.
2666
+
2667
+ :::caution[`MM-DD` vs `DD-MM` is a guess]
2668
+ The parser tells the two apart by whether the first field is greater than 12 — a month can't be. When both fields are ≤ 12 the stamp is genuinely ambiguous and it assumes `MM-DD`. Two migrations generated on `01-08` and `08-01` of the same year sort by that assumption rather than by fact. If exact ordering between two such files matters, make it unambiguous — a differing hour is enough.
2669
+ :::
2670
+
2639
2671
  ## Column helpers — the building blocks
2640
2672
 
2641
2673
  Every column in a `Migration.create` / `Migration.alter` map starts with a helper imported from `@warlock.js/cascade`; each returns a builder you chain modifiers onto (`.notNullable()`, `.nullable()`, `.unique()`, `.default(value)`, `.index()`, `.primary()`, `.references(table)`):
package/package.json CHANGED
@@ -27,9 +27,9 @@
27
27
  "@mongez/events": "^2.2.6",
28
28
  "@mongez/reinforcements": "^3.3.0",
29
29
  "@mongez/supportive-is": "^2.1.3",
30
- "@warlock.js/context": "4.8.2",
31
- "@warlock.js/logger": "4.8.2",
32
- "@warlock.js/seal": "4.8.2",
30
+ "@warlock.js/context": "4.9.1",
31
+ "@warlock.js/logger": "4.9.1",
32
+ "@warlock.js/seal": "4.9.1",
33
33
  "citty": "^0.2.2",
34
34
  "fast-glob": "^3.3.3"
35
35
  },
@@ -40,7 +40,7 @@
40
40
  "bin": {
41
41
  "cascade": "bin/cascade.js"
42
42
  },
43
- "version": "4.8.2",
43
+ "version": "4.9.1",
44
44
  "main": "./cjs/index.cjs",
45
45
  "module": "./esm/index.mjs",
46
46
  "types": "./esm/index.d.mts",
@@ -97,6 +97,24 @@ for (const [field, { oldValue, newValue }] of Object.entries(dirty)) {
97
97
  }
98
98
  ```
99
99
 
100
+ ## How a merge decides what changed
101
+
102
+ `.merge()` deep-merges **plain objects only**. Every other value — primitives, arrays, and class instances such as `Date`, `Map`, `Set`, `RegExp` — **replaces** the target outright.
103
+
104
+ ```ts
105
+ // plain object → merged key-by-key, siblings survive
106
+ user.merge({ profile: { age: 26 } }); // profile.city untouched
107
+ user.getDirtyColumns(); // ["profile.age"]
108
+
109
+ // Date → replaced, not merged
110
+ conversation.merge({ lastOutboundAt: new Date() });
111
+ conversation.getDirtyColumns(); // ["lastOutboundAt"]
112
+ ```
113
+
114
+ :::note[Fixed in 4.9.1]
115
+ Before 4.9.1 a `Date` merged over a column that **already held a `Date`** was silently dropped: the merge treated it as a mergeable container and recursed into it, and a `Date` has no own enumerable properties, so nothing was copied. The column never went dirty and `save()` returned `{ success: true, modifiedCount: 0 }` with no `UPDATE` issued. Writing into an empty column always worked, so only overwrites were affected. If you are on 4.9.0 or earlier, upgrade.
116
+ :::
117
+
100
118
  ## Things NOT to do
101
119
 
102
120
  - Don't use `hasChanges()` / `isDirty()` after `save()` to verify the save persisted. The tracker resets to clean on save — these become false regardless. Read back from the DB if you need verification.
@@ -64,6 +64,20 @@ yarn cascade migrate:export-sql # write .up.sql / .down.sql instead of executing
64
64
 
65
65
  Name files with a timestamp prefix (`MM-DD-YYYY_HH-MM-SS-<name>.migration.ts`) so Cascade orders runs deterministically and infers the migration name from the filename. A timestamp prefix prevents the "two devs picked the same number" merge conflict.
66
66
 
67
+ ### How ordering actually works
68
+
69
+ Every pending migration's SQL is pooled and sorted **globally**, by three keys in order:
70
+
71
+ 1. **Phase** — the dependency-safe DDL order, derived from the statement itself: extensions/types (1), `CREATE TABLE` (2), `ADD COLUMN` (3), foreign keys (4), drops/alters (5). A `CREATE TABLE` from a newer migration therefore still runs before an older migration's `ALTER`.
72
+ 2. **`createdAt`** — parsed from the filename prefix.
73
+ 3. **Migration name** — alphabetical, only when the timestamps can't decide.
74
+
75
+ Because phase wins, cross-migration ordering matters most for statements that share a phase — two migrations both altering the same table, for instance.
76
+
77
+ :::caution[`MM-DD` vs `DD-MM` is a guess]
78
+ The parser tells the two apart by whether the first field is greater than 12 — a month can't be. When both fields are ≤ 12 the stamp is genuinely ambiguous and it assumes `MM-DD`. Two migrations generated on `01-08` and `08-01` of the same year sort by that assumption rather than by fact. If exact ordering between two such files matters, make it unambiguous — a differing hour is enough.
79
+ :::
80
+
67
81
  ## Column helpers — the building blocks
68
82
 
69
83
  Every column in a `Migration.create` / `Migration.alter` map starts with a helper imported from `@warlock.js/cascade`; each returns a builder you chain modifiers onto (`.notNullable()`, `.nullable()`, `.unique()`, `.default(value)`, `.index()`, `.primary()`, `.references(table)`):