@warlock.js/cascade 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +1 -5
  3. package/cjs/index.cjs +104 -86
  4. package/cjs/index.cjs.map +1 -1
  5. package/esm/context/database-data-source-context.d.mts +2 -3
  6. package/esm/context/database-data-source-context.d.mts.map +1 -1
  7. package/esm/context/database-transaction-context.d.mts +2 -3
  8. package/esm/context/database-transaction-context.d.mts.map +1 -1
  9. package/esm/contracts/database-driver.contract.d.mts +56 -36
  10. package/esm/contracts/database-driver.contract.d.mts.map +1 -1
  11. package/esm/contracts/database-id-generator.contract.d.mts +7 -5
  12. package/esm/contracts/database-id-generator.contract.d.mts.map +1 -1
  13. package/esm/contracts/database-remover.contract.d.mts +3 -5
  14. package/esm/contracts/database-remover.contract.d.mts.map +1 -1
  15. package/esm/contracts/database-restorer.contract.d.mts +6 -6
  16. package/esm/contracts/database-restorer.contract.d.mts.map +1 -1
  17. package/esm/contracts/database-writer.contract.d.mts +4 -6
  18. package/esm/contracts/database-writer.contract.d.mts.map +1 -1
  19. package/esm/contracts/driver-blueprint.contract.d.mts +2 -3
  20. package/esm/contracts/driver-blueprint.contract.d.mts.map +1 -1
  21. package/esm/contracts/index.d.mts +2 -1
  22. package/esm/contracts/migration-driver.contract.d.mts +101 -53
  23. package/esm/contracts/migration-driver.contract.d.mts.map +1 -1
  24. package/esm/contracts/query-builder.contract.d.mts +51 -33
  25. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  26. package/esm/contracts/sync-adapter.contract.d.mts +25 -14
  27. package/esm/contracts/sync-adapter.contract.d.mts.map +1 -1
  28. package/esm/data-source/data-source-registry.d.mts +4 -5
  29. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  30. package/esm/data-source/data-source-registry.mjs.map +1 -1
  31. package/esm/data-source/data-source.d.mts +8 -5
  32. package/esm/data-source/data-source.d.mts.map +1 -1
  33. package/esm/database-dirty-tracker.d.mts +2 -2
  34. package/esm/database-dirty-tracker.d.mts.map +1 -1
  35. package/esm/database-dirty-tracker.mjs +3 -2
  36. package/esm/database-dirty-tracker.mjs.map +1 -1
  37. package/esm/drivers/mongodb/mongodb-driver.d.mts +3 -4
  38. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  39. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  40. package/esm/drivers/mongodb/mongodb-id-generator.d.mts +2 -3
  41. package/esm/drivers/mongodb/mongodb-id-generator.d.mts.map +1 -1
  42. package/esm/drivers/mongodb/mongodb-id-generator.mjs.map +1 -1
  43. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -3
  44. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
  45. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +2 -3
  46. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  47. package/esm/drivers/mongodb/mongodb-query-builder.mjs +8 -5
  48. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  49. package/esm/drivers/mongodb/mongodb-query-operations.d.mts +1 -3
  50. package/esm/drivers/mongodb/mongodb-query-operations.d.mts.map +1 -1
  51. package/esm/drivers/mongodb/mongodb-query-operations.mjs.map +1 -1
  52. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +7 -6
  53. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  54. package/esm/drivers/mongodb/mongodb-query-parser.mjs +2 -6
  55. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  56. package/esm/drivers/mongodb/mongodb-sync-adapter.d.mts +1 -3
  57. package/esm/drivers/mongodb/mongodb-sync-adapter.d.mts.map +1 -1
  58. package/esm/drivers/mongodb/types.d.mts +11 -9
  59. package/esm/drivers/mongodb/types.d.mts.map +1 -1
  60. package/esm/drivers/postgres/index.d.mts +2 -1
  61. package/esm/drivers/postgres/index.mjs +1 -1
  62. package/esm/drivers/postgres/naming.mjs +1 -1
  63. package/esm/drivers/postgres/naming.mjs.map +1 -1
  64. package/esm/drivers/postgres/postgres-blueprint.d.mts +1 -3
  65. package/esm/drivers/postgres/postgres-blueprint.d.mts.map +1 -1
  66. package/esm/drivers/postgres/postgres-dialect.d.mts +4 -6
  67. package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
  68. package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
  69. package/esm/drivers/postgres/postgres-driver.d.mts +4 -5
  70. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  71. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  72. package/esm/drivers/postgres/postgres-migration-driver.d.mts +1 -3
  73. package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
  74. package/esm/drivers/postgres/postgres-migration-driver.mjs +13 -10
  75. package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
  76. package/esm/drivers/postgres/postgres-query-builder.d.mts +4 -4
  77. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  78. package/esm/drivers/postgres/postgres-query-builder.mjs +14 -10
  79. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  80. package/esm/drivers/postgres/postgres-query-parser.d.mts +16 -11
  81. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  82. package/esm/drivers/postgres/postgres-query-parser.mjs +1 -2
  83. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  84. package/esm/drivers/postgres/postgres-sql-serializer.mjs +13 -10
  85. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  86. package/esm/drivers/postgres/postgres-ttl-purge.mjs +1 -1
  87. package/esm/drivers/postgres/postgres-ttl-purge.mjs.map +1 -1
  88. package/esm/drivers/postgres/postgres-type-converters.mjs +2 -2
  89. package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -1
  90. package/esm/drivers/postgres/postgres-update-validator.mjs +1 -1
  91. package/esm/drivers/postgres/postgres-update-validator.mjs.map +1 -1
  92. package/esm/drivers/postgres/types.d.mts +87 -45
  93. package/esm/drivers/postgres/types.d.mts.map +1 -1
  94. package/esm/drivers/sql/sql-dialect.contract.d.mts +1 -3
  95. package/esm/drivers/sql/sql-dialect.contract.d.mts.map +1 -1
  96. package/esm/drivers/sql/sql-types.d.mts +113 -59
  97. package/esm/drivers/sql/sql-types.d.mts.map +1 -1
  98. package/esm/errors/missing-data-source.error.d.mts +1 -2
  99. package/esm/errors/missing-data-source.error.d.mts.map +1 -1
  100. package/esm/errors/transaction-rollback.error.d.mts +1 -2
  101. package/esm/errors/transaction-rollback.error.d.mts.map +1 -1
  102. package/esm/errors/undefined-where-value.error.d.mts +1 -2
  103. package/esm/errors/undefined-where-value.error.d.mts.map +1 -1
  104. package/esm/errors/unsafe-filter.error.d.mts +1 -2
  105. package/esm/errors/unsafe-filter.error.d.mts.map +1 -1
  106. package/esm/errors/unsafe-raw-expression.error.d.mts +1 -2
  107. package/esm/errors/unsafe-raw-expression.error.d.mts.map +1 -1
  108. package/esm/errors/unsupported-lean-operation.error.d.mts +1 -2
  109. package/esm/errors/unsupported-lean-operation.error.d.mts.map +1 -1
  110. package/esm/errors/unsupported-lock-for-update.error.d.mts +1 -2
  111. package/esm/errors/unsupported-lock-for-update.error.d.mts.map +1 -1
  112. package/esm/errors/unsupported-query-operation.error.d.mts +1 -2
  113. package/esm/errors/unsupported-query-operation.error.d.mts.map +1 -1
  114. package/esm/errors/unsupported-query-operation.error.mjs.map +1 -1
  115. package/esm/errors/unsupported-update-operation.error.d.mts +1 -2
  116. package/esm/errors/unsupported-update-operation.error.d.mts.map +1 -1
  117. package/esm/events/model-events.d.mts +8 -6
  118. package/esm/events/model-events.d.mts.map +1 -1
  119. package/esm/expressions/aggregate-expressions.d.mts +7 -7
  120. package/esm/expressions/aggregate-expressions.d.mts.map +1 -1
  121. package/esm/expressions/column-expressions.d.mts +11 -11
  122. package/esm/expressions/column-expressions.d.mts.map +1 -1
  123. package/esm/expressions/column-expressions.mjs.map +1 -1
  124. package/esm/index.d.mts +7 -2
  125. package/esm/index.mjs +1 -1
  126. package/esm/migration/column-builder.d.mts +2 -3
  127. package/esm/migration/column-builder.d.mts.map +1 -1
  128. package/esm/migration/column-helpers.d.mts +49 -50
  129. package/esm/migration/column-helpers.d.mts.map +1 -1
  130. package/esm/migration/column-helpers.mjs +1 -1
  131. package/esm/migration/foreign-key-builder.d.mts +2 -3
  132. package/esm/migration/foreign-key-builder.d.mts.map +1 -1
  133. package/esm/migration/foreign-key-builder.mjs.map +1 -1
  134. package/esm/migration/index.d.mts +3 -2
  135. package/esm/migration/index.mjs +2 -2
  136. package/esm/migration/migration-runner.d.mts +7 -6
  137. package/esm/migration/migration-runner.d.mts.map +1 -1
  138. package/esm/migration/migration-runner.mjs +15 -11
  139. package/esm/migration/migration-runner.mjs.map +1 -1
  140. package/esm/migration/migration.d.mts +50 -32
  141. package/esm/migration/migration.d.mts.map +1 -1
  142. package/esm/migration/migration.mjs +1 -3
  143. package/esm/migration/migration.mjs.map +1 -1
  144. package/esm/migration/parse-created-at.mjs.map +1 -1
  145. package/esm/migration/sql-grammar.mjs.map +1 -1
  146. package/esm/migration/sql-serializer.d.mts +1 -3
  147. package/esm/migration/sql-serializer.d.mts.map +1 -1
  148. package/esm/migration/types.d.mts +7 -7
  149. package/esm/migration/types.d.mts.map +1 -1
  150. package/esm/model/generate-model-id.mjs.map +1 -1
  151. package/esm/model/methods/accessor-methods.mjs +1 -1
  152. package/esm/model/methods/accessor-methods.mjs.map +1 -1
  153. package/esm/model/methods/dirty-methods.mjs.map +1 -1
  154. package/esm/model/methods/hydration-methods.mjs.map +1 -1
  155. package/esm/model/methods/meta-methods.mjs +4 -2
  156. package/esm/model/methods/meta-methods.mjs.map +1 -1
  157. package/esm/model/methods/query-methods.mjs +1 -1
  158. package/esm/model/methods/query-methods.mjs.map +1 -1
  159. package/esm/model/methods/scope-methods.mjs.map +1 -1
  160. package/esm/model/methods/static-event-methods.mjs.map +1 -1
  161. package/esm/model/methods/write-methods.d.mts +3 -2
  162. package/esm/model/methods/write-methods.d.mts.map +1 -1
  163. package/esm/model/methods/write-methods.mjs +5 -2
  164. package/esm/model/methods/write-methods.mjs.map +1 -1
  165. package/esm/model/model.d.mts +9 -9
  166. package/esm/model/model.d.mts.map +1 -1
  167. package/esm/model/model.mjs.map +1 -1
  168. package/esm/model/model.types.d.mts +7 -7
  169. package/esm/model/model.types.d.mts.map +1 -1
  170. package/esm/model/register-model.d.mts +13 -15
  171. package/esm/model/register-model.d.mts.map +1 -1
  172. package/esm/model/register-model.mjs.map +1 -1
  173. package/esm/model/relation-decorators.d.mts +6 -8
  174. package/esm/model/relation-decorators.d.mts.map +1 -1
  175. package/esm/operations/database.d.mts +13 -8
  176. package/esm/operations/database.d.mts.map +1 -1
  177. package/esm/operations/index.d.mts +2 -1
  178. package/esm/operations/index.mjs +1 -1
  179. package/esm/operations/migrations.d.mts +18 -14
  180. package/esm/operations/migrations.d.mts.map +1 -1
  181. package/esm/operations/migrations.mjs.map +1 -1
  182. package/esm/query-builder/query-builder.d.mts +2 -4
  183. package/esm/query-builder/query-builder.d.mts.map +1 -1
  184. package/esm/query-builder/query-builder.mjs +12 -11
  185. package/esm/query-builder/query-builder.mjs.map +1 -1
  186. package/esm/relations/index.d.mts +2 -1
  187. package/esm/relations/index.mjs +1 -1
  188. package/esm/relations/key-conventions.mjs +2 -1
  189. package/esm/relations/key-conventions.mjs.map +1 -1
  190. package/esm/relations/pivot-operations.d.mts +3 -5
  191. package/esm/relations/pivot-operations.d.mts.map +1 -1
  192. package/esm/relations/pivot-operations.mjs.map +1 -1
  193. package/esm/relations/relation-hydrator.d.mts +3 -5
  194. package/esm/relations/relation-hydrator.d.mts.map +1 -1
  195. package/esm/relations/relation-loader.d.mts +2 -4
  196. package/esm/relations/relation-loader.d.mts.map +1 -1
  197. package/esm/relations/relation-loader.mjs +4 -5
  198. package/esm/relations/relation-loader.mjs.map +1 -1
  199. package/esm/relations/types.d.mts +14 -15
  200. package/esm/relations/types.d.mts.map +1 -1
  201. package/esm/remover/database-remover.d.mts +1 -3
  202. package/esm/remover/database-remover.d.mts.map +1 -1
  203. package/esm/remover/database-remover.mjs.map +1 -1
  204. package/esm/restorer/database-restorer.d.mts +1 -3
  205. package/esm/restorer/database-restorer.d.mts.map +1 -1
  206. package/esm/restorer/database-restorer.mjs +2 -1
  207. package/esm/restorer/database-restorer.mjs.map +1 -1
  208. package/esm/sql-database-dirty-tracker.d.mts +1 -3
  209. package/esm/sql-database-dirty-tracker.d.mts.map +1 -1
  210. package/esm/sync/model-events.mjs +1 -1
  211. package/esm/sync/model-sync-operation.d.mts +2 -4
  212. package/esm/sync/model-sync-operation.d.mts.map +1 -1
  213. package/esm/sync/model-sync.d.mts +3 -4
  214. package/esm/sync/model-sync.d.mts.map +1 -1
  215. package/esm/sync/sync-context.d.mts +2 -4
  216. package/esm/sync/sync-context.d.mts.map +1 -1
  217. package/esm/sync/sync-context.mjs.map +1 -1
  218. package/esm/sync/sync-fanout.mjs +1 -1
  219. package/esm/sync/sync-fanout.mjs.map +1 -1
  220. package/esm/sync/sync-manager.d.mts +2 -4
  221. package/esm/sync/sync-manager.d.mts.map +1 -1
  222. package/esm/sync/sync-manager.mjs.map +1 -1
  223. package/esm/sync/types.d.mts +121 -64
  224. package/esm/sync/types.d.mts.map +1 -1
  225. package/esm/transactions/after-commit.d.mts +2 -2
  226. package/esm/transactions/after-commit.d.mts.map +1 -1
  227. package/esm/types.d.mts +7 -8
  228. package/esm/types.d.mts.map +1 -1
  229. package/esm/utils/connect-to-database.d.mts +7 -8
  230. package/esm/utils/connect-to-database.d.mts.map +1 -1
  231. package/esm/utils/database-writer.utils.d.mts +3 -4
  232. package/esm/utils/database-writer.utils.d.mts.map +1 -1
  233. package/esm/utils/database-writer.utils.mjs.map +1 -1
  234. package/esm/utils/define-model.d.mts +3 -5
  235. package/esm/utils/define-model.d.mts.map +1 -1
  236. package/esm/utils/escape-regex.d.mts +3 -4
  237. package/esm/utils/escape-regex.d.mts.map +1 -1
  238. package/esm/utils/is-valid-date-value.mjs.map +1 -1
  239. package/esm/utils/once-connected.d.mts +2 -4
  240. package/esm/utils/once-connected.d.mts.map +1 -1
  241. package/esm/utils/sanitize-filter.d.mts +3 -4
  242. package/esm/utils/sanitize-filter.d.mts.map +1 -1
  243. package/esm/validation/database-seal-plugins.d.mts +2 -1
  244. package/esm/validation/database-writer-validation-error.d.mts +1 -3
  245. package/esm/validation/database-writer-validation-error.d.mts.map +1 -1
  246. package/esm/validation/index.d.mts +3 -1
  247. package/esm/validation/index.mjs +1 -1
  248. package/esm/validation/plugins/database-rules-plugin.d.mts +2 -3
  249. package/esm/validation/plugins/database-rules-plugin.d.mts.map +1 -1
  250. package/esm/validation/plugins/embed-validator-plugin.d.mts +4 -3
  251. package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
  252. package/esm/validation/types.d.mts +14 -10
  253. package/esm/validation/types.d.mts.map +1 -1
  254. package/esm/validation/validators/embed-validator.d.mts +2 -3
  255. package/esm/validation/validators/embed-validator.d.mts.map +1 -1
  256. package/esm/writer/database-writer.d.mts +1 -3
  257. package/esm/writer/database-writer.d.mts.map +1 -1
  258. package/esm/writer/database-writer.mjs +1 -1
  259. package/esm/writer/database-writer.mjs.map +1 -1
  260. package/llms-full.txt +541 -434
  261. package/package.json +5 -5
  262. package/skills/aggregate-data/SKILL.md +28 -17
  263. package/skills/alter-migration/SKILL.md +325 -233
  264. package/skills/cascade-basics/SKILL.md +19 -19
  265. package/skills/configure-delete-strategy/SKILL.md +11 -11
  266. package/skills/define-model/SKILL.md +19 -17
  267. package/skills/define-relations/SKILL.md +13 -18
  268. package/skills/index.md +3 -0
  269. package/skills/manage-transactions/SKILL.md +18 -12
  270. package/skills/paginate-results/SKILL.md +6 -8
  271. package/skills/perform-atomic-ops/SKILL.md +26 -21
  272. package/skills/query-data/SKILL.md +27 -33
  273. package/skills/run-cascade-cli/SKILL.md +11 -10
  274. package/skills/search-by-vector/SKILL.md +8 -5
  275. package/skills/subscribe-to-model-events/SKILL.md +17 -17
  276. package/skills/track-changes/SKILL.md +9 -9
  277. package/skills/write-migration/SKILL.md +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"database-restorer.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/restorer/database-restorer.ts"],"sourcesContent":["import type { DriverContract, UpdateOperations } from \"../contracts/database-driver.contract\";\nimport type {\n RestorerContract,\n RestorerOptions,\n RestorerResult,\n} from \"../contracts/database-restorer.contract\";\nimport type { DataSource } from \"../data-source/data-source\";\nimport type { Model } from \"../model/model\";\nimport type { DeleteStrategy } from \"../types\";\nimport { enqueueSyncFanout } from \"../sync/sync-fanout\";\nimport { triggerModelEvent } from \"../sync/model-events\";\nimport { DatabaseWriter } from \"../writer/database-writer\";\n\n/**\n * Database restorer service that orchestrates model restoration.\n *\n * Handles the complete restoration pipeline:\n * 1. Strategy detection (trash vs soft delete)\n * 2. Record retrieval from trash table or soft-deleted records\n * 3. ID conflict resolution\n * 4. Event emission (restoring, restored)\n * 5. Driver execution (insert back to original table, remove from trash/clear deletedAt)\n *\n * @example\n * ```typescript\n * const restorer = new DatabaseRestorer(User);\n * const result = await restorer.restore(123);\n *\n * console.log(result.success); // true\n * console.log(result.strategy); // \"trash\" | \"soft\"\n * ```\n */\nexport class DatabaseRestorer implements RestorerContract {\n /** Model constructor reference */\n private readonly ctor: typeof 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 restorer instance for a model class.\n *\n * @param modelClass - The model class (static context)\n *\n * @example\n * ```typescript\n * const restorer = new DatabaseRestorer(User);\n * await restorer.restore(123);\n * ```\n */\n public constructor(modelClass: typeof Model) {\n this.ctor = modelClass;\n this.dataSource = modelClass.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = modelClass.table;\n this.primaryKey = modelClass.primaryKey;\n }\n\n /**\n * Restore a single deleted record by its ID.\n *\n * @param id - The primary key value of the record to restore\n * @param options - Restorer options\n * @returns Result containing success status, strategy used, and restored record\n * @throws {Error} If record not found in trash or soft-deleted records\n * @throws {Error} If ID conflict and onIdConflict is \"fail\"\n */\n public async restore(\n id: string | number,\n options: RestorerOptions = {},\n ): Promise<RestorerResult> {\n const onIdConflict = options.onIdConflict ?? \"assignNew\";\n const skipEvents = options.skipEvents ?? false;\n\n // 1. Resolve strategy (options → model static → data source default)\n const strategy = this.resolveStrategy(options.strategy);\n\n // 2. Validate strategy (cannot restore permanent deletes)\n if (strategy === \"permanent\") {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${id}: permanently deleted records cannot be restored.`,\n );\n }\n\n // 3. Fetch record based on strategy\n const recordData = await this.fetchRecordByStrategy(id, strategy);\n\n if (!recordData) {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${id}: record not found in ${strategy === \"trash\" ? \"trash table\" : \"soft-deleted records\"}.`,\n );\n }\n\n // 4. Prepare record data (remove metadata fields)\n const restoredData = { ...recordData };\n delete restoredData.deletedAt;\n delete restoredData.originalTable;\n\n // 5. Check for ID conflict and handle. A soft-deleted row never left the\n // table, so it would \"conflict\" with itself: it keeps its id and is\n // updated in place.\n const finalData =\n strategy === \"trash\"\n ? await this.handleIdConflict(restoredData, id, onIdConflict)\n : restoredData;\n\n // 6. Create temporary model instance for event emission\n // Note: Model is abstract, but at runtime this.ctor is a concrete subclass\n const model = new (this.ctor as any)(finalData) as Model;\n\n // 7. Emit restoring event (unless skipEvents)\n if (!skipEvents) {\n await model.emitEvent(\"restoring\");\n }\n\n // 8. Restore based on strategy\n if (strategy === \"trash\") {\n // Insert record back into original table (was moved to trash)\n await this.driver.insert(this.table, finalData);\n model.isNew = false;\n\n // Remove from trash table\n await this.driver.delete(this.resolveTrashTable(), {\n [this.primaryKey]: id,\n originalTable: this.table,\n });\n } else if (strategy === \"soft\") {\n // Record still exists, just unset deletedAt (don't insert - would create duplicate!)\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (deletedAtColumn) {\n const filter = { [this.primaryKey]: id };\n const updateOperations: UpdateOperations = {\n $unset: { [deletedAtColumn]: 1 },\n };\n\n await this.driver.update(this.table, filter, updateOperations);\n model.isNew = false;\n }\n }\n\n // 10. Emit restored event (unless skipEvents)\n if (!skipEvents) {\n await model.emitEvent(\"restored\");\n }\n\n await this.triggerSync(model);\n\n return {\n success: true,\n restoredCount: 1,\n strategy,\n restoredRecord: model,\n };\n }\n\n /**\n * Restore all deleted records for the model's table.\n *\n * @param options - Restorer options\n * @returns Result containing success status, strategy used, and aggregate counts\n */\n public async restoreAll(options: RestorerOptions = {}): Promise<RestorerResult> {\n const onIdConflict = options.onIdConflict ?? \"assignNew\";\n const skipEvents = options.skipEvents ?? false;\n\n // 1. Resolve strategy (options → model static → data source default)\n const strategy = this.resolveStrategy(options.strategy);\n\n // 2. Validate strategy (cannot restore permanent deletes)\n if (strategy === \"permanent\") {\n throw new Error(\n `Cannot restore all ${this.ctor.name} records: permanently deleted records cannot be restored.`,\n );\n }\n\n // 3. Fetch all records based on strategy\n const recordsToRestore = await this.fetchAllRecordsByStrategy(strategy);\n\n if (recordsToRestore.length === 0) {\n return {\n success: true,\n restoredCount: 0,\n strategy,\n };\n }\n\n // 4. Restore each record\n let restoredCount = 0;\n const conflicts: Array<{ id: string | number; reason: string }> = [];\n\n const restoredRecords: Model[] = [];\n\n for (const recordData of recordsToRestore) {\n const id = recordData[this.primaryKey] as string | number;\n\n try {\n // Prepare record data (remove metadata fields)\n const restoredData = { ...recordData };\n delete restoredData.deletedAt;\n delete restoredData.originalTable;\n\n // Check for ID conflict\n // Soft rows never left the table; only trash restores can conflict.\n const idExists = strategy === \"trash\" && (await this.checkIdExists(id));\n if (idExists) {\n if (onIdConflict === \"fail\") {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${id}: ID already exists in target table.`,\n );\n }\n\n // Assign new ID\n const finalData = await this.assignNewId(restoredData);\n conflicts.push({\n id,\n reason: `ID ${id} already exists, assigned new ID ${finalData[this.primaryKey]}`,\n });\n\n // Create temporary model for events\n const model = new (this.ctor as any)(finalData) as Model;\n\n // Emit restoring event\n if (!skipEvents) {\n await model.emitEvent(\"restoring\");\n }\n\n // Restore based on strategy\n if (strategy === \"trash\") {\n // Insert with new ID (was moved to trash)\n await this.driver.insert(this.table, finalData);\n model.isNew = false;\n } else if (strategy === \"soft\") {\n // Record still exists, just unset deletedAt\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (deletedAtColumn) {\n const filter = { [this.primaryKey]: id };\n const updateOperations: UpdateOperations = {\n $unset: { [deletedAtColumn]: 1 },\n };\n await this.driver.update(this.table, filter, updateOperations);\n model.isNew = false;\n }\n }\n\n restoredRecords.push(model);\n\n // Emit restored event\n if (!skipEvents) {\n await model.emitEvent(\"restored\");\n }\n\n await this.triggerSync(model);\n } else {\n // No conflict, restore with original ID\n const model = new (this.ctor as any)(restoredData) as Model;\n\n // Emit restoring event\n if (!skipEvents) {\n await model.emitEvent(\"restoring\");\n }\n\n // Restore based on strategy\n if (strategy === \"trash\") {\n // Insert with original ID (was moved to trash)\n await this.driver.insert(this.table, restoredData);\n model.isNew = false;\n } else if (strategy === \"soft\") {\n // Record still exists, just unset deletedAt\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (deletedAtColumn) {\n const filter = { [this.primaryKey]: id };\n const updateOperations: UpdateOperations = {\n $unset: { [deletedAtColumn]: 1 },\n };\n await this.driver.update(this.table, filter, updateOperations);\n model.isNew = false;\n }\n }\n\n restoredRecords.push(model);\n\n // Emit restored event\n if (!skipEvents) {\n await model.emitEvent(\"restored\");\n }\n\n await this.triggerSync(model);\n }\n\n // Remove from trash (only for trash strategy)\n if (strategy === \"trash\") {\n const trashTable = this.resolveTrashTable();\n const trashFilter = { [this.primaryKey]: id, originalTable: this.table };\n await this.driver.delete(trashTable, trashFilter);\n }\n\n restoredCount++;\n } catch (error) {\n if (onIdConflict === \"fail\") {\n throw error;\n }\n // Continue with next record if assignNew mode\n conflicts.push({\n id,\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n success: true,\n restoredCount,\n restoredRecords,\n strategy,\n conflicts: conflicts.length > 0 ? conflicts : undefined,\n };\n }\n\n /**\n * Resolve the delete strategy.\n *\n * Priority: options → model static → data source default → \"permanent\"\n *\n * @param strategyOption - Optional strategy override from options\n * @returns The resolved delete strategy\n * @private\n */\n private resolveStrategy(strategyOption?: \"trash\" | \"soft\"): DeleteStrategy {\n return (\n strategyOption ??\n this.ctor.deleteStrategy ??\n this.dataSource.defaultDeleteStrategy ??\n \"permanent\"\n );\n }\n\n /**\n * Fetch a record by ID based on the delete strategy.\n *\n * @param id - The primary key value\n * @param strategy - The delete strategy to use\n * @returns The record data, or null if not found\n * @private\n */\n private async fetchRecordByStrategy(id: string | number, strategy: \"trash\" | \"soft\") {\n if (strategy === \"trash\") {\n const trashTable = this.resolveTrashTable();\n try {\n const trashQuery = await this.driver\n .queryBuilder(trashTable)\n .where(this.primaryKey, id)\n .where(\"originalTable\", this.table)\n .first<Record<string, unknown>>();\n\n return trashQuery;\n } catch {\n return null;\n }\n } else if (strategy === \"soft\") {\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (!deletedAtColumn) {\n return null;\n }\n\n try {\n const softDeletedQuery = await this.driver\n .queryBuilder(this.table)\n .where(this.primaryKey, id)\n .whereNotNull(deletedAtColumn)\n .first<Record<string, unknown>>();\n\n return softDeletedQuery;\n } catch {\n return null;\n }\n }\n\n return null;\n }\n\n /**\n * Fetch all records based on the delete strategy.\n *\n * @param strategy - The delete strategy to use\n * @returns Array of record data\n * @private\n */\n private async fetchAllRecordsByStrategy(\n strategy: \"trash\" | \"soft\",\n ): Promise<Record<string, unknown>[]> {\n if (strategy === \"trash\") {\n const trashTable = this.resolveTrashTable();\n try {\n const trashQuery = this.driver\n .queryBuilder(trashTable)\n .where(\"originalTable\", this.table)\n .get();\n\n return (await trashQuery) as Record<string, unknown>[];\n } catch {\n return [];\n }\n } else if (strategy === \"soft\") {\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (!deletedAtColumn) {\n return [];\n }\n\n try {\n const softDeletedQuery = this.driver\n .queryBuilder(this.table)\n .whereNotNull(deletedAtColumn)\n .get();\n\n return (await softDeletedQuery) as Record<string, unknown>[];\n } catch {\n return [];\n }\n }\n\n return [];\n }\n\n /**\n * Handle ID conflict by checking if ID exists and assigning new one if needed.\n *\n * @param recordData - The record data to restore\n * @param originalId - The original ID value\n * @param onIdConflict - Conflict resolution strategy\n * @returns Record data with potentially new ID\n * @private\n */\n private async handleIdConflict(\n recordData: Record<string, unknown>,\n originalId: string | number,\n onIdConflict: \"fail\" | \"assignNew\",\n ): Promise<Record<string, unknown>> {\n const idExists = await this.checkIdExists(originalId);\n\n if (idExists) {\n if (onIdConflict === \"fail\") {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${originalId}: ID already exists in target table.`,\n );\n }\n\n // Assign new ID\n return await this.assignNewId(recordData);\n }\n\n return recordData;\n }\n\n /**\n * Check if an ID already exists in the target table.\n *\n * @param id - The ID to check\n * @returns True if ID exists, false otherwise\n * @private\n */\n private async checkIdExists(id: string | number): Promise<boolean> {\n try {\n const query = this.driver.queryBuilder(this.table).where(this.primaryKey, id).exists();\n\n return await query;\n } catch {\n return false;\n }\n }\n\n /**\n * Assign a new ID to the record data.\n *\n * For MongoDB: drops the old `_id` and `id`, then runs the record through the\n * writer's insert preparation so the new `id` comes from the same generator\n * a normal save uses (validation and events are skipped; this is a restore)\n * For SQL: Removes the primary key to let database auto-increment\n *\n * @param recordData - The record data\n * @returns Record data with new ID assigned\n * @private\n */\n private async assignNewId(recordData: Record<string, unknown>): Promise<Record<string, unknown>> {\n const newData = { ...recordData };\n\n if (this.driver.name !== \"mongodb\") {\n // SQL: Remove primary key to let database auto-increment\n delete newData[this.primaryKey];\n\n return newData;\n }\n\n // MongoDB: let the driver mint a new _id and the writer generate a new id.\n // The generator skips records that already carry an `id`, so drop both.\n delete newData._id;\n delete newData.id;\n\n const model = new (this.ctor as any)(newData) as Model;\n\n return await new DatabaseWriter(model).prepareForInsert({\n skipEvents: true,\n skipValidation: true,\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 * Queue sync for a restored record so embedded copies come back up to date.\n * The restore has already succeeded, so queued failures are retried separately.\n *\n * @private\n */\n private triggerSync(model: Model): void {\n enqueueSyncFanout({\n sourceModel: this.ctor.name,\n operation: \"restore\",\n execute: () =>\n triggerModelEvent(\n this.ctor as any,\n \"updated\",\n model,\n Object.keys(model.data as Record<string, unknown>),\n ),\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"database-restorer.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/restorer/database-restorer.ts"],"sourcesContent":["import type { DriverContract, UpdateOperations } from \"../contracts/database-driver.contract\";\nimport type {\n RestorerContract,\n RestorerOptions,\n RestorerResult,\n} from \"../contracts/database-restorer.contract\";\nimport type { DataSource } from \"../data-source/data-source\";\nimport type { Model } from \"../model/model\";\nimport type { DeleteStrategy } from \"../types\";\nimport { enqueueSyncFanout } from \"../sync/sync-fanout\";\nimport { triggerModelEvent } from \"../sync/model-events\";\nimport { DatabaseWriter } from \"../writer/database-writer\";\n\n/**\n * Database restorer service that orchestrates model restoration.\n *\n * Handles the complete restoration pipeline:\n * 1. Strategy detection (trash vs soft delete)\n * 2. Record retrieval from trash table or soft-deleted records\n * 3. ID conflict resolution\n * 4. Event emission (restoring, restored)\n * 5. Driver execution (insert back to original table, remove from trash/clear deletedAt)\n *\n * @example\n * ```typescript\n * const restorer = new DatabaseRestorer(User);\n * const result = await restorer.restore(123);\n *\n * console.log(result.success); // true\n * console.log(result.strategy); // \"trash\" | \"soft\"\n * ```\n */\nexport class DatabaseRestorer implements RestorerContract {\n /** Model constructor reference */\n private readonly ctor: typeof 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 restorer instance for a model class.\n *\n * @param modelClass - The model class (static context)\n *\n * @example\n * ```typescript\n * const restorer = new DatabaseRestorer(User);\n * await restorer.restore(123);\n * ```\n */\n public constructor(modelClass: typeof Model) {\n this.ctor = modelClass;\n this.dataSource = modelClass.getDataSource();\n this.driver = this.dataSource.driver;\n this.table = modelClass.table;\n this.primaryKey = modelClass.primaryKey;\n }\n\n /**\n * Restore a single deleted record by its ID.\n *\n * @param id - The primary key value of the record to restore\n * @param options - Restorer options\n * @returns Result containing success status, strategy used, and restored record\n * @throws {Error} If record not found in trash or soft-deleted records\n * @throws {Error} If ID conflict and onIdConflict is \"fail\"\n */\n public async restore(\n id: string | number,\n options: RestorerOptions = {},\n ): Promise<RestorerResult> {\n const onIdConflict = options.onIdConflict ?? \"assignNew\";\n const skipEvents = options.skipEvents ?? false;\n\n // 1. Resolve strategy (options → model static → data source default)\n const strategy = this.resolveStrategy(options.strategy);\n\n // 2. Validate strategy (cannot restore permanent deletes)\n if (strategy === \"permanent\") {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${id}: permanently deleted records cannot be restored.`,\n );\n }\n\n // 3. Fetch record based on strategy\n const recordData = await this.fetchRecordByStrategy(id, strategy);\n\n if (!recordData) {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${id}: record not found in ${strategy === \"trash\" ? \"trash table\" : \"soft-deleted records\"}.`,\n );\n }\n\n // 4. Prepare record data (remove metadata fields)\n const restoredData = { ...recordData };\n delete restoredData.deletedAt;\n delete restoredData.originalTable;\n\n // 5. Check for ID conflict and handle. A soft-deleted row never left the\n // table, so it would \"conflict\" with itself: it keeps its id and is\n // updated in place.\n const finalData =\n strategy === \"trash\"\n ? await this.handleIdConflict(restoredData, id, onIdConflict)\n : restoredData;\n\n // 6. Create temporary model instance for event emission\n // Note: Model is abstract, but at runtime this.ctor is a concrete subclass\n const model = new (this.ctor as any)(finalData) as Model;\n\n // 7. Emit restoring event (unless skipEvents)\n if (!skipEvents) {\n await model.emitEvent(\"restoring\");\n }\n\n // 8. Restore based on strategy\n if (strategy === \"trash\") {\n // Insert record back into original table (was moved to trash)\n await this.driver.insert(this.table, finalData);\n model.isNew = false;\n\n // Remove from trash table\n await this.driver.delete(this.resolveTrashTable(), {\n [this.primaryKey]: id,\n originalTable: this.table,\n });\n } else if (strategy === \"soft\") {\n // Record still exists, just unset deletedAt (don't insert - would create duplicate!)\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (deletedAtColumn) {\n const filter = { [this.primaryKey]: id };\n const updateOperations: UpdateOperations = {\n $unset: { [deletedAtColumn]: 1 },\n };\n\n await this.driver.update(this.table, filter, updateOperations);\n model.isNew = false;\n }\n }\n\n // 10. Emit restored event (unless skipEvents)\n if (!skipEvents) {\n await model.emitEvent(\"restored\");\n }\n\n await this.triggerSync(model);\n\n return {\n success: true,\n restoredCount: 1,\n strategy,\n restoredRecord: model,\n };\n }\n\n /**\n * Restore all deleted records for the model's table.\n *\n * @param options - Restorer options\n * @returns Result containing success status, strategy used, and aggregate counts\n */\n public async restoreAll(options: RestorerOptions = {}): Promise<RestorerResult> {\n const onIdConflict = options.onIdConflict ?? \"assignNew\";\n const skipEvents = options.skipEvents ?? false;\n\n // 1. Resolve strategy (options → model static → data source default)\n const strategy = this.resolveStrategy(options.strategy);\n\n // 2. Validate strategy (cannot restore permanent deletes)\n if (strategy === \"permanent\") {\n throw new Error(\n `Cannot restore all ${this.ctor.name} records: permanently deleted records cannot be restored.`,\n );\n }\n\n // 3. Fetch all records based on strategy\n const recordsToRestore = await this.fetchAllRecordsByStrategy(strategy);\n\n if (recordsToRestore.length === 0) {\n return {\n success: true,\n restoredCount: 0,\n strategy,\n };\n }\n\n // 4. Restore each record\n let restoredCount = 0;\n const conflicts: Array<{ id: string | number; reason: string }> = [];\n\n const restoredRecords: Model[] = [];\n\n for (const recordData of recordsToRestore) {\n const id = recordData[this.primaryKey] as string | number;\n\n try {\n // Prepare record data (remove metadata fields)\n const restoredData = { ...recordData };\n delete restoredData.deletedAt;\n delete restoredData.originalTable;\n\n // Check for ID conflict\n // Soft rows never left the table; only trash restores can conflict.\n const idExists = strategy === \"trash\" && (await this.checkIdExists(id));\n if (idExists) {\n if (onIdConflict === \"fail\") {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${id}: ID already exists in target table.`,\n );\n }\n\n // Assign new ID\n const finalData = await this.assignNewId(restoredData);\n conflicts.push({\n id,\n reason: `ID ${id} already exists, assigned new ID ${finalData[this.primaryKey]}`,\n });\n\n // Create temporary model for events\n const model = new (this.ctor as any)(finalData) as Model;\n\n // Emit restoring event\n if (!skipEvents) {\n await model.emitEvent(\"restoring\");\n }\n\n // Restore based on strategy\n if (strategy === \"trash\") {\n // Insert with new ID (was moved to trash)\n await this.driver.insert(this.table, finalData);\n model.isNew = false;\n } else if (strategy === \"soft\") {\n // Record still exists, just unset deletedAt\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (deletedAtColumn) {\n const filter = { [this.primaryKey]: id };\n const updateOperations: UpdateOperations = {\n $unset: { [deletedAtColumn]: 1 },\n };\n await this.driver.update(this.table, filter, updateOperations);\n model.isNew = false;\n }\n }\n\n restoredRecords.push(model);\n\n // Emit restored event\n if (!skipEvents) {\n await model.emitEvent(\"restored\");\n }\n\n await this.triggerSync(model);\n } else {\n // No conflict, restore with original ID\n const model = new (this.ctor as any)(restoredData) as Model;\n\n // Emit restoring event\n if (!skipEvents) {\n await model.emitEvent(\"restoring\");\n }\n\n // Restore based on strategy\n if (strategy === \"trash\") {\n // Insert with original ID (was moved to trash)\n await this.driver.insert(this.table, restoredData);\n model.isNew = false;\n } else if (strategy === \"soft\") {\n // Record still exists, just unset deletedAt\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (deletedAtColumn) {\n const filter = { [this.primaryKey]: id };\n const updateOperations: UpdateOperations = {\n $unset: { [deletedAtColumn]: 1 },\n };\n await this.driver.update(this.table, filter, updateOperations);\n model.isNew = false;\n }\n }\n\n restoredRecords.push(model);\n\n // Emit restored event\n if (!skipEvents) {\n await model.emitEvent(\"restored\");\n }\n\n await this.triggerSync(model);\n }\n\n // Remove from trash (only for trash strategy)\n if (strategy === \"trash\") {\n const trashTable = this.resolveTrashTable();\n const trashFilter = { [this.primaryKey]: id, originalTable: this.table };\n await this.driver.delete(trashTable, trashFilter);\n }\n\n restoredCount++;\n } catch (error) {\n if (onIdConflict === \"fail\") {\n throw error;\n }\n // Continue with next record if assignNew mode\n conflicts.push({\n id,\n reason: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n return {\n success: true,\n restoredCount,\n restoredRecords,\n strategy,\n conflicts: conflicts.length > 0 ? conflicts : undefined,\n };\n }\n\n /**\n * Resolve the delete strategy.\n *\n * Priority: options → model static → data source default → \"permanent\"\n *\n * @param strategyOption - Optional strategy override from options\n * @returns The resolved delete strategy\n * @private\n */\n private resolveStrategy(strategyOption?: \"trash\" | \"soft\"): DeleteStrategy {\n return (\n strategyOption ??\n this.ctor.deleteStrategy ??\n this.dataSource.defaultDeleteStrategy ??\n \"permanent\"\n );\n }\n\n /**\n * Fetch a record by ID based on the delete strategy.\n *\n * @param id - The primary key value\n * @param strategy - The delete strategy to use\n * @returns The record data, or null if not found\n * @private\n */\n private async fetchRecordByStrategy(id: string | number, strategy: \"trash\" | \"soft\") {\n if (strategy === \"trash\") {\n const trashTable = this.resolveTrashTable();\n try {\n const trashQuery = await this.driver\n .queryBuilder(trashTable)\n .where(this.primaryKey, id)\n .where(\"originalTable\", this.table)\n .first<Record<string, unknown>>();\n\n return trashQuery;\n } catch {\n return null;\n }\n } else if (strategy === \"soft\") {\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (!deletedAtColumn) {\n return null;\n }\n\n try {\n const softDeletedQuery = await this.driver\n .queryBuilder(this.table)\n .where(this.primaryKey, id)\n .whereNotNull(deletedAtColumn)\n .first<Record<string, unknown>>();\n\n return softDeletedQuery;\n } catch {\n return null;\n }\n }\n\n return null;\n }\n\n /**\n * Fetch all records based on the delete strategy.\n *\n * @param strategy - The delete strategy to use\n * @returns Array of record data\n * @private\n */\n private async fetchAllRecordsByStrategy(\n strategy: \"trash\" | \"soft\",\n ): Promise<Record<string, unknown>[]> {\n if (strategy === \"trash\") {\n const trashTable = this.resolveTrashTable();\n try {\n const trashQuery = this.driver\n .queryBuilder(trashTable)\n .where(\"originalTable\", this.table)\n .get();\n\n return (await trashQuery) as Record<string, unknown>[];\n } catch {\n return [];\n }\n } else if (strategy === \"soft\") {\n const deletedAtColumn = this.ctor.deletedAtColumn ?? \"deletedAt\";\n if (!deletedAtColumn) {\n return [];\n }\n\n try {\n const softDeletedQuery = this.driver\n .queryBuilder(this.table)\n .whereNotNull(deletedAtColumn)\n .get();\n\n return (await softDeletedQuery) as Record<string, unknown>[];\n } catch {\n return [];\n }\n }\n\n return [];\n }\n\n /**\n * Handle ID conflict by checking if ID exists and assigning new one if needed.\n *\n * @param recordData - The record data to restore\n * @param originalId - The original ID value\n * @param onIdConflict - Conflict resolution strategy\n * @returns Record data with potentially new ID\n * @private\n */\n private async handleIdConflict(\n recordData: Record<string, unknown>,\n originalId: string | number,\n onIdConflict: \"fail\" | \"assignNew\",\n ): Promise<Record<string, unknown>> {\n const idExists = await this.checkIdExists(originalId);\n\n if (idExists) {\n if (onIdConflict === \"fail\") {\n throw new Error(\n `Cannot restore ${this.ctor.name} with ${this.primaryKey} ${originalId}: ID already exists in target table.`,\n );\n }\n\n // Assign new ID\n return await this.assignNewId(recordData);\n }\n\n return recordData;\n }\n\n /**\n * Check if an ID already exists in the target table.\n *\n * @param id - The ID to check\n * @returns True if ID exists, false otherwise\n * @private\n */\n private async checkIdExists(id: string | number): Promise<boolean> {\n try {\n const query = this.driver.queryBuilder(this.table).where(this.primaryKey, id).exists();\n\n return await query;\n } catch {\n return false;\n }\n }\n\n /**\n * Assign a new ID to the record data.\n *\n * For MongoDB: drops the old `_id` and `id`, then runs the record through the\n * writer's insert preparation so the new `id` comes from the same generator\n * a normal save uses (validation and events are skipped; this is a restore)\n * For SQL: Removes the primary key to let database auto-increment\n *\n * @param recordData - The record data\n * @returns Record data with new ID assigned\n * @private\n */\n private async assignNewId(recordData: Record<string, unknown>): Promise<Record<string, unknown>> {\n const newData = { ...recordData };\n\n if (this.driver.name !== \"mongodb\") {\n // SQL: Remove primary key to let database auto-increment\n delete newData[this.primaryKey];\n\n return newData;\n }\n\n // MongoDB: let the driver mint a new _id and the writer generate a new id.\n // The generator skips records that already carry an `id`, so drop both.\n delete newData._id;\n delete newData.id;\n\n const model = new (this.ctor as any)(newData) as Model;\n\n return await new DatabaseWriter(model).prepareForInsert({\n skipEvents: true,\n skipValidation: true,\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 * Queue sync for a restored record so embedded copies come back up to date.\n * The restore has already succeeded, so queued failures are retried separately.\n *\n * @private\n */\n private triggerSync(model: Model): void {\n enqueueSyncFanout({\n sourceModel: this.ctor.name,\n operation: \"restore\",\n execute: () =>\n triggerModelEvent(\n this.ctor as any,\n \"updated\",\n model,\n Object.keys(model.data as Record<string, unknown>),\n ),\n });\n }\n}\n"],"mappings":"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"}
@@ -1,17 +1,15 @@
1
1
  import { DatabaseDirtyTracker } from "./database-dirty-tracker.mjs";
2
-
3
2
  //#region ../../packages/cascade/src/sql-database-dirty-tracker.d.ts
4
3
  /**
5
4
  * A dirty tracker designed for SQL databases where nested objects shouldn't be flattened.
6
5
  * Since SQL drivers (like Postgres) replace the entire JSON column when updated,
7
6
  * we keep the object intact instead of using dot-notation keys.
8
7
  */
9
- declare class SqlDatabaseDirtyTracker extends DatabaseDirtyTracker {
8
+ export declare class SqlDatabaseDirtyTracker extends DatabaseDirtyTracker {
10
9
  /**
11
10
  * Overrides the default flattening behavior to keep the raw data structure.
12
11
  */
13
12
  protected flattenData(data: Record<string, unknown>): Record<string, unknown>;
14
13
  }
15
14
  //#endregion
16
- export { SqlDatabaseDirtyTracker };
17
15
  //# sourceMappingURL=sql-database-dirty-tracker.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sql-database-dirty-tracker.d.mts","names":[],"sources":["../../../../../../../packages/cascade/src/sql-database-dirty-tracker.ts"],"mappings":";;;;;AAOA;;;cAAa,uBAAA,SAAgC,oBAAA;EAIoB;;;EAAA,UAA5C,WAAA,CAAY,IAAA,EAAM,MAAA,oBAA0B,MAAA;AAAA"}
1
+ {"version":3,"file":"sql-database-dirty-tracker.d.mts","names":[],"sources":["../../../../../../../packages/cascade/src/sql-database-dirty-tracker.ts"],"mappings":";;;;;;;qBAOa,gCAAgC;;;;YAIxB,YAAY,MAAM,0BAA0B"}
@@ -102,5 +102,5 @@ async function triggerModelEvent(modelClass, eventType, ...args) {
102
102
  }
103
103
 
104
104
  //#endregion
105
- export { getModelDeletedEvent, getModelUpdatedEvent, triggerModelEvent };
105
+ export { MODEL_EVENT_PREFIX, ModelSyncEventType, getLegacyModelEvent, getModelDeletedEvent, getModelEvent, getModelUpdatedEvent, triggerModelEvent };
106
106
  //# sourceMappingURL=model-events.mjs.map
@@ -1,7 +1,6 @@
1
- import { ChildModel } from "../model/model.types.mjs";
2
1
  import { ModelSyncConfig, ModelSyncOperationContract } from "./types.mjs";
2
+ import { ChildModel } from "../model/model.types.mjs";
3
3
  import { Model } from "../model/model.mjs";
4
-
5
4
  //#region ../../packages/cascade/src/sync/model-sync-operation.d.ts
6
5
  /**
7
6
  * Manages a single model sync operation.
@@ -19,7 +18,7 @@ import { Model } from "../model/model.mjs";
19
18
  * .unsetOnDelete();
20
19
  * ```
21
20
  */
22
- declare class ModelSyncOperation implements ModelSyncOperationContract {
21
+ export declare class ModelSyncOperation implements ModelSyncOperationContract {
23
22
  /**
24
23
  * Configuration for this sync operation.
25
24
  */
@@ -155,5 +154,4 @@ declare class ModelSyncOperation implements ModelSyncOperationContract {
155
154
  getConfig(): Readonly<ModelSyncConfig>;
156
155
  }
157
156
  //#endregion
158
- export { ModelSyncOperation };
159
157
  //# sourceMappingURL=model-sync-operation.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-sync-operation.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/model-sync-operation.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;cAkCa,kBAAA,YAA8B,0BAAA;EA2BvC;;;EAAA,iBAvBe,MAAA;EA0EV;;;EAAA,iBArEU,aAAA;EAsGV;;;EAAA,QAjGC,YAAA;EA4IA;;;;;;;;cAjIN,gBAAA,EAAkB,UAAA,CAAW,KAAA,GAC7B,gBAAA,EAAkB,UAAA,CAAW,KAAA,GAC7B,WAAA,UACA,MAAA;EAoSkB;;;AAAwB;;;;;;;;EAnQrC,KAAA,CAAM,MAAA;;;;;;;;;;;;;EAiBN,UAAA,CAAW,KAAA;;;;;;;;;;;;EAgBX,QAAA,CAAS,KAAA;;;;;;;;;;;;;EAiBT,WAAA,CAAY,MAAA;;;;;;;;;;;EAeZ,aAAA;;;;;;;;;;;EAeA,cAAA;;;;;UAaC,SAAA;;;;;EAwBD,WAAA;;;;EAYA,QAAA;;;;;UAYC,kBAAA;;;;;UAuCA,kBAAA;;;;UAwCA,UAAA;;;;UAWA,eAAA;;;;UAiBM,qBAAA;;;;EAWP,SAAA,IAAa,QAAA,CAAS,eAAA;AAAA"}
1
+ {"version":3,"file":"model-sync-operation.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/model-sync-operation.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;qBAkCa,8BAA8B;;;;mBAIxB;;;;mBAKA;;;;UAKT;;;;;;;;;EAUR,YACE,kBAAkB,WAAW,QAC7B,kBAAkB,WAAW,QAC7B,qBACA;;;;;;;;;;;;EAiCK,MAAM;;;;;;;;;;;;;EAiBN,WAAW;;;;;;;;;;;;EAgBX,SAAS;;;;;;;;;;;;;EAiBT,YAAY;;;;;;;;;;;EAeZ;;;;;;;;;;;EAeA;;;;;UAaC;;;;;EAwBD;;;;EAYA;;;;;UAYC;;;;;UAuCA;;;;UAwCA;;;;UAWA;;;;UAiBM;;;;EAWP,aAAa,SAAS"}
@@ -1,7 +1,6 @@
1
- import { ChildModel } from "../model/model.types.mjs";
2
1
  import { ModelSyncContract, ModelSyncOperationContract } from "./types.mjs";
2
+ import { ChildModel } from "../model/model.types.mjs";
3
3
  import { Model } from "../model/model.mjs";
4
-
5
4
  //#region ../../packages/cascade/src/sync/model-sync.d.ts
6
5
  /**
7
6
  * ModelSync facade implementation.
@@ -120,7 +119,7 @@ declare class ModelSyncFacade implements ModelSyncContract {
120
119
  * });
121
120
  * ```
122
121
  */
123
- declare const modelSync: ModelSyncFacade;
122
+ export declare const modelSync: ModelSyncFacade;
124
123
  //#endregion
125
- export { modelSync };
124
+ export {};
126
125
  //# sourceMappingURL=model-sync.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"model-sync.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/model-sync.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;cA6BM,eAAA,YAA2B,iBAAA;EAuC7B;;;EAAA,iBAnCe,UAAA;EAqCf;;;;EAAA,iBA/Be,iBAAA;EAwDf;;;;;;;;;;;;AAkFoB;AA0BxB;;;;AAA8C;;;;;EAxIrC,IAAA,CACL,MAAA,EAAQ,UAAA,CAAW,KAAA,GACnB,MAAA,EAAQ,UAAA,CAAW,KAAA,GACnB,KAAA,WACC,0BAAA;;;;;;;;;;;;;;;;;;EAuBI,QAAA,CACL,MAAA,EAAQ,UAAA,CAAW,KAAA,GACnB,MAAA,EAAQ,UAAA,CAAW,KAAA,GACnB,KAAA,WACC,0BAAA;;;;;;;;;;;;;;;;;;;;;EA0BI,QAAA,CAAS,QAAA;;;;;EA+BT,KAAA;;;;;MAWI,KAAA;;;;UAWH,cAAA;AAAA;;;;;;;;;;;;;;;cA0BG,SAAA,EAAS,eAAwB"}
1
+ {"version":3,"file":"model-sync.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/model-sync.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;cA6BM,2BAA2B;;;;mBAId;;;;;mBAMA;;;;;;;;;;;;;;;;;;;;;;;EA4BV,KACL,QAAQ,WAAW,QACnB,QAAQ,WAAW,QACnB,gBACC;;;;;;;;;;;;;;;;;;EAuBI,SACL,QAAQ,WAAW,QACnB,QAAQ,WAAW,QACnB,gBACC;;;;;;;;;;;;;;;;;;;;;EA0BI,SAAS;;;;;EA+BT;;;;;MAWI;;;;UAWH;;;;;;;;;;;;;;;;qBA0BG,WAAS"}
@@ -1,15 +1,14 @@
1
1
  import { SyncContext, SyncInstruction } from "./types.mjs";
2
-
3
2
  //#region ../../packages/cascade/src/sync/sync-context.d.ts
4
3
  /**
5
4
  * Default maximum sync depth.
6
5
  * Prevents infinite sync chains.
7
6
  */
8
- declare const DEFAULT_MAX_SYNC_DEPTH = 3;
7
+ export declare const DEFAULT_MAX_SYNC_DEPTH = 3;
9
8
  /**
10
9
  * Manages sync context and provides validation utilities.
11
10
  */
12
- declare class SyncContextManager {
11
+ export declare class SyncContextManager {
13
12
  /**
14
13
  * Creates a new sync context from a sync instruction.
15
14
  *
@@ -65,5 +64,4 @@ declare class SyncContextManager {
65
64
  static canSyncDeeper(currentDepth: number, maxDepth: number): boolean;
66
65
  }
67
66
  //#endregion
68
- export { DEFAULT_MAX_SYNC_DEPTH, SyncContextManager };
69
67
  //# sourceMappingURL=sync-context.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-context.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-context.ts"],"mappings":";;;;AAYmC;AAKnC;;cALa,sBAAA;;;;cAKA,kBAAA;EAUT;;;;;;;EAAA,OAFY,aAAA,CACZ,WAAA,EAAa,eAAA,EACb,aAAA,WACC,WAAW;EA8BT;;;;;;;;;;EAAA,OANS,QAAA,CACZ,KAAA,UACA,KAAA,YACA,WAAA,UACA,QAAA,UACA,eAAA;IACG,KAAA;IAAgB,KAAA;EAAA;EA6DH;;;;;;;EAAA,OAlCJ,QAAA,CAAS,KAAA,YAAiB,WAAA;;;;;;;;SAW1B,WAAA,CAAY,KAAA,YAAiB,SAAA;;;;;;;SAU7B,WAAA,CAAY,KAAA;;;;;;;;SAWZ,aAAA,CACZ,YAAA,UACA,QAAA;AAAA"}
1
+ {"version":3,"file":"sync-context.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-context.ts"],"mappings":";;;;;;qBAYa;;;;qBAKA;;;;;;;;SAQG,cAAc,aAAa,iBAAiB,wBAAwB;;;;;;;;;;;SAwBpE,SACZ,eACA,iBACA,qBACA,kBACA;IACG;IAAgB;;;;;;;;;SA2BP,SAAS,iBAAiB;;;;;;;;SAW1B,YAAY,iBAAiB;;;;;;;SAU7B,YAAY;;;;;;;;SAWZ,cAAc,sBAAsB"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync-context.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-context.ts"],"sourcesContent":["/**\n * Sync context management and validation.\n *\n * @module cascade-next/sync/sync-context\n */\n\nimport type { SyncContext, SyncInstruction } from \"./types\";\n\n/**\n * Default maximum sync depth.\n * Prevents infinite sync chains.\n */\nexport const DEFAULT_MAX_SYNC_DEPTH = 3;\n\n/**\n * Manages sync context and provides validation utilities.\n */\nexport class SyncContextManager {\n /**\n * Creates a new sync context from a sync instruction.\n *\n * @param instruction - The sync instruction to create context from\n * @param affectedCount - Number of documents affected\n * @returns A new sync context\n */\n public static createContext(\n instruction: SyncInstruction,\n affectedCount: number,\n ): SyncContext {\n return {\n sourceModel: instruction.sourceModel,\n sourceId: instruction.sourceId,\n currentDepth: instruction.depth,\n syncChain: [...instruction.chain],\n targetModel: instruction.targetModel,\n filter: { ...instruction.filter },\n update: { ...instruction.update },\n affectedCount,\n timestamp: new Date(),\n };\n }\n\n /**\n * Validates if a sync operation can proceed based on depth and cycle detection.\n *\n * @param depth - Current sync depth\n * @param chain - Current sync chain\n * @param targetModel - Target model name\n * @param maxDepth - Maximum allowed depth\n * @param preventCircular - Whether to prevent circular references\n * @returns Validation result with success flag and optional error message\n */\n public static validate(\n depth: number,\n chain: string[],\n targetModel: string,\n maxDepth: number,\n preventCircular: boolean,\n ): { valid: boolean; error?: string } {\n // Check depth limit\n if (depth > maxDepth) {\n return {\n valid: false,\n error: `Sync depth limit exceeded: ${depth} > ${maxDepth}. Chain: ${chain.join(\" → \")}`,\n };\n }\n\n // Check for circular references\n if (preventCircular && this.hasCycle(chain, targetModel)) {\n return {\n valid: false,\n error: `Circular sync detected: ${targetModel} already exists in chain [${chain.join(\" → \")}]`,\n };\n }\n\n return { valid: true };\n }\n\n /**\n * Checks if adding a target model would create a cycle in the sync chain.\n *\n * @param chain - Current sync chain\n * @param targetModel - Model to be added to the chain\n * @returns True if adding the model would create a cycle\n */\n public static hasCycle(chain: string[], targetModel: string): boolean {\n return chain.includes(targetModel);\n }\n\n /**\n * Creates a new sync chain by appending a model name.\n *\n * @param chain - Current sync chain\n * @param modelName - Model name to append\n * @returns New sync chain array\n */\n public static extendChain(chain: string[], modelName: string): string[] {\n return [...chain, modelName];\n }\n\n /**\n * Formats a sync chain for display.\n *\n * @param chain - Sync chain to format\n * @returns Formatted string (e.g., \"Category → Product → Module\")\n */\n public static formatChain(chain: string[]): string {\n return chain.join(\" → \");\n }\n\n /**\n * Checks if the current depth allows for further syncing.\n *\n * @param currentDepth - Current depth in the chain\n * @param maxDepth - Maximum allowed depth\n * @returns True if more syncing is allowed\n */\n public static canSyncDeeper(\n currentDepth: number,\n maxDepth: number,\n ): boolean {\n return currentDepth < maxDepth;\n }\n}\n\n"],"mappings":";;;;;AAYA,MAAa,yBAAyB;;;;AAKtC,IAAa,qBAAb,MAAgC;;;;;;;;CAQ9B,OAAc,cACZ,aACA,eACa;EACb,OAAO;GACL,aAAa,YAAY;GACzB,UAAU,YAAY;GACtB,cAAc,YAAY;GAC1B,WAAW,CAAC,GAAG,YAAY,KAAK;GAChC,aAAa,YAAY;GACzB,QAAQ,EAAE,GAAG,YAAY,OAAO;GAChC,QAAQ,EAAE,GAAG,YAAY,OAAO;GAChC;GACA,2BAAW,IAAI,KAAK;EACtB;CACF;;;;;;;;;;;CAYA,OAAc,SACZ,OACA,OACA,aACA,UACA,iBACoC;EAEpC,IAAI,QAAQ,UACV,OAAO;GACL,OAAO;GACP,OAAO,8BAA8B,MAAM,KAAK,SAAS,WAAW,MAAM,KAAK,KAAK;EACtF;EAIF,IAAI,mBAAmB,KAAK,SAAS,OAAO,WAAW,GACrD,OAAO;GACL,OAAO;GACP,OAAO,2BAA2B,YAAY,4BAA4B,MAAM,KAAK,KAAK,EAAE;EAC9F;EAGF,OAAO,EAAE,OAAO,KAAK;CACvB;;;;;;;;CASA,OAAc,SAAS,OAAiB,aAA8B;EACpE,OAAO,MAAM,SAAS,WAAW;CACnC;;;;;;;;CASA,OAAc,YAAY,OAAiB,WAA6B;EACtE,OAAO,CAAC,GAAG,OAAO,SAAS;CAC7B;;;;;;;CAQA,OAAc,YAAY,OAAyB;EACjD,OAAO,MAAM,KAAK,KAAK;CACzB;;;;;;;;CASA,OAAc,cACZ,cACA,UACS;EACT,OAAO,eAAe;CACxB;AACF"}
1
+ {"version":3,"file":"sync-context.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-context.ts"],"sourcesContent":["/**\n * Sync context management and validation.\n *\n * @module cascade-next/sync/sync-context\n */\n\nimport type { SyncContext, SyncInstruction } from \"./types\";\n\n/**\n * Default maximum sync depth.\n * Prevents infinite sync chains.\n */\nexport const DEFAULT_MAX_SYNC_DEPTH = 3;\n\n/**\n * Manages sync context and provides validation utilities.\n */\nexport class SyncContextManager {\n /**\n * Creates a new sync context from a sync instruction.\n *\n * @param instruction - The sync instruction to create context from\n * @param affectedCount - Number of documents affected\n * @returns A new sync context\n */\n public static createContext(instruction: SyncInstruction, affectedCount: number): SyncContext {\n return {\n sourceModel: instruction.sourceModel,\n sourceId: instruction.sourceId,\n currentDepth: instruction.depth,\n syncChain: [...instruction.chain],\n targetModel: instruction.targetModel,\n filter: { ...instruction.filter },\n update: { ...instruction.update },\n affectedCount,\n timestamp: new Date(),\n };\n }\n\n /**\n * Validates if a sync operation can proceed based on depth and cycle detection.\n *\n * @param depth - Current sync depth\n * @param chain - Current sync chain\n * @param targetModel - Target model name\n * @param maxDepth - Maximum allowed depth\n * @param preventCircular - Whether to prevent circular references\n * @returns Validation result with success flag and optional error message\n */\n public static validate(\n depth: number,\n chain: string[],\n targetModel: string,\n maxDepth: number,\n preventCircular: boolean,\n ): { valid: boolean; error?: string } {\n // Check depth limit\n if (depth > maxDepth) {\n return {\n valid: false,\n error: `Sync depth limit exceeded: ${depth} > ${maxDepth}. Chain: ${chain.join(\" → \")}`,\n };\n }\n\n // Check for circular references\n if (preventCircular && this.hasCycle(chain, targetModel)) {\n return {\n valid: false,\n error: `Circular sync detected: ${targetModel} already exists in chain [${chain.join(\" → \")}]`,\n };\n }\n\n return { valid: true };\n }\n\n /**\n * Checks if adding a target model would create a cycle in the sync chain.\n *\n * @param chain - Current sync chain\n * @param targetModel - Model to be added to the chain\n * @returns True if adding the model would create a cycle\n */\n public static hasCycle(chain: string[], targetModel: string): boolean {\n return chain.includes(targetModel);\n }\n\n /**\n * Creates a new sync chain by appending a model name.\n *\n * @param chain - Current sync chain\n * @param modelName - Model name to append\n * @returns New sync chain array\n */\n public static extendChain(chain: string[], modelName: string): string[] {\n return [...chain, modelName];\n }\n\n /**\n * Formats a sync chain for display.\n *\n * @param chain - Sync chain to format\n * @returns Formatted string (e.g., \"Category → Product → Module\")\n */\n public static formatChain(chain: string[]): string {\n return chain.join(\" → \");\n }\n\n /**\n * Checks if the current depth allows for further syncing.\n *\n * @param currentDepth - Current depth in the chain\n * @param maxDepth - Maximum allowed depth\n * @returns True if more syncing is allowed\n */\n public static canSyncDeeper(currentDepth: number, maxDepth: number): boolean {\n return currentDepth < maxDepth;\n }\n}\n"],"mappings":";;;;;AAYA,MAAa,yBAAyB;;;;AAKtC,IAAa,qBAAb,MAAgC;;;;;;;;CAQ9B,OAAc,cAAc,aAA8B,eAAoC;EAC5F,OAAO;GACL,aAAa,YAAY;GACzB,UAAU,YAAY;GACtB,cAAc,YAAY;GAC1B,WAAW,CAAC,GAAG,YAAY,KAAK;GAChC,aAAa,YAAY;GACzB,QAAQ,EAAE,GAAG,YAAY,OAAO;GAChC,QAAQ,EAAE,GAAG,YAAY,OAAO;GAChC;GACA,2BAAW,IAAI,KAAK;EACtB;CACF;;;;;;;;;;;CAYA,OAAc,SACZ,OACA,OACA,aACA,UACA,iBACoC;EAEpC,IAAI,QAAQ,UACV,OAAO;GACL,OAAO;GACP,OAAO,8BAA8B,MAAM,KAAK,SAAS,WAAW,MAAM,KAAK,KAAK;EACtF;EAIF,IAAI,mBAAmB,KAAK,SAAS,OAAO,WAAW,GACrD,OAAO;GACL,OAAO;GACP,OAAO,2BAA2B,YAAY,4BAA4B,MAAM,KAAK,KAAK,EAAE;EAC9F;EAGF,OAAO,EAAE,OAAO,KAAK;CACvB;;;;;;;;CASA,OAAc,SAAS,OAAiB,aAA8B;EACpE,OAAO,MAAM,SAAS,WAAW;CACnC;;;;;;;;CASA,OAAc,YAAY,OAAiB,WAA6B;EACtE,OAAO,CAAC,GAAG,OAAO,SAAS;CAC7B;;;;;;;CAQA,OAAc,YAAY,OAAyB;EACjD,OAAO,MAAM,KAAK,KAAK;CACzB;;;;;;;;CASA,OAAc,cAAc,cAAsB,UAA2B;EAC3E,OAAO,eAAe;CACxB;AACF"}
@@ -63,5 +63,5 @@ function enqueueSyncFanout(job) {
63
63
  }
64
64
 
65
65
  //#endregion
66
- export { enqueueSyncFanout };
66
+ export { SyncFanoutQueue, enqueueSyncFanout };
67
67
  //# sourceMappingURL=sync-fanout.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-fanout.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-fanout.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\n\ntype SyncFanoutJob = {\n sourceModel: string;\n operation: string;\n execute: () => Promise<void>;\n};\n\ntype SyncFanoutQueueOptions = {\n maxAttempts?: number;\n retryDelay?: (attempt: number) => number;\n reportFailure?: (job: SyncFanoutJob, error: unknown) => void;\n};\n\n/**\n * Serializes post-commit sync fan-out and retries transient failures without\n * making the already-committed write, delete, or restore fail.\n */\nexport class SyncFanoutQueue {\n private readonly jobs: SyncFanoutJob[] = [];\n private readonly maxAttempts: number;\n private readonly retryDelay: (attempt: number) => number;\n private readonly reportFailure: (job: SyncFanoutJob, error: unknown) => void;\n private draining = false;\n private idleResolvers: Array<() => void> = [];\n\n public constructor(options: SyncFanoutQueueOptions = {}) {\n this.maxAttempts = options.maxAttempts ?? 3;\n this.retryDelay = options.retryDelay ?? ((attempt) => attempt * 50);\n this.reportFailure = options.reportFailure ?? ((job, error) => {\n log.error(\n \"database\",\n \"sync.failed\",\n `[cascade] ${job.operation} sync failed for ${job.sourceModel} after ${this.maxAttempts} attempts: ${error}`,\n );\n });\n }\n\n /** Add a committed lifecycle fan-out job without awaiting its execution. */\n public enqueue(job: SyncFanoutJob): void {\n this.jobs.push(job);\n\n if (!this.draining) {\n this.draining = true;\n void this.drain();\n }\n }\n\n /** Wait until the currently queued fan-out jobs have settled. */\n public async flush(): Promise<void> {\n if (!this.draining && this.jobs.length === 0) {\n return;\n }\n\n await new Promise<void>((resolve) => this.idleResolvers.push(resolve));\n }\n\n private async drain(): Promise<void> {\n while (this.jobs.length > 0) {\n const job = this.jobs.shift();\n\n if (job) {\n await this.execute(job);\n }\n }\n\n this.draining = false;\n this.idleResolvers.splice(0).forEach((resolve) => resolve());\n }\n\n private async execute(job: SyncFanoutJob): Promise<void> {\n let error: unknown;\n\n for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n try {\n await job.execute();\n return;\n } catch (caught) {\n error = caught;\n\n if (attempt < this.maxAttempts) {\n await this.wait(this.retryDelay(attempt));\n }\n }\n }\n\n this.reportFailure(job, error);\n }\n\n private async wait(milliseconds: number): Promise<void> {\n if (milliseconds <= 0) {\n return;\n }\n\n await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));\n }\n}\n\nconst syncFanoutQueue = new SyncFanoutQueue();\n\n/** Queue a lifecycle sync after its database change has committed. */\nexport function enqueueSyncFanout(job: SyncFanoutJob): void {\n syncFanoutQueue.enqueue(job);\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,kBAAb,MAA6B;CAC3B,AAAiB,OAAwB,CAAC;CAC1C,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,WAAW;CACnB,AAAQ,gBAAmC,CAAC;CAE5C,AAAO,YAAY,UAAkC,CAAC,GAAG;EACvD,KAAK,cAAc,QAAQ,eAAe;EAC1C,KAAK,aAAa,QAAQ,gBAAgB,YAAY,UAAU;EAChE,KAAK,gBAAgB,QAAQ,mBAAmB,KAAK,UAAU;GAC7D,IAAI,MACF,YACA,eACA,aAAa,IAAI,UAAU,mBAAmB,IAAI,YAAY,SAAS,KAAK,YAAY,aAAa,OACvG;EACF;CACF;;CAGA,AAAO,QAAQ,KAA0B;EACvC,KAAK,KAAK,KAAK,GAAG;EAElB,IAAI,CAAC,KAAK,UAAU;GAClB,KAAK,WAAW;GAChB,AAAK,KAAK,MAAM;EAClB;CACF;;CAGA,MAAa,QAAuB;EAClC,IAAI,CAAC,KAAK,YAAY,KAAK,KAAK,WAAW,GACzC;EAGF,MAAM,IAAI,SAAe,YAAY,KAAK,cAAc,KAAK,OAAO,CAAC;CACvE;CAEA,MAAc,QAAuB;EACnC,OAAO,KAAK,KAAK,SAAS,GAAG;GAC3B,MAAM,MAAM,KAAK,KAAK,MAAM;GAE5B,IAAI,KACF,MAAM,KAAK,QAAQ,GAAG;EAE1B;EAEA,KAAK,WAAW;EAChB,KAAK,cAAc,OAAO,CAAC,CAAC,CAAC,SAAS,YAAY,QAAQ,CAAC;CAC7D;CAEA,MAAc,QAAQ,KAAmC;EACvD,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,KAAK,aAAa,WACjD,IAAI;GACF,MAAM,IAAI,QAAQ;GAClB;EACF,SAAS,QAAQ;GACf,QAAQ;GAER,IAAI,UAAU,KAAK,aACjB,MAAM,KAAK,KAAK,KAAK,WAAW,OAAO,CAAC;EAE5C;EAGF,KAAK,cAAc,KAAK,KAAK;CAC/B;CAEA,MAAc,KAAK,cAAqC;EACtD,IAAI,gBAAgB,GAClB;EAGF,MAAM,IAAI,SAAe,YAAY,WAAW,SAAS,YAAY,CAAC;CACxE;AACF;AAEA,MAAM,kBAAkB,IAAI,gBAAgB;;AAG5C,SAAgB,kBAAkB,KAA0B;CAC1D,gBAAgB,QAAQ,GAAG;AAC7B"}
1
+ {"version":3,"file":"sync-fanout.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-fanout.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\n\ntype SyncFanoutJob = {\n sourceModel: string;\n operation: string;\n execute: () => Promise<void>;\n};\n\ntype SyncFanoutQueueOptions = {\n maxAttempts?: number;\n retryDelay?: (attempt: number) => number;\n reportFailure?: (job: SyncFanoutJob, error: unknown) => void;\n};\n\n/**\n * Serializes post-commit sync fan-out and retries transient failures without\n * making the already-committed write, delete, or restore fail.\n */\nexport class SyncFanoutQueue {\n private readonly jobs: SyncFanoutJob[] = [];\n private readonly maxAttempts: number;\n private readonly retryDelay: (attempt: number) => number;\n private readonly reportFailure: (job: SyncFanoutJob, error: unknown) => void;\n private draining = false;\n private idleResolvers: Array<() => void> = [];\n\n public constructor(options: SyncFanoutQueueOptions = {}) {\n this.maxAttempts = options.maxAttempts ?? 3;\n this.retryDelay = options.retryDelay ?? ((attempt) => attempt * 50);\n this.reportFailure =\n options.reportFailure ??\n ((job, error) => {\n log.error(\n \"database\",\n \"sync.failed\",\n `[cascade] ${job.operation} sync failed for ${job.sourceModel} after ${this.maxAttempts} attempts: ${error}`,\n );\n });\n }\n\n /** Add a committed lifecycle fan-out job without awaiting its execution. */\n public enqueue(job: SyncFanoutJob): void {\n this.jobs.push(job);\n\n if (!this.draining) {\n this.draining = true;\n void this.drain();\n }\n }\n\n /** Wait until the currently queued fan-out jobs have settled. */\n public async flush(): Promise<void> {\n if (!this.draining && this.jobs.length === 0) {\n return;\n }\n\n await new Promise<void>((resolve) => this.idleResolvers.push(resolve));\n }\n\n private async drain(): Promise<void> {\n while (this.jobs.length > 0) {\n const job = this.jobs.shift();\n\n if (job) {\n await this.execute(job);\n }\n }\n\n this.draining = false;\n this.idleResolvers.splice(0).forEach((resolve) => resolve());\n }\n\n private async execute(job: SyncFanoutJob): Promise<void> {\n let error: unknown;\n\n for (let attempt = 1; attempt <= this.maxAttempts; attempt++) {\n try {\n await job.execute();\n return;\n } catch (caught) {\n error = caught;\n\n if (attempt < this.maxAttempts) {\n await this.wait(this.retryDelay(attempt));\n }\n }\n }\n\n this.reportFailure(job, error);\n }\n\n private async wait(milliseconds: number): Promise<void> {\n if (milliseconds <= 0) {\n return;\n }\n\n await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));\n }\n}\n\nconst syncFanoutQueue = new SyncFanoutQueue();\n\n/** Queue a lifecycle sync after its database change has committed. */\nexport function enqueueSyncFanout(job: SyncFanoutJob): void {\n syncFanoutQueue.enqueue(job);\n}\n"],"mappings":";;;;;;;AAkBA,IAAa,kBAAb,MAA6B;CAC3B,AAAiB,OAAwB,CAAC;CAC1C,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAQ,WAAW;CACnB,AAAQ,gBAAmC,CAAC;CAE5C,AAAO,YAAY,UAAkC,CAAC,GAAG;EACvD,KAAK,cAAc,QAAQ,eAAe;EAC1C,KAAK,aAAa,QAAQ,gBAAgB,YAAY,UAAU;EAChE,KAAK,gBACH,QAAQ,mBACN,KAAK,UAAU;GACf,IAAI,MACF,YACA,eACA,aAAa,IAAI,UAAU,mBAAmB,IAAI,YAAY,SAAS,KAAK,YAAY,aAAa,OACvG;EACF;CACJ;;CAGA,AAAO,QAAQ,KAA0B;EACvC,KAAK,KAAK,KAAK,GAAG;EAElB,IAAI,CAAC,KAAK,UAAU;GAClB,KAAK,WAAW;GAChB,AAAK,KAAK,MAAM;EAClB;CACF;;CAGA,MAAa,QAAuB;EAClC,IAAI,CAAC,KAAK,YAAY,KAAK,KAAK,WAAW,GACzC;EAGF,MAAM,IAAI,SAAe,YAAY,KAAK,cAAc,KAAK,OAAO,CAAC;CACvE;CAEA,MAAc,QAAuB;EACnC,OAAO,KAAK,KAAK,SAAS,GAAG;GAC3B,MAAM,MAAM,KAAK,KAAK,MAAM;GAE5B,IAAI,KACF,MAAM,KAAK,QAAQ,GAAG;EAE1B;EAEA,KAAK,WAAW;EAChB,KAAK,cAAc,OAAO,CAAC,CAAC,CAAC,SAAS,YAAY,QAAQ,CAAC;CAC7D;CAEA,MAAc,QAAQ,KAAmC;EACvD,IAAI;EAEJ,KAAK,IAAI,UAAU,GAAG,WAAW,KAAK,aAAa,WACjD,IAAI;GACF,MAAM,IAAI,QAAQ;GAClB;EACF,SAAS,QAAQ;GACf,QAAQ;GAER,IAAI,UAAU,KAAK,aACjB,MAAM,KAAK,KAAK,KAAK,WAAW,OAAO,CAAC;EAE5C;EAGF,KAAK,cAAc,KAAK,KAAK;CAC/B;CAEA,MAAc,KAAK,cAAqC;EACtD,IAAI,gBAAgB,GAClB;EAGF,MAAM,IAAI,SAAe,YAAY,WAAW,SAAS,YAAY,CAAC;CACxE;AACF;AAEA,MAAM,kBAAkB,IAAI,gBAAgB;;AAG5C,SAAgB,kBAAkB,KAA0B;CAC1D,gBAAgB,QAAQ,GAAG;AAC7B"}
@@ -1,13 +1,12 @@
1
- import { ChildModel } from "../model/model.types.mjs";
2
1
  import { SyncConfig, SyncResult } from "./types.mjs";
2
+ import { ChildModel } from "../model/model.types.mjs";
3
3
  import { Model } from "../model/model.mjs";
4
4
  import { DriverContract } from "../contracts/database-driver.contract.mjs";
5
-
6
5
  //#region ../../packages/cascade/src/sync/sync-manager.d.ts
7
6
  /**
8
7
  * Manages sync operations across models with multi-level support.
9
8
  */
10
- declare class SyncManager {
9
+ export declare class SyncManager {
11
10
  private readonly sourceModel;
12
11
  private readonly driver;
13
12
  /**
@@ -170,5 +169,4 @@ declare class SyncManager {
170
169
  private createEmptyResult;
171
170
  }
172
171
  //#endregion
173
- export { SyncManager };
174
172
  //# sourceMappingURL=sync-manager.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sync-manager.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-manager.ts"],"mappings":";;;;;;;;;cAqBa,WAAA;EAAA,iBACM,WAAA;EAAA,iBACA,MAAA;EAyBN;;;;;;cAjBQ,WAAA,EAAa,UAAA,CAAW,KAAA,GAAQ,MAAA,EAAQ,cAAA;EAkJjD;;;;;;;;EArIG,UAAA,CACX,QAAA,mBACA,WAAA,EAAa,MAAA,oBAA0B,KAAA,EACvC,aAAA,aACC,OAAA,CAAQ,UAAA;;;;;;;;;;;EAgEE,oBAAA,CACX,QAAA,mBACA,WAAA,EAAa,MAAA,oBAA0B,KAAA,EACvC,aAAA,YACA,MAAA,EAAQ,UAAA,GACP,OAAA,CAAQ,UAAA;EAtET;;;;;;;;EAgIW,oBAAA,CACX,QAAA,mBACA,MAAA,EAAQ,UAAA,GACP,OAAA,CAAQ,UAAA;EA9DD;;;;;;EAoHG,UAAA,CAAW,QAAA,oBAA4B,OAAA,CAAQ,UAAA;EAvD1D;;;;;;EAAA,QA6GY,mBAAA;EAAA;;;;;;;;EAAA,QAkDA,yBAAA;EAmQN;;;;;;;AAkHiB;;EAlHjB,QAzNA,sBAAA;;;;;;;;;UAwCA,sBAAA;;;;;;;;UA+BA,WAAA;;;;;;;;UAiBA,WAAA;;;;;;;;;UAqBM,mBAAA;;;;;;;;UAmEN,eAAA;;;;;;;UAmBA,YAAA;;;;;;;;UAsBA,UAAA;;;;;;;;UAeM,YAAA;;;;;;UAoCN,cAAA;;;;;;UAiBM,gBAAA;;;;;;UAuBA,eAAA;;;;;;UAuBN,iBAAA;AAAA"}
1
+ {"version":3,"file":"sync-manager.d.mts","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-manager.ts"],"mappings":";;;;;;;;qBAqBa;mBACM;mBACA;;;;;;;EAQjB,YAAmB,aAAa,WAAW,QAAQ,QAAQ;;;;;;;;;EAa9C,WACX,2BACA,aAAa,0BAA0B,OACvC,0BACC,QAAQ;;;;;;;;;;;EAgEE,qBACX,2BACA,aAAa,0BAA0B,OACvC,yBACA,QAAQ,aACP,QAAQ;;;;;;;;;EA0DE,qBACX,2BACA,QAAQ,aACP,QAAQ;;;;;;;EAsDE,WAAW,4BAA4B,QAAQ;;;;;;;UAsD9C;;;;;;;;;UAiDA;;;;;;;;;;UA0CN;;;;;;;;;UAwCA;;;;;;;;UA+BA;;;;;;;;UAiBA;;;;;;;;;UAqBM;;;;;;;;UAmEN;;;;;;;UAmBA;;;;;;;;UAsBA;;;;;;;;UAeM;;;;;;UAoCN;;;;;;UAiBM;;;;;;UAuBA;;;;;;UAuBN"}
@@ -1 +1 @@
1
- {"version":3,"file":"sync-manager.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-manager.ts"],"sourcesContent":["/**\n * Sync manager service for handling multi-level sync operations.\n *\n * @module cascade-next/sync/sync-manager\n */\n\nimport type { DriverContract } from \"../contracts/database-driver.contract\";\nimport { type ChildModel, Model } from \"../model/model\";\nimport { DEFAULT_MAX_SYNC_DEPTH, SyncContextManager } from \"./sync-context\";\nimport type {\n CollectInstructionsPayload,\n SyncConfig,\n SyncEventPayload,\n SyncInstruction,\n SyncInstructionOptions,\n SyncResult,\n} from \"./types\";\n\n/**\n * Manages sync operations across models with multi-level support.\n */\nexport class SyncManager {\n private readonly sourceModel: ChildModel<Model>;\n private readonly driver: DriverContract;\n\n /**\n * Creates a new sync manager.\n *\n * @param sourceModel - The source model class\n * @param driver - The database driver\n */\n public constructor(sourceModel: ChildModel<Model>, driver: DriverContract) {\n this.sourceModel = sourceModel;\n this.driver = driver;\n }\n\n /**\n * Executes sync operations for a model update.\n *\n * @param sourceId - The source model ID\n * @param updatedData - The updated data to sync (Model instance or plain data)\n * @param changedFields - Fields that were changed (for filtering)\n * @returns Sync result with success status and details\n */\n public async syncUpdate(\n sourceId: string | number,\n updatedData: Record<string, unknown> | Model,\n changedFields: string[],\n ): Promise<SyncResult> {\n try {\n const syncConfigs = this.getSyncConfigs();\n\n if (syncConfigs.length === 0) {\n return this.createEmptyResult();\n }\n\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: DEFAULT_MAX_SYNC_DEPTH,\n preventCircular: true,\n };\n\n const instructions = await this.collectInstructions({\n sourceId,\n updatedData,\n changedFields,\n syncConfigs,\n options,\n });\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n // Catch-all error handler for unexpected failures\n console.error(`Sync update failed for ${this.sourceModel.name}#${sourceId}:`, error);\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Executes sync operations for a model update with a specific config.\n * Used by ModelSyncOperation for event-based sync.\n *\n * @param sourceId - The source model ID\n * @param updatedData - The updated data to sync\n * @param changedFields - Fields that were changed\n * @param config - The sync configuration to use\n * @returns Sync result with success status and details\n */\n public async syncUpdateWithConfig(\n sourceId: string | number,\n updatedData: Record<string, unknown> | Model,\n changedFields: string[],\n config: SyncConfig,\n ): Promise<SyncResult> {\n try {\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: config.maxSyncDepth,\n preventCircular: config.preventCircularSync,\n };\n\n const instructions = await this.collectInstructions({\n sourceId,\n updatedData,\n changedFields,\n syncConfigs: [config],\n options,\n });\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n console.error(\n `Sync update with config failed for ${this.sourceModel.name}#${sourceId}:`,\n error,\n );\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Executes sync delete operations with a specific config.\n * Used by ModelSyncOperation for event-based sync.\n *\n * @param sourceId - The source model ID\n * @param config - The sync configuration to use\n * @returns Sync result with success status and details\n */\n public async syncDeleteWithConfig(\n sourceId: string | number,\n config: SyncConfig,\n ): Promise<SyncResult> {\n try {\n if (!config.unsetOnDelete) {\n return this.createEmptyResult();\n }\n\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: config.maxSyncDepth,\n preventCircular: config.preventCircularSync,\n };\n\n const instructions = await this.collectDeleteInstructions(sourceId, [config], options);\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n console.error(\n `Sync delete with config failed for ${this.sourceModel.name}#${sourceId}:`,\n error,\n );\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Executes sync operations for a model deletion.\n *\n * @param sourceId - The source model ID\n * @returns Sync result with success status and details\n */\n public async syncDelete(sourceId: string | number): Promise<SyncResult> {\n try {\n const syncConfigs = this.getSyncConfigs();\n\n if (syncConfigs.length === 0) {\n return this.createEmptyResult();\n }\n\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: DEFAULT_MAX_SYNC_DEPTH,\n preventCircular: true,\n };\n\n const instructions = await this.collectDeleteInstructions(sourceId, syncConfigs, options);\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n // Catch-all error handler for unexpected failures\n console.error(`Sync delete failed for ${this.sourceModel.name}#${sourceId}:`, error);\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Collects sync instructions recursively with depth limiting.\n *\n * @param payload - Data payload\n * @returns Array of sync instructions\n */\n private async collectInstructions(\n payload: CollectInstructionsPayload,\n ): Promise<SyncInstruction[]> {\n const { sourceId, updatedData, changedFields, syncConfigs, options } = payload;\n const instructions: SyncInstruction[] = [];\n\n for (const config of syncConfigs) {\n // Check if we should sync based on watched fields\n if (!this.shouldSync(config, changedFields)) {\n continue;\n }\n\n // Validate depth and circular references\n const validation = SyncContextManager.validate(\n options.currentDepth,\n options.syncChain,\n config.targetModelClass.name,\n config.maxSyncDepth,\n config.preventCircularSync,\n );\n\n if (!validation.valid) {\n console.warn(`Sync validation failed: ${validation.error}`);\n continue;\n }\n\n // Get embedded data (handle both Model instance and plain data)\n const embedData = await this.getEmbedData(updatedData, config);\n\n // Build instruction\n const instruction = this.buildUpdateInstruction(sourceId, config, embedData, options);\n\n instructions.push(instruction);\n\n // Emit syncing event\n await this.emitSyncingEvent(instruction);\n\n }\n\n return instructions;\n }\n\n /**\n * Collects delete sync instructions.\n *\n * @param sourceId - Source model ID\n * @param syncConfigs - Sync configurations\n * @param options - Instruction options\n * @returns Array of sync instructions\n */\n private async collectDeleteInstructions(\n sourceId: string | number,\n syncConfigs: SyncConfig[],\n options: SyncInstructionOptions,\n ): Promise<SyncInstruction[]> {\n const instructions: SyncInstruction[] = [];\n\n for (const config of syncConfigs) {\n if (!config.unsetOnDelete) {\n continue;\n }\n\n const validation = SyncContextManager.validate(\n options.currentDepth,\n options.syncChain,\n config.targetModelClass.name,\n config.maxSyncDepth,\n config.preventCircularSync,\n );\n\n if (!validation.valid) {\n continue;\n }\n\n const instruction = this.buildDeleteInstruction(sourceId, config, options);\n instructions.push(instruction);\n\n await this.emitSyncingEvent(instruction);\n }\n\n return instructions;\n }\n\n /**\n * Builds an update sync instruction.\n *\n * @param sourceId - Source model ID\n * @param config - Sync configuration\n * @param embedData - Embedded data to sync\n * @param options - Instruction options\n * @returns Sync instruction\n */\n private buildUpdateInstruction(\n sourceId: string | number,\n config: SyncConfig,\n embedData: Record<string, unknown>,\n options: SyncInstructionOptions,\n ): SyncInstruction {\n const targetModelClass = config.targetModelClass;\n const filter = this.buildFilter(sourceId, config);\n const update = this.buildUpdate(embedData, config);\n\n const instruction: SyncInstruction = {\n targetTable: targetModelClass.table,\n targetModel: targetModelClass.name,\n filter,\n update,\n depth: options.currentDepth,\n chain: [...options.syncChain],\n sourceModel: this.sourceModel.name,\n sourceId,\n };\n\n // Add array update metadata if needed\n if (config.isMany) {\n instruction.isArrayUpdate = true;\n instruction.arrayField = config.targetField;\n instruction.identifierField = config.identifierField;\n instruction.identifierValue = sourceId;\n }\n\n return instruction;\n }\n\n /**\n * Builds a delete sync instruction.\n *\n * @param sourceId - Source model ID\n * @param config - Sync configuration\n * @param options - Instruction options\n * @returns Sync instruction\n */\n private buildDeleteInstruction(\n sourceId: string | number,\n config: SyncConfig,\n options: SyncInstructionOptions,\n ): SyncInstruction {\n const targetModelClass = config.targetModelClass;\n const filter = this.buildFilter(sourceId, config);\n // For arrays, remove only the deleted element instead of wiping the whole field\n const update = config.isMany\n ? { $pull: { [config.targetField]: { [config.identifierField]: sourceId } } }\n : { $unset: { [config.targetField]: 1 } };\n\n return {\n targetTable: targetModelClass.table,\n targetModel: targetModelClass.name,\n filter,\n update,\n depth: options.currentDepth,\n chain: [...options.syncChain],\n sourceModel: this.sourceModel.name,\n sourceId,\n };\n }\n\n /**\n * Builds a filter for identifying target documents.\n *\n * @param sourceId - Source model ID\n * @param config - Sync configuration\n * @returns Filter object\n */\n private buildFilter(sourceId: string | number, config: SyncConfig): Record<string, unknown> {\n if (config.isMany) {\n // For arrays: { \"products.id\": productId }\n return { [`${config.targetField}.${config.identifierField}`]: sourceId };\n } else {\n // For single: { \"category.id\": categoryId }\n return { [`${config.targetField}.${config.identifierField}`]: sourceId };\n }\n }\n\n /**\n * Builds an update operation for syncing data.\n *\n * @param embedData - Embedded data to sync\n * @param config - Sync configuration\n * @returns Update operation object\n */\n private buildUpdate(\n embedData: Record<string, unknown>,\n config: SyncConfig,\n ): Record<string, unknown> {\n if (config.isMany) {\n // For arrays: use positional operator $\n return { $set: { [`${config.targetField}.$`]: embedData } };\n } else {\n // For single: direct set\n return { $set: { [config.targetField]: embedData } };\n }\n }\n\n /**\n * Executes sync instructions, once each, through the driver's dialect-aware\n * sync adapter. Instructions run in depth order and each one records its own\n * outcome, so one failing instruction never re-runs the ones that succeeded.\n *\n * @param instructions - Array of sync instructions\n * @returns Sync result\n */\n private async executeInstructions(instructions: SyncInstruction[]): Promise<SyncResult> {\n const result: SyncResult = {\n success: true,\n attempted: instructions.length,\n succeeded: 0,\n failed: 0,\n errors: [],\n depthReached: 0,\n contexts: [],\n };\n\n if (instructions.length === 0) {\n return result;\n }\n\n const adapter = this.driver.syncAdapter();\n\n for (const [depth, depthInstructions] of this.groupByDepth(instructions)) {\n result.depthReached = Math.max(result.depthReached, depth);\n\n for (const instruction of depthInstructions) {\n try {\n const affected = await adapter.executeBatch([instruction]);\n\n const context = SyncContextManager.createContext(instruction, affected);\n result.contexts.push(context);\n result.succeeded++;\n\n await this.emitSyncedEvent(context);\n } catch (error) {\n result.failed++;\n\n const errorMessage = this.formatSyncError(instruction, error);\n const syncError = new Error(errorMessage);\n\n // Preserve original error stack if available\n if (error instanceof Error && error.stack) {\n syncError.stack = error.stack;\n }\n\n result.errors.push({ instruction, error: syncError });\n\n console.error(`Sync operation failed:`, {\n sourceModel: instruction.sourceModel,\n sourceId: instruction.sourceId,\n targetModel: instruction.targetModel,\n targetTable: instruction.targetTable,\n depth: instruction.depth,\n chain: SyncContextManager.formatChain(instruction.chain),\n filter: instruction.filter,\n error: errorMessage,\n });\n }\n }\n }\n\n result.success = result.failed === 0;\n return result;\n }\n\n /**\n * Formats a sync error with detailed context.\n *\n * @param instruction - The failed instruction\n * @param error - The error that occurred\n * @returns Formatted error message\n */\n private formatSyncError(instruction: SyncInstruction, error: unknown): string {\n const baseMessage = error instanceof Error ? error.message : String(error);\n const chain = SyncContextManager.formatChain(instruction.chain);\n\n return [\n `Sync failed at depth ${instruction.depth}:`,\n `Chain: ${chain} → ${instruction.targetModel}`,\n `Source: ${instruction.sourceModel}#${instruction.sourceId}`,\n `Target: ${instruction.targetTable}`,\n `Error: ${baseMessage}`,\n ].join(\" | \");\n }\n\n /**\n * Groups instructions by depth for batch processing.\n *\n * @param instructions - Instructions to group\n * @returns Map of depth to instructions (sorted ascending)\n */\n private groupByDepth(instructions: SyncInstruction[]): Map<number, SyncInstruction[]> {\n const grouped = new Map<number, SyncInstruction[]>();\n\n for (const instruction of instructions) {\n const depth = instruction.depth;\n if (!grouped.has(depth)) {\n grouped.set(depth, []);\n }\n grouped.get(depth)!.push(instruction);\n }\n\n // Sort by depth (ascending) for sequential execution\n return new Map([...grouped.entries()].sort((a, b) => a[0] - b[0]));\n }\n\n /**\n * Checks if sync should proceed based on watched fields.\n *\n * @param config - Sync configuration\n * @param changedFields - Fields that changed\n * @returns True if sync should proceed\n */\n private shouldSync(config: SyncConfig, changedFields: string[]): boolean {\n if (config.watchFields.length === 0) {\n return true; // Watch all fields\n }\n\n return config.watchFields.some((field) => changedFields.includes(field));\n }\n\n /**\n * Gets embedded data from the source model.\n *\n * @param data - Source model data or Model instance\n * @param config - Sync configuration\n * @returns Embedded data\n */\n private async getEmbedData(\n data: Record<string, unknown> | Model,\n config: SyncConfig,\n ): Promise<Record<string, unknown>> {\n // If data is a Model instance, call the embed method on it\n if (data instanceof Model) {\n const hidden = data.self().hidden ?? [];\n const withoutHidden = (embedded: Record<string, unknown>): Record<string, unknown> =>\n Object.fromEntries(Object.entries(embedded).filter(([key]) => !hidden.includes(key)));\n\n if (Array.isArray(config.embedKey)) {\n return withoutHidden(data.only(config.embedKey));\n }\n\n if (typeof data[config.embedKey as keyof Model] !== \"function\") {\n return withoutHidden(data[config.embedKey as keyof Model] as Record<string, unknown>);\n }\n\n // The model declares an explicit `embed` column list\n if (data.self().embed) {\n return withoutHidden(data.embedData);\n }\n\n // Default: the model's public (serialised) shape, which strips hidden fields\n return data.toJSON() as Record<string, unknown>;\n }\n\n // Otherwise, return the data as-is\n return data as Record<string, unknown>;\n }\n\n /**\n * Gets sync configurations from the source model.\n *\n * @returns Array of sync configurations\n */\n private getSyncConfigs(): SyncConfig[] {\n const syncWith = (this.sourceModel as any).syncWith;\n\n if (!syncWith || !Array.isArray(syncWith)) {\n return [];\n }\n\n return syncWith.map((builder: any) =>\n typeof builder.build === \"function\" ? builder.build() : builder,\n );\n }\n\n /**\n * Emits a syncing event.\n *\n * @param instruction - The sync instruction\n */\n private async emitSyncingEvent(instruction: SyncInstruction): Promise<void> {\n const payload: SyncEventPayload = {\n sourceModel: instruction.sourceModel,\n sourceId: instruction.sourceId,\n targetModel: instruction.targetModel,\n filter: instruction.filter,\n update: instruction.update,\n affectedCount: 0, // Not yet known\n depth: instruction.depth,\n chain: instruction.chain,\n };\n\n // Emit on source model\n if (typeof (this.sourceModel as any).emitSyncEvent === \"function\") {\n await (this.sourceModel as any).emitSyncEvent(\"syncing\", payload);\n }\n }\n\n /**\n * Emits a synced event.\n *\n * @param context - The sync context\n */\n private async emitSyncedEvent(context: any): Promise<void> {\n const payload: SyncEventPayload = {\n sourceModel: context.sourceModel,\n sourceId: context.sourceId,\n targetModel: context.targetModel,\n filter: context.filter,\n update: context.update,\n affectedCount: context.affectedCount,\n depth: context.currentDepth,\n chain: context.syncChain,\n };\n\n // Emit on source model\n if (typeof (this.sourceModel as any).emitSyncEvent === \"function\") {\n await (this.sourceModel as any).emitSyncEvent(\"synced\", payload);\n }\n }\n\n /**\n * Creates an empty sync result.\n *\n * @returns Empty sync result\n */\n private createEmptyResult(): SyncResult {\n return {\n success: true,\n attempted: 0,\n succeeded: 0,\n failed: 0,\n errors: [],\n depthReached: 0,\n contexts: [],\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sync-manager.mjs","names":[],"sources":["../../../../../../../../packages/cascade/src/sync/sync-manager.ts"],"sourcesContent":["/**\n * Sync manager service for handling multi-level sync operations.\n *\n * @module cascade-next/sync/sync-manager\n */\n\nimport type { DriverContract } from \"../contracts/database-driver.contract\";\nimport { type ChildModel, Model } from \"../model/model\";\nimport { DEFAULT_MAX_SYNC_DEPTH, SyncContextManager } from \"./sync-context\";\nimport type {\n CollectInstructionsPayload,\n SyncConfig,\n SyncEventPayload,\n SyncInstruction,\n SyncInstructionOptions,\n SyncResult,\n} from \"./types\";\n\n/**\n * Manages sync operations across models with multi-level support.\n */\nexport class SyncManager {\n private readonly sourceModel: ChildModel<Model>;\n private readonly driver: DriverContract;\n\n /**\n * Creates a new sync manager.\n *\n * @param sourceModel - The source model class\n * @param driver - The database driver\n */\n public constructor(sourceModel: ChildModel<Model>, driver: DriverContract) {\n this.sourceModel = sourceModel;\n this.driver = driver;\n }\n\n /**\n * Executes sync operations for a model update.\n *\n * @param sourceId - The source model ID\n * @param updatedData - The updated data to sync (Model instance or plain data)\n * @param changedFields - Fields that were changed (for filtering)\n * @returns Sync result with success status and details\n */\n public async syncUpdate(\n sourceId: string | number,\n updatedData: Record<string, unknown> | Model,\n changedFields: string[],\n ): Promise<SyncResult> {\n try {\n const syncConfigs = this.getSyncConfigs();\n\n if (syncConfigs.length === 0) {\n return this.createEmptyResult();\n }\n\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: DEFAULT_MAX_SYNC_DEPTH,\n preventCircular: true,\n };\n\n const instructions = await this.collectInstructions({\n sourceId,\n updatedData,\n changedFields,\n syncConfigs,\n options,\n });\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n // Catch-all error handler for unexpected failures\n console.error(`Sync update failed for ${this.sourceModel.name}#${sourceId}:`, error);\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Executes sync operations for a model update with a specific config.\n * Used by ModelSyncOperation for event-based sync.\n *\n * @param sourceId - The source model ID\n * @param updatedData - The updated data to sync\n * @param changedFields - Fields that were changed\n * @param config - The sync configuration to use\n * @returns Sync result with success status and details\n */\n public async syncUpdateWithConfig(\n sourceId: string | number,\n updatedData: Record<string, unknown> | Model,\n changedFields: string[],\n config: SyncConfig,\n ): Promise<SyncResult> {\n try {\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: config.maxSyncDepth,\n preventCircular: config.preventCircularSync,\n };\n\n const instructions = await this.collectInstructions({\n sourceId,\n updatedData,\n changedFields,\n syncConfigs: [config],\n options,\n });\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n console.error(\n `Sync update with config failed for ${this.sourceModel.name}#${sourceId}:`,\n error,\n );\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Executes sync delete operations with a specific config.\n * Used by ModelSyncOperation for event-based sync.\n *\n * @param sourceId - The source model ID\n * @param config - The sync configuration to use\n * @returns Sync result with success status and details\n */\n public async syncDeleteWithConfig(\n sourceId: string | number,\n config: SyncConfig,\n ): Promise<SyncResult> {\n try {\n if (!config.unsetOnDelete) {\n return this.createEmptyResult();\n }\n\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: config.maxSyncDepth,\n preventCircular: config.preventCircularSync,\n };\n\n const instructions = await this.collectDeleteInstructions(sourceId, [config], options);\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n console.error(\n `Sync delete with config failed for ${this.sourceModel.name}#${sourceId}:`,\n error,\n );\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Executes sync operations for a model deletion.\n *\n * @param sourceId - The source model ID\n * @returns Sync result with success status and details\n */\n public async syncDelete(sourceId: string | number): Promise<SyncResult> {\n try {\n const syncConfigs = this.getSyncConfigs();\n\n if (syncConfigs.length === 0) {\n return this.createEmptyResult();\n }\n\n const options: SyncInstructionOptions = {\n currentDepth: 1,\n syncChain: [this.sourceModel.name],\n maxDepth: DEFAULT_MAX_SYNC_DEPTH,\n preventCircular: true,\n };\n\n const instructions = await this.collectDeleteInstructions(sourceId, syncConfigs, options);\n\n return await this.executeInstructions(instructions);\n } catch (error) {\n // Catch-all error handler for unexpected failures\n console.error(`Sync delete failed for ${this.sourceModel.name}#${sourceId}:`, error);\n\n return {\n success: false,\n attempted: 0,\n succeeded: 0,\n failed: 1,\n errors: [\n {\n instruction: {\n targetTable: \"\",\n targetModel: \"\",\n filter: {},\n update: {},\n depth: 0,\n chain: [this.sourceModel.name],\n sourceModel: this.sourceModel.name,\n sourceId,\n },\n error: error instanceof Error ? error : new Error(String(error)),\n },\n ],\n depthReached: 0,\n contexts: [],\n };\n }\n }\n\n /**\n * Collects sync instructions recursively with depth limiting.\n *\n * @param payload - Data payload\n * @returns Array of sync instructions\n */\n private async collectInstructions(\n payload: CollectInstructionsPayload,\n ): Promise<SyncInstruction[]> {\n const { sourceId, updatedData, changedFields, syncConfigs, options } = payload;\n const instructions: SyncInstruction[] = [];\n\n for (const config of syncConfigs) {\n // Check if we should sync based on watched fields\n if (!this.shouldSync(config, changedFields)) {\n continue;\n }\n\n // Validate depth and circular references\n const validation = SyncContextManager.validate(\n options.currentDepth,\n options.syncChain,\n config.targetModelClass.name,\n config.maxSyncDepth,\n config.preventCircularSync,\n );\n\n if (!validation.valid) {\n console.warn(`Sync validation failed: ${validation.error}`);\n continue;\n }\n\n // Get embedded data (handle both Model instance and plain data)\n const embedData = await this.getEmbedData(updatedData, config);\n\n // Build instruction\n const instruction = this.buildUpdateInstruction(sourceId, config, embedData, options);\n\n instructions.push(instruction);\n\n // Emit syncing event\n await this.emitSyncingEvent(instruction);\n }\n\n return instructions;\n }\n\n /**\n * Collects delete sync instructions.\n *\n * @param sourceId - Source model ID\n * @param syncConfigs - Sync configurations\n * @param options - Instruction options\n * @returns Array of sync instructions\n */\n private async collectDeleteInstructions(\n sourceId: string | number,\n syncConfigs: SyncConfig[],\n options: SyncInstructionOptions,\n ): Promise<SyncInstruction[]> {\n const instructions: SyncInstruction[] = [];\n\n for (const config of syncConfigs) {\n if (!config.unsetOnDelete) {\n continue;\n }\n\n const validation = SyncContextManager.validate(\n options.currentDepth,\n options.syncChain,\n config.targetModelClass.name,\n config.maxSyncDepth,\n config.preventCircularSync,\n );\n\n if (!validation.valid) {\n continue;\n }\n\n const instruction = this.buildDeleteInstruction(sourceId, config, options);\n instructions.push(instruction);\n\n await this.emitSyncingEvent(instruction);\n }\n\n return instructions;\n }\n\n /**\n * Builds an update sync instruction.\n *\n * @param sourceId - Source model ID\n * @param config - Sync configuration\n * @param embedData - Embedded data to sync\n * @param options - Instruction options\n * @returns Sync instruction\n */\n private buildUpdateInstruction(\n sourceId: string | number,\n config: SyncConfig,\n embedData: Record<string, unknown>,\n options: SyncInstructionOptions,\n ): SyncInstruction {\n const targetModelClass = config.targetModelClass;\n const filter = this.buildFilter(sourceId, config);\n const update = this.buildUpdate(embedData, config);\n\n const instruction: SyncInstruction = {\n targetTable: targetModelClass.table,\n targetModel: targetModelClass.name,\n filter,\n update,\n depth: options.currentDepth,\n chain: [...options.syncChain],\n sourceModel: this.sourceModel.name,\n sourceId,\n };\n\n // Add array update metadata if needed\n if (config.isMany) {\n instruction.isArrayUpdate = true;\n instruction.arrayField = config.targetField;\n instruction.identifierField = config.identifierField;\n instruction.identifierValue = sourceId;\n }\n\n return instruction;\n }\n\n /**\n * Builds a delete sync instruction.\n *\n * @param sourceId - Source model ID\n * @param config - Sync configuration\n * @param options - Instruction options\n * @returns Sync instruction\n */\n private buildDeleteInstruction(\n sourceId: string | number,\n config: SyncConfig,\n options: SyncInstructionOptions,\n ): SyncInstruction {\n const targetModelClass = config.targetModelClass;\n const filter = this.buildFilter(sourceId, config);\n // For arrays, remove only the deleted element instead of wiping the whole field\n const update = config.isMany\n ? { $pull: { [config.targetField]: { [config.identifierField]: sourceId } } }\n : { $unset: { [config.targetField]: 1 } };\n\n return {\n targetTable: targetModelClass.table,\n targetModel: targetModelClass.name,\n filter,\n update,\n depth: options.currentDepth,\n chain: [...options.syncChain],\n sourceModel: this.sourceModel.name,\n sourceId,\n };\n }\n\n /**\n * Builds a filter for identifying target documents.\n *\n * @param sourceId - Source model ID\n * @param config - Sync configuration\n * @returns Filter object\n */\n private buildFilter(sourceId: string | number, config: SyncConfig): Record<string, unknown> {\n if (config.isMany) {\n // For arrays: { \"products.id\": productId }\n return { [`${config.targetField}.${config.identifierField}`]: sourceId };\n } else {\n // For single: { \"category.id\": categoryId }\n return { [`${config.targetField}.${config.identifierField}`]: sourceId };\n }\n }\n\n /**\n * Builds an update operation for syncing data.\n *\n * @param embedData - Embedded data to sync\n * @param config - Sync configuration\n * @returns Update operation object\n */\n private buildUpdate(\n embedData: Record<string, unknown>,\n config: SyncConfig,\n ): Record<string, unknown> {\n if (config.isMany) {\n // For arrays: use positional operator $\n return { $set: { [`${config.targetField}.$`]: embedData } };\n } else {\n // For single: direct set\n return { $set: { [config.targetField]: embedData } };\n }\n }\n\n /**\n * Executes sync instructions, once each, through the driver's dialect-aware\n * sync adapter. Instructions run in depth order and each one records its own\n * outcome, so one failing instruction never re-runs the ones that succeeded.\n *\n * @param instructions - Array of sync instructions\n * @returns Sync result\n */\n private async executeInstructions(instructions: SyncInstruction[]): Promise<SyncResult> {\n const result: SyncResult = {\n success: true,\n attempted: instructions.length,\n succeeded: 0,\n failed: 0,\n errors: [],\n depthReached: 0,\n contexts: [],\n };\n\n if (instructions.length === 0) {\n return result;\n }\n\n const adapter = this.driver.syncAdapter();\n\n for (const [depth, depthInstructions] of this.groupByDepth(instructions)) {\n result.depthReached = Math.max(result.depthReached, depth);\n\n for (const instruction of depthInstructions) {\n try {\n const affected = await adapter.executeBatch([instruction]);\n\n const context = SyncContextManager.createContext(instruction, affected);\n result.contexts.push(context);\n result.succeeded++;\n\n await this.emitSyncedEvent(context);\n } catch (error) {\n result.failed++;\n\n const errorMessage = this.formatSyncError(instruction, error);\n const syncError = new Error(errorMessage);\n\n // Preserve original error stack if available\n if (error instanceof Error && error.stack) {\n syncError.stack = error.stack;\n }\n\n result.errors.push({ instruction, error: syncError });\n\n console.error(`Sync operation failed:`, {\n sourceModel: instruction.sourceModel,\n sourceId: instruction.sourceId,\n targetModel: instruction.targetModel,\n targetTable: instruction.targetTable,\n depth: instruction.depth,\n chain: SyncContextManager.formatChain(instruction.chain),\n filter: instruction.filter,\n error: errorMessage,\n });\n }\n }\n }\n\n result.success = result.failed === 0;\n return result;\n }\n\n /**\n * Formats a sync error with detailed context.\n *\n * @param instruction - The failed instruction\n * @param error - The error that occurred\n * @returns Formatted error message\n */\n private formatSyncError(instruction: SyncInstruction, error: unknown): string {\n const baseMessage = error instanceof Error ? error.message : String(error);\n const chain = SyncContextManager.formatChain(instruction.chain);\n\n return [\n `Sync failed at depth ${instruction.depth}:`,\n `Chain: ${chain} → ${instruction.targetModel}`,\n `Source: ${instruction.sourceModel}#${instruction.sourceId}`,\n `Target: ${instruction.targetTable}`,\n `Error: ${baseMessage}`,\n ].join(\" | \");\n }\n\n /**\n * Groups instructions by depth for batch processing.\n *\n * @param instructions - Instructions to group\n * @returns Map of depth to instructions (sorted ascending)\n */\n private groupByDepth(instructions: SyncInstruction[]): Map<number, SyncInstruction[]> {\n const grouped = new Map<number, SyncInstruction[]>();\n\n for (const instruction of instructions) {\n const depth = instruction.depth;\n if (!grouped.has(depth)) {\n grouped.set(depth, []);\n }\n grouped.get(depth)!.push(instruction);\n }\n\n // Sort by depth (ascending) for sequential execution\n return new Map([...grouped.entries()].sort((a, b) => a[0] - b[0]));\n }\n\n /**\n * Checks if sync should proceed based on watched fields.\n *\n * @param config - Sync configuration\n * @param changedFields - Fields that changed\n * @returns True if sync should proceed\n */\n private shouldSync(config: SyncConfig, changedFields: string[]): boolean {\n if (config.watchFields.length === 0) {\n return true; // Watch all fields\n }\n\n return config.watchFields.some((field) => changedFields.includes(field));\n }\n\n /**\n * Gets embedded data from the source model.\n *\n * @param data - Source model data or Model instance\n * @param config - Sync configuration\n * @returns Embedded data\n */\n private async getEmbedData(\n data: Record<string, unknown> | Model,\n config: SyncConfig,\n ): Promise<Record<string, unknown>> {\n // If data is a Model instance, call the embed method on it\n if (data instanceof Model) {\n const hidden = data.self().hidden ?? [];\n const withoutHidden = (embedded: Record<string, unknown>): Record<string, unknown> =>\n Object.fromEntries(Object.entries(embedded).filter(([key]) => !hidden.includes(key)));\n\n if (Array.isArray(config.embedKey)) {\n return withoutHidden(data.only(config.embedKey));\n }\n\n if (typeof data[config.embedKey as keyof Model] !== \"function\") {\n return withoutHidden(data[config.embedKey as keyof Model] as Record<string, unknown>);\n }\n\n // The model declares an explicit `embed` column list\n if (data.self().embed) {\n return withoutHidden(data.embedData);\n }\n\n // Default: the model's public (serialised) shape, which strips hidden fields\n return data.toJSON() as Record<string, unknown>;\n }\n\n // Otherwise, return the data as-is\n return data as Record<string, unknown>;\n }\n\n /**\n * Gets sync configurations from the source model.\n *\n * @returns Array of sync configurations\n */\n private getSyncConfigs(): SyncConfig[] {\n const syncWith = (this.sourceModel as any).syncWith;\n\n if (!syncWith || !Array.isArray(syncWith)) {\n return [];\n }\n\n return syncWith.map((builder: any) =>\n typeof builder.build === \"function\" ? builder.build() : builder,\n );\n }\n\n /**\n * Emits a syncing event.\n *\n * @param instruction - The sync instruction\n */\n private async emitSyncingEvent(instruction: SyncInstruction): Promise<void> {\n const payload: SyncEventPayload = {\n sourceModel: instruction.sourceModel,\n sourceId: instruction.sourceId,\n targetModel: instruction.targetModel,\n filter: instruction.filter,\n update: instruction.update,\n affectedCount: 0, // Not yet known\n depth: instruction.depth,\n chain: instruction.chain,\n };\n\n // Emit on source model\n if (typeof (this.sourceModel as any).emitSyncEvent === \"function\") {\n await (this.sourceModel as any).emitSyncEvent(\"syncing\", payload);\n }\n }\n\n /**\n * Emits a synced event.\n *\n * @param context - The sync context\n */\n private async emitSyncedEvent(context: any): Promise<void> {\n const payload: SyncEventPayload = {\n sourceModel: context.sourceModel,\n sourceId: context.sourceId,\n targetModel: context.targetModel,\n filter: context.filter,\n update: context.update,\n affectedCount: context.affectedCount,\n depth: context.currentDepth,\n chain: context.syncChain,\n };\n\n // Emit on source model\n if (typeof (this.sourceModel as any).emitSyncEvent === \"function\") {\n await (this.sourceModel as any).emitSyncEvent(\"synced\", payload);\n }\n }\n\n /**\n * Creates an empty sync result.\n *\n * @returns Empty sync result\n */\n private createEmptyResult(): SyncResult {\n return {\n success: true,\n attempted: 0,\n succeeded: 0,\n failed: 0,\n errors: [],\n depthReached: 0,\n contexts: [],\n };\n }\n}\n"],"mappings":"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"}