@warlock.js/cascade 5.19.1 → 5.21.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 (194) hide show
  1. package/CHANGELOG.md +36 -1
  2. package/cjs/index.cjs +2356 -1039
  3. package/cjs/index.cjs.map +1 -1
  4. package/esm/context/database-transaction-context.d.mts +10 -0
  5. package/esm/context/database-transaction-context.d.mts.map +1 -1
  6. package/esm/context/database-transaction-context.mjs +17 -0
  7. package/esm/context/database-transaction-context.mjs.map +1 -1
  8. package/esm/contracts/query-builder.contract.d.mts +30 -3
  9. package/esm/contracts/query-builder.contract.d.mts.map +1 -1
  10. package/esm/data-source/data-source-registry.d.mts +33 -1
  11. package/esm/data-source/data-source-registry.d.mts.map +1 -1
  12. package/esm/data-source/data-source-registry.mjs +75 -5
  13. package/esm/data-source/data-source-registry.mjs.map +1 -1
  14. package/esm/database-dirty-tracker.d.mts +5 -0
  15. package/esm/database-dirty-tracker.d.mts.map +1 -1
  16. package/esm/database-dirty-tracker.mjs +16 -0
  17. package/esm/database-dirty-tracker.mjs.map +1 -1
  18. package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
  19. package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
  20. package/esm/drivers/mongodb/mongodb-driver.mjs +58 -19
  21. package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
  22. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
  23. package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
  24. package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
  25. package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
  26. package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
  27. package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
  28. package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
  29. package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
  30. package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
  31. package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
  32. package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
  33. package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
  34. package/esm/drivers/postgres/index.d.mts +2 -3
  35. package/esm/drivers/postgres/index.mjs +1 -2
  36. package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
  37. package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
  38. package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
  39. package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
  40. package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
  41. package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
  42. package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
  43. package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
  44. package/esm/drivers/postgres/postgres-driver.mjs +196 -58
  45. package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
  46. package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
  47. package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
  48. package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
  49. package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
  50. package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
  51. package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
  52. package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
  53. package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
  54. package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
  55. package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
  56. package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
  57. package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
  58. package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
  59. package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
  60. package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
  61. package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
  62. package/esm/events/model-events.d.mts +1 -1
  63. package/esm/events/model-events.d.mts.map +1 -1
  64. package/esm/events/model-events.mjs.map +1 -1
  65. package/esm/index.d.mts +6 -6
  66. package/esm/index.mjs +5 -5
  67. package/esm/migration/column-builder.d.mts.map +1 -1
  68. package/esm/migration/column-builder.mjs +2 -2
  69. package/esm/migration/column-builder.mjs.map +1 -1
  70. package/esm/migration/index.d.mts +2 -2
  71. package/esm/migration/index.mjs +1 -1
  72. package/esm/migration/migration-order.mjs +5 -2
  73. package/esm/migration/migration-order.mjs.map +1 -1
  74. package/esm/migration/migration-runner.d.mts +50 -5
  75. package/esm/migration/migration-runner.d.mts.map +1 -1
  76. package/esm/migration/migration-runner.mjs +370 -147
  77. package/esm/migration/migration-runner.mjs.map +1 -1
  78. package/esm/migration/migration.d.mts +36 -3
  79. package/esm/migration/migration.d.mts.map +1 -1
  80. package/esm/migration/migration.mjs +62 -5
  81. package/esm/migration/migration.mjs.map +1 -1
  82. package/esm/model/methods/accessor-methods.mjs +1 -3
  83. package/esm/model/methods/accessor-methods.mjs.map +1 -1
  84. package/esm/model/methods/delete-methods.mjs +6 -2
  85. package/esm/model/methods/delete-methods.mjs.map +1 -1
  86. package/esm/model/methods/hydration-methods.mjs +16 -2
  87. package/esm/model/methods/hydration-methods.mjs.map +1 -1
  88. package/esm/model/methods/instance-event-methods.mjs +2 -1
  89. package/esm/model/methods/instance-event-methods.mjs.map +1 -1
  90. package/esm/model/methods/meta-methods.mjs +53 -16
  91. package/esm/model/methods/meta-methods.mjs.map +1 -1
  92. package/esm/model/methods/query-methods.mjs +86 -15
  93. package/esm/model/methods/query-methods.mjs.map +1 -1
  94. package/esm/model/methods/static-event-methods.mjs +16 -1
  95. package/esm/model/methods/static-event-methods.mjs.map +1 -1
  96. package/esm/model/methods/write-methods.d.mts.map +1 -1
  97. package/esm/model/methods/write-methods.mjs +50 -50
  98. package/esm/model/methods/write-methods.mjs.map +1 -1
  99. package/esm/model/model.d.mts +16 -1
  100. package/esm/model/model.d.mts.map +1 -1
  101. package/esm/model/model.mjs +39 -5
  102. package/esm/model/model.mjs.map +1 -1
  103. package/esm/model/register-model.d.mts +6 -1
  104. package/esm/model/register-model.d.mts.map +1 -1
  105. package/esm/model/register-model.mjs +16 -1
  106. package/esm/model/register-model.mjs.map +1 -1
  107. package/esm/query-builder/query-builder.d.mts +21 -2
  108. package/esm/query-builder/query-builder.d.mts.map +1 -1
  109. package/esm/query-builder/query-builder.mjs +97 -20
  110. package/esm/query-builder/query-builder.mjs.map +1 -1
  111. package/esm/relations/key-conventions.mjs +13 -3
  112. package/esm/relations/key-conventions.mjs.map +1 -1
  113. package/esm/relations/pivot-operations.mjs +1 -1
  114. package/esm/relations/relation-loader.d.mts +15 -5
  115. package/esm/relations/relation-loader.d.mts.map +1 -1
  116. package/esm/relations/relation-loader.mjs +51 -16
  117. package/esm/relations/relation-loader.mjs.map +1 -1
  118. package/esm/remover/database-remover.d.mts.map +1 -1
  119. package/esm/remover/database-remover.mjs +21 -7
  120. package/esm/remover/database-remover.mjs.map +1 -1
  121. package/esm/restorer/database-restorer.d.mts +12 -2
  122. package/esm/restorer/database-restorer.d.mts.map +1 -1
  123. package/esm/restorer/database-restorer.mjs +43 -13
  124. package/esm/restorer/database-restorer.mjs.map +1 -1
  125. package/esm/sync/model-events.mjs +63 -3
  126. package/esm/sync/model-events.mjs.map +1 -1
  127. package/esm/sync/model-sync-operation.d.mts.map +1 -1
  128. package/esm/sync/model-sync-operation.mjs +3 -2
  129. package/esm/sync/model-sync-operation.mjs.map +1 -1
  130. package/esm/sync/sync-manager.d.mts +3 -42
  131. package/esm/sync/sync-manager.d.mts.map +1 -1
  132. package/esm/sync/sync-manager.mjs +37 -126
  133. package/esm/sync/sync-manager.mjs.map +1 -1
  134. package/esm/transactions/after-commit.d.mts +24 -0
  135. package/esm/transactions/after-commit.d.mts.map +1 -0
  136. package/esm/transactions/after-commit.mjs +47 -0
  137. package/esm/transactions/after-commit.mjs.map +1 -0
  138. package/esm/utils/connect-to-database.d.mts.map +1 -1
  139. package/esm/utils/connect-to-database.mjs +4 -4
  140. package/esm/utils/connect-to-database.mjs.map +1 -1
  141. package/esm/utils/database-writer.utils.d.mts +1 -1
  142. package/esm/utils/database-writer.utils.d.mts.map +1 -1
  143. package/esm/utils/database-writer.utils.mjs +1 -0
  144. package/esm/utils/database-writer.utils.mjs.map +1 -1
  145. package/esm/utils/define-model.d.mts.map +1 -1
  146. package/esm/utils/define-model.mjs +2 -10
  147. package/esm/utils/define-model.mjs.map +1 -1
  148. package/esm/utils/is-valid-date-value.mjs +3 -2
  149. package/esm/utils/is-valid-date-value.mjs.map +1 -1
  150. package/esm/utils/once-connected.d.mts.map +1 -1
  151. package/esm/utils/once-connected.mjs +12 -2
  152. package/esm/utils/once-connected.mjs.map +1 -1
  153. package/esm/validation/mutators/embed-mutator.mjs +19 -11
  154. package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
  155. package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
  156. package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
  157. package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
  158. package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
  159. package/esm/validation/rules/database-model-rule.mjs +6 -5
  160. package/esm/validation/rules/database-model-rule.mjs.map +1 -1
  161. package/esm/validation/rules/exists-rule.mjs +6 -4
  162. package/esm/validation/rules/exists-rule.mjs.map +1 -1
  163. package/esm/validation/rules/unique-rule.mjs +5 -2
  164. package/esm/validation/rules/unique-rule.mjs.map +1 -1
  165. package/esm/validation/validators/embed-validator.d.mts.map +1 -1
  166. package/esm/validation/validators/embed-validator.mjs +1 -1
  167. package/esm/validation/validators/embed-validator.mjs.map +1 -1
  168. package/esm/writer/database-writer.d.mts +27 -0
  169. package/esm/writer/database-writer.d.mts.map +1 -1
  170. package/esm/writer/database-writer.mjs +84 -17
  171. package/esm/writer/database-writer.mjs.map +1 -1
  172. package/llms-full.txt +2783 -2766
  173. package/llms.txt +15 -15
  174. package/package.json +6 -6
  175. package/skills/aggregate-data/SKILL.md +165 -165
  176. package/skills/alter-migration/SKILL.md +531 -531
  177. package/skills/configure-delete-strategy/SKILL.md +130 -130
  178. package/skills/define-model/SKILL.md +205 -205
  179. package/skills/define-relations/SKILL.md +171 -171
  180. package/skills/index.md +23 -0
  181. package/skills/manage-data-sources/SKILL.md +140 -140
  182. package/skills/manage-transactions/SKILL.md +208 -191
  183. package/skills/paginate-results/SKILL.md +122 -122
  184. package/skills/perform-atomic-ops/SKILL.md +156 -156
  185. package/skills/query-data/SKILL.md +213 -213
  186. package/skills/run-cascade-cli/SKILL.md +125 -125
  187. package/skills/search-by-vector/SKILL.md +127 -127
  188. package/skills/subscribe-to-model-events/SKILL.md +148 -148
  189. package/skills/track-changes/SKILL.md +127 -127
  190. package/skills/write-migration/SKILL.md +206 -206
  191. package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
  192. package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
  193. package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
  194. package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"sync-manager.mjs","names":[],"sources":["../../../../../../../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 // Recursively collect instructions for next level\n if (SyncContextManager.canSyncDeeper(options.currentDepth, config.maxSyncDepth)) {\n const nextLevelInstructions = await this.collectNextLevelInstructions(\n instruction,\n embedData,\n changedFields,\n config,\n options,\n );\n instructions.push(...nextLevelInstructions);\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 * Collects instructions for the next level in the sync chain.\n *\n * @param parentInstruction - The parent instruction\n * @param embedData - Embedded data from parent\n * @param changedFields - Changed fields\n * @param parentConfig - Parent sync config\n * @param parentOptions - Parent instruction options\n * @returns Array of next-level sync instructions\n */\n private async collectNextLevelInstructions(\n parentInstruction: SyncInstruction,\n embedData: Record<string, unknown>,\n changedFields: string[],\n parentConfig: SyncConfig,\n parentOptions: SyncInstructionOptions,\n ): Promise<SyncInstruction[]> {\n const targetModelClass = parentConfig.targetModelClass;\n const targetSyncConfigs = this.getSyncConfigsForModel(targetModelClass);\n\n if (targetSyncConfigs.length === 0) {\n return [];\n }\n\n const nextOptions: SyncInstructionOptions = {\n currentDepth: parentOptions.currentDepth + 1,\n syncChain: SyncContextManager.extendChain(parentOptions.syncChain, targetModelClass.name),\n maxDepth: Math.min(parentConfig.maxSyncDepth, parentOptions.maxDepth),\n preventCircular: parentOptions.preventCircular && parentConfig.preventCircularSync,\n };\n\n // Get the ID from embed data for next level\n const sourceId = embedData[parentConfig.identifierField] as string | number;\n\n return await this.collectInstructions({\n sourceId,\n updatedData: embedData,\n changedFields,\n syncConfigs: targetSyncConfigs,\n options: nextOptions,\n });\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 const update = { $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 with batch optimization.\n * Groups by depth and target table for optimal batching.\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 // Group instructions by depth for sequential execution\n const instructionsByDepth = this.groupByDepth(instructions);\n\n for (const [depth, depthInstructions] of instructionsByDepth) {\n result.depthReached = Math.max(result.depthReached, depth);\n\n // Further group by target table for better batching\n const instructionsByTable = this.groupByTable(depthInstructions);\n\n for (const [table, tableInstructions] of instructionsByTable) {\n try {\n // Try batch execution first (all instructions for this table)\n await this.executeBatch(tableInstructions, result);\n } catch (batchError) {\n // Fallback to individual execution on batch failure\n console.warn(\n `Batch execution failed for table ${table} at depth ${depth}, falling back to individual execution`,\n );\n await this.executeIndividual(tableInstructions, result);\n }\n }\n }\n\n result.success = result.failed === 0;\n return result;\n }\n\n /**\n * Executes instructions in batch.\n *\n * @param instructions - Instructions to execute\n * @param result - Result object to update\n */\n private async executeBatch(instructions: SyncInstruction[], result: SyncResult): Promise<void> {\n for (const instruction of instructions) {\n try {\n const updateResult = await this.driver.updateMany(\n instruction.targetTable,\n instruction.filter,\n instruction.update,\n );\n\n const context = SyncContextManager.createContext(instruction, updateResult.modifiedCount);\n result.contexts.push(context);\n result.succeeded++;\n\n await this.emitSyncedEvent(context);\n } catch (error) {\n throw error; // Re-throw for batch fallback\n }\n }\n }\n\n /**\n * Executes instructions individually (fallback).\n * Provides detailed error reporting for each failed instruction.\n *\n * @param instructions - Instructions to execute\n * @param result - Result object to update\n */\n private async executeIndividual(\n instructions: SyncInstruction[],\n result: SyncResult,\n ): Promise<void> {\n for (const instruction of instructions) {\n try {\n const updateResult = await this.driver.updateMany(\n instruction.targetTable,\n instruction.filter,\n instruction.update,\n );\n\n const context = SyncContextManager.createContext(instruction, updateResult.modifiedCount);\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({\n instruction,\n error: syncError,\n });\n\n // Log detailed error for debugging\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 /**\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 * Groups instructions by target table for batch optimization.\n *\n * @param instructions - Instructions to group\n * @returns Map of table name to instructions\n */\n private groupByTable(instructions: SyncInstruction[]): Map<string, SyncInstruction[]> {\n const grouped = new Map<string, SyncInstruction[]>();\n\n for (const instruction of instructions) {\n const table = instruction.targetTable;\n if (!grouped.has(table)) {\n grouped.set(table, []);\n }\n grouped.get(table)!.push(instruction);\n }\n\n return grouped;\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 if (Array.isArray(config.embedKey)) {\n return data.only(config.embedKey);\n }\n\n if (typeof data[config.embedKey as keyof Model] !== \"function\") {\n return data[config.embedKey as keyof Model] as Record<string, unknown>;\n }\n // Fallback: use embedData() if available\n if (typeof data.embedData === \"function\") {\n return data.embedData;\n }\n // Last resort: return model data\n return data.data;\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 * Gets sync configurations for a specific model.\n *\n * @param modelClass - The model class\n * @returns Array of sync configurations\n */\n private getSyncConfigsForModel(modelClass: ChildModel<Model>): SyncConfig[] {\n const syncWith = (modelClass 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":["../../../../../../../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":";;;;;;;AAqBA,IAAa,cAAb,MAAyB;CACvB,AAAiB;CACjB,AAAiB;;;;;;;CAQjB,AAAO,YAAY,aAAgC,QAAwB;EACzE,KAAK,cAAc;EACnB,KAAK,SAAS;CAChB;;;;;;;;;CAUA,MAAa,WACX,UACA,aACA,eACqB;EACrB,IAAI;GACF,MAAM,cAAc,KAAK,eAAe;GAExC,IAAI,YAAY,WAAW,GACzB,OAAO,KAAK,kBAAkB;GAGhC,MAAM,UAAkC;IACtC,cAAc;IACd,WAAW,CAAC,KAAK,YAAY,IAAI;IACjC;IACA,iBAAiB;GACnB;GAEA,MAAM,eAAe,MAAM,KAAK,oBAAoB;IAClD;IACA;IACA;IACA;IACA;GACF,CAAC;GAED,OAAO,MAAM,KAAK,oBAAoB,YAAY;EACpD,SAAS,OAAO;GAEd,QAAQ,MAAM,0BAA0B,KAAK,YAAY,KAAK,GAAG,SAAS,IAAI,KAAK;GAEnF,OAAO;IACL,SAAS;IACT,WAAW;IACX,WAAW;IACX,QAAQ;IACR,QAAQ,CACN;KACE,aAAa;MACX,aAAa;MACb,aAAa;MACb,QAAQ,CAAC;MACT,QAAQ,CAAC;MACT,OAAO;MACP,OAAO,CAAC,KAAK,YAAY,IAAI;MAC7B,aAAa,KAAK,YAAY;MAC9B;KACF;KACA,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CACF;IACA,cAAc;IACd,UAAU,CAAC;GACb;EACF;CACF;;;;;;;;;;;CAYA,MAAa,qBACX,UACA,aACA,eACA,QACqB;EACrB,IAAI;GACF,MAAM,UAAkC;IACtC,cAAc;IACd,WAAW,CAAC,KAAK,YAAY,IAAI;IACjC,UAAU,OAAO;IACjB,iBAAiB,OAAO;GAC1B;GAEA,MAAM,eAAe,MAAM,KAAK,oBAAoB;IAClD;IACA;IACA;IACA,aAAa,CAAC,MAAM;IACpB;GACF,CAAC;GAED,OAAO,MAAM,KAAK,oBAAoB,YAAY;EACpD,SAAS,OAAO;GACd,QAAQ,MACN,sCAAsC,KAAK,YAAY,KAAK,GAAG,SAAS,IACxE,KACF;GAEA,OAAO;IACL,SAAS;IACT,WAAW;IACX,WAAW;IACX,QAAQ;IACR,QAAQ,CACN;KACE,aAAa;MACX,aAAa;MACb,aAAa;MACb,QAAQ,CAAC;MACT,QAAQ,CAAC;MACT,OAAO;MACP,OAAO,CAAC,KAAK,YAAY,IAAI;MAC7B,aAAa,KAAK,YAAY;MAC9B;KACF;KACA,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CACF;IACA,cAAc;IACd,UAAU,CAAC;GACb;EACF;CACF;;;;;;;;;CAUA,MAAa,qBACX,UACA,QACqB;EACrB,IAAI;GACF,IAAI,CAAC,OAAO,eACV,OAAO,KAAK,kBAAkB;GAGhC,MAAM,UAAkC;IACtC,cAAc;IACd,WAAW,CAAC,KAAK,YAAY,IAAI;IACjC,UAAU,OAAO;IACjB,iBAAiB,OAAO;GAC1B;GAEA,MAAM,eAAe,MAAM,KAAK,0BAA0B,UAAU,CAAC,MAAM,GAAG,OAAO;GAErF,OAAO,MAAM,KAAK,oBAAoB,YAAY;EACpD,SAAS,OAAO;GACd,QAAQ,MACN,sCAAsC,KAAK,YAAY,KAAK,GAAG,SAAS,IACxE,KACF;GAEA,OAAO;IACL,SAAS;IACT,WAAW;IACX,WAAW;IACX,QAAQ;IACR,QAAQ,CACN;KACE,aAAa;MACX,aAAa;MACb,aAAa;MACb,QAAQ,CAAC;MACT,QAAQ,CAAC;MACT,OAAO;MACP,OAAO,CAAC,KAAK,YAAY,IAAI;MAC7B,aAAa,KAAK,YAAY;MAC9B;KACF;KACA,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CACF;IACA,cAAc;IACd,UAAU,CAAC;GACb;EACF;CACF;;;;;;;CAQA,MAAa,WAAW,UAAgD;EACtE,IAAI;GACF,MAAM,cAAc,KAAK,eAAe;GAExC,IAAI,YAAY,WAAW,GACzB,OAAO,KAAK,kBAAkB;GAGhC,MAAM,UAAkC;IACtC,cAAc;IACd,WAAW,CAAC,KAAK,YAAY,IAAI;IACjC;IACA,iBAAiB;GACnB;GAEA,MAAM,eAAe,MAAM,KAAK,0BAA0B,UAAU,aAAa,OAAO;GAExF,OAAO,MAAM,KAAK,oBAAoB,YAAY;EACpD,SAAS,OAAO;GAEd,QAAQ,MAAM,0BAA0B,KAAK,YAAY,KAAK,GAAG,SAAS,IAAI,KAAK;GAEnF,OAAO;IACL,SAAS;IACT,WAAW;IACX,WAAW;IACX,QAAQ;IACR,QAAQ,CACN;KACE,aAAa;MACX,aAAa;MACb,aAAa;MACb,QAAQ,CAAC;MACT,QAAQ,CAAC;MACT,OAAO;MACP,OAAO,CAAC,KAAK,YAAY,IAAI;MAC7B,aAAa,KAAK,YAAY;MAC9B;KACF;KACA,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;IACjE,CACF;IACA,cAAc;IACd,UAAU,CAAC;GACb;EACF;CACF;;;;;;;CAQA,MAAc,oBACZ,SAC4B;EAC5B,MAAM,EAAE,UAAU,aAAa,eAAe,aAAa,YAAY;EACvE,MAAM,eAAkC,CAAC;EAEzC,KAAK,MAAM,UAAU,aAAa;GAEhC,IAAI,CAAC,KAAK,WAAW,QAAQ,aAAa,GACxC;GAIF,MAAM,aAAa,mBAAmB,SACpC,QAAQ,cACR,QAAQ,WACR,OAAO,iBAAiB,MACxB,OAAO,cACP,OAAO,mBACT;GAEA,IAAI,CAAC,WAAW,OAAO;IACrB,QAAQ,KAAK,2BAA2B,WAAW,OAAO;IAC1D;GACF;GAGA,MAAM,YAAY,MAAM,KAAK,aAAa,aAAa,MAAM;GAG7D,MAAM,cAAc,KAAK,uBAAuB,UAAU,QAAQ,WAAW,OAAO;GAEpF,aAAa,KAAK,WAAW;GAG7B,MAAM,KAAK,iBAAiB,WAAW;EAEzC;EAEA,OAAO;CACT;;;;;;;;;CAUA,MAAc,0BACZ,UACA,aACA,SAC4B;EAC5B,MAAM,eAAkC,CAAC;EAEzC,KAAK,MAAM,UAAU,aAAa;GAChC,IAAI,CAAC,OAAO,eACV;GAWF,IAAI,CARe,mBAAmB,SACpC,QAAQ,cACR,QAAQ,WACR,OAAO,iBAAiB,MACxB,OAAO,cACP,OAAO,mBAGK,CAAC,CAAC,OACd;GAGF,MAAM,cAAc,KAAK,uBAAuB,UAAU,QAAQ,OAAO;GACzE,aAAa,KAAK,WAAW;GAE7B,MAAM,KAAK,iBAAiB,WAAW;EACzC;EAEA,OAAO;CACT;;;;;;;;;;CAWA,AAAQ,uBACN,UACA,QACA,WACA,SACiB;EACjB,MAAM,mBAAmB,OAAO;EAChC,MAAM,SAAS,KAAK,YAAY,UAAU,MAAM;EAChD,MAAM,SAAS,KAAK,YAAY,WAAW,MAAM;EAEjD,MAAM,cAA+B;GACnC,aAAa,iBAAiB;GAC9B,aAAa,iBAAiB;GAC9B;GACA;GACA,OAAO,QAAQ;GACf,OAAO,CAAC,GAAG,QAAQ,SAAS;GAC5B,aAAa,KAAK,YAAY;GAC9B;EACF;EAGA,IAAI,OAAO,QAAQ;GACjB,YAAY,gBAAgB;GAC5B,YAAY,aAAa,OAAO;GAChC,YAAY,kBAAkB,OAAO;GACrC,YAAY,kBAAkB;EAChC;EAEA,OAAO;CACT;;;;;;;;;CAUA,AAAQ,uBACN,UACA,QACA,SACiB;EACjB,MAAM,mBAAmB,OAAO;EAChC,MAAM,SAAS,KAAK,YAAY,UAAU,MAAM;EAEhD,MAAM,SAAS,OAAO,SAClB,EAAE,OAAO,GAAG,OAAO,cAAc,GAAG,OAAO,kBAAkB,SAAS,EAAE,EAAE,IAC1E,EAAE,QAAQ,GAAG,OAAO,cAAc,EAAE,EAAE;EAE1C,OAAO;GACL,aAAa,iBAAiB;GAC9B,aAAa,iBAAiB;GAC9B;GACA;GACA,OAAO,QAAQ;GACf,OAAO,CAAC,GAAG,QAAQ,SAAS;GAC5B,aAAa,KAAK,YAAY;GAC9B;EACF;CACF;;;;;;;;CASA,AAAQ,YAAY,UAA2B,QAA6C;EAC1F,IAAI,OAAO,QAET,OAAO,GAAG,GAAG,OAAO,YAAY,GAAG,OAAO,oBAAoB,SAAS;OAGvE,OAAO,GAAG,GAAG,OAAO,YAAY,GAAG,OAAO,oBAAoB,SAAS;CAE3E;;;;;;;;CASA,AAAQ,YACN,WACA,QACyB;EACzB,IAAI,OAAO,QAET,OAAO,EAAE,MAAM,GAAG,GAAG,OAAO,YAAY,MAAM,UAAU,EAAE;OAG1D,OAAO,EAAE,MAAM,GAAG,OAAO,cAAc,UAAU,EAAE;CAEvD;;;;;;;;;CAUA,MAAc,oBAAoB,cAAsD;EACtF,MAAM,SAAqB;GACzB,SAAS;GACT,WAAW,aAAa;GACxB,WAAW;GACX,QAAQ;GACR,QAAQ,CAAC;GACT,cAAc;GACd,UAAU,CAAC;EACb;EAEA,IAAI,aAAa,WAAW,GAC1B,OAAO;EAGT,MAAM,UAAU,KAAK,OAAO,YAAY;EAExC,KAAK,MAAM,CAAC,OAAO,sBAAsB,KAAK,aAAa,YAAY,GAAG;GACxE,OAAO,eAAe,KAAK,IAAI,OAAO,cAAc,KAAK;GAEzD,KAAK,MAAM,eAAe,mBACxB,IAAI;IACF,MAAM,WAAW,MAAM,QAAQ,aAAa,CAAC,WAAW,CAAC;IAEzD,MAAM,UAAU,mBAAmB,cAAc,aAAa,QAAQ;IACtE,OAAO,SAAS,KAAK,OAAO;IAC5B,OAAO;IAEP,MAAM,KAAK,gBAAgB,OAAO;GACpC,SAAS,OAAO;IACd,OAAO;IAEP,MAAM,eAAe,KAAK,gBAAgB,aAAa,KAAK;IAC5D,MAAM,YAAY,IAAI,MAAM,YAAY;IAGxC,IAAI,iBAAiB,SAAS,MAAM,OAClC,UAAU,QAAQ,MAAM;IAG1B,OAAO,OAAO,KAAK;KAAE;KAAa,OAAO;IAAU,CAAC;IAEpD,QAAQ,MAAM,0BAA0B;KACtC,aAAa,YAAY;KACzB,UAAU,YAAY;KACtB,aAAa,YAAY;KACzB,aAAa,YAAY;KACzB,OAAO,YAAY;KACnB,OAAO,mBAAmB,YAAY,YAAY,KAAK;KACvD,QAAQ,YAAY;KACpB,OAAO;IACT,CAAC;GACH;EAEJ;EAEA,OAAO,UAAU,OAAO,WAAW;EACnC,OAAO;CACT;;;;;;;;CASA,AAAQ,gBAAgB,aAA8B,OAAwB;EAC5E,MAAM,cAAc,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EACzE,MAAM,QAAQ,mBAAmB,YAAY,YAAY,KAAK;EAE9D,OAAO;GACL,wBAAwB,YAAY,MAAM;GAC1C,UAAU,MAAM,KAAK,YAAY;GACjC,WAAW,YAAY,YAAY,GAAG,YAAY;GAClD,WAAW,YAAY;GACvB,UAAU;EACZ,CAAC,CAAC,KAAK,KAAK;CACd;;;;;;;CAQA,AAAQ,aAAa,cAAiE;EACpF,MAAM,0BAAU,IAAI,IAA+B;EAEnD,KAAK,MAAM,eAAe,cAAc;GACtC,MAAM,QAAQ,YAAY;GAC1B,IAAI,CAAC,QAAQ,IAAI,KAAK,GACpB,QAAQ,IAAI,OAAO,CAAC,CAAC;GAEvB,QAAQ,IAAI,KAAK,CAAC,CAAE,KAAK,WAAW;EACtC;EAGA,OAAO,IAAI,IAAI,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;CACnE;;;;;;;;CASA,AAAQ,WAAW,QAAoB,eAAkC;EACvE,IAAI,OAAO,YAAY,WAAW,GAChC,OAAO;EAGT,OAAO,OAAO,YAAY,MAAM,UAAU,cAAc,SAAS,KAAK,CAAC;CACzE;;;;;;;;CASA,MAAc,aACZ,MACA,QACkC;EAElC,IAAI,gBAAgB,OAAO;GACzB,MAAM,SAAS,KAAK,KAAK,CAAC,CAAC,UAAU,CAAC;GACtC,MAAM,iBAAiB,aACrB,OAAO,YAAY,OAAO,QAAQ,QAAQ,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC,OAAO,SAAS,GAAG,CAAC,CAAC;GAEtF,IAAI,MAAM,QAAQ,OAAO,QAAQ,GAC/B,OAAO,cAAc,KAAK,KAAK,OAAO,QAAQ,CAAC;GAGjD,IAAI,OAAO,KAAK,OAAO,cAA6B,YAClD,OAAO,cAAc,KAAK,OAAO,SAAmD;GAItF,IAAI,KAAK,KAAK,CAAC,CAAC,OACd,OAAO,cAAc,KAAK,SAAS;GAIrC,OAAO,KAAK,OAAO;EACrB;EAGA,OAAO;CACT;;;;;;CAOA,AAAQ,iBAA+B;EACrC,MAAM,WAAY,KAAK,YAAoB;EAE3C,IAAI,CAAC,YAAY,CAAC,MAAM,QAAQ,QAAQ,GACtC,OAAO,CAAC;EAGV,OAAO,SAAS,KAAK,YACnB,OAAO,QAAQ,UAAU,aAAa,QAAQ,MAAM,IAAI,OAC1D;CACF;;;;;;CAOA,MAAc,iBAAiB,aAA6C;EAC1E,MAAM,UAA4B;GAChC,aAAa,YAAY;GACzB,UAAU,YAAY;GACtB,aAAa,YAAY;GACzB,QAAQ,YAAY;GACpB,QAAQ,YAAY;GACpB,eAAe;GACf,OAAO,YAAY;GACnB,OAAO,YAAY;EACrB;EAGA,IAAI,OAAQ,KAAK,YAAoB,kBAAkB,YACrD,MAAO,KAAK,YAAoB,cAAc,WAAW,OAAO;CAEpE;;;;;;CAOA,MAAc,gBAAgB,SAA6B;EACzD,MAAM,UAA4B;GAChC,aAAa,QAAQ;GACrB,UAAU,QAAQ;GAClB,aAAa,QAAQ;GACrB,QAAQ,QAAQ;GAChB,QAAQ,QAAQ;GAChB,eAAe,QAAQ;GACvB,OAAO,QAAQ;GACf,OAAO,QAAQ;EACjB;EAGA,IAAI,OAAQ,KAAK,YAAoB,kBAAkB,YACrD,MAAO,KAAK,YAAoB,cAAc,UAAU,OAAO;CAEnE;;;;;;CAOA,AAAQ,oBAAgC;EACtC,OAAO;GACL,SAAS;GACT,WAAW;GACX,WAAW;GACX,QAAQ;GACR,QAAQ,CAAC;GACT,cAAc;GACd,UAAU,CAAC;EACb;CACF;AACF"}
@@ -0,0 +1,24 @@
1
+ //#region ../cascade/src/transactions/after-commit.d.ts
2
+ type AfterCommitCallback = () => void | Promise<void>;
3
+ /**
4
+ * Run a callback only once the current transaction has COMMITTED.
5
+ *
6
+ * Model events (`saved`, `created`, `updated`, `deleted`) fire INSIDE the
7
+ * transaction, before COMMIT. Anything that must observe committed data
8
+ * (cache invalidation, sitemap regeneration, webhooks) belongs here.
9
+ *
10
+ * - Inside a transaction: queued, run after the outermost COMMIT, in order,
11
+ * awaited one by one. Discarded on rollback or a failed COMMIT.
12
+ * - Outside a transaction: run on the next microtask (not awaited by the caller).
13
+ * - A callback that throws is logged; it never affects the transaction result
14
+ * or the remaining callbacks.
15
+ * - Callbacks run after the transaction context has exited, so any DB work
16
+ * they do is outside the finished transaction.
17
+ *
18
+ * @example
19
+ * Product.events().onSaved(() => afterCommit(() => regenerateSitemap()));
20
+ */
21
+ declare function afterCommit(fn: AfterCommitCallback): void;
22
+ //#endregion
23
+ export { afterCommit };
24
+ //# sourceMappingURL=after-commit.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"after-commit.d.mts","names":[],"sources":["../../../../../../../cascade/src/transactions/after-commit.ts"],"mappings":";KAGK,mBAAA,gBAAmC,OAAO;;;;;AAAA;AAwB/C;;;;AAAmD;;;;;;;;;iBAAnC,WAAA,CAAY,EAAuB,EAAnB,mBAAmB"}
@@ -0,0 +1,47 @@
1
+ import { databaseTransactionContext } from "../context/database-transaction-context.mjs";
2
+ import { log } from "@warlock.js/logger";
3
+
4
+ //#region ../cascade/src/transactions/after-commit.ts
5
+ function reportFailure(error) {
6
+ log.error("database", "afterCommit", `[cascade] afterCommit callback failed: ${error}`);
7
+ }
8
+ /**
9
+ * Run a callback only once the current transaction has COMMITTED.
10
+ *
11
+ * Model events (`saved`, `created`, `updated`, `deleted`) fire INSIDE the
12
+ * transaction, before COMMIT. Anything that must observe committed data
13
+ * (cache invalidation, sitemap regeneration, webhooks) belongs here.
14
+ *
15
+ * - Inside a transaction: queued, run after the outermost COMMIT, in order,
16
+ * awaited one by one. Discarded on rollback or a failed COMMIT.
17
+ * - Outside a transaction: run on the next microtask (not awaited by the caller).
18
+ * - A callback that throws is logged; it never affects the transaction result
19
+ * or the remaining callbacks.
20
+ * - Callbacks run after the transaction context has exited, so any DB work
21
+ * they do is outside the finished transaction.
22
+ *
23
+ * @example
24
+ * Product.events().onSaved(() => afterCommit(() => regenerateSitemap()));
25
+ */
26
+ function afterCommit(fn) {
27
+ if (databaseTransactionContext.hasActiveTransaction()) {
28
+ databaseTransactionContext.addAfterCommit(fn);
29
+ return;
30
+ }
31
+ Promise.resolve().then(fn).catch(reportFailure);
32
+ }
33
+ /**
34
+ * Run committed callbacks sequentially. Never throws.
35
+ * Drivers call this after a successful COMMIT and after the context exited.
36
+ */
37
+ async function flushAfterCommit(callbacks) {
38
+ for (const callback of callbacks) try {
39
+ await callback();
40
+ } catch (error) {
41
+ reportFailure(error);
42
+ }
43
+ }
44
+
45
+ //#endregion
46
+ export { afterCommit, flushAfterCommit };
47
+ //# sourceMappingURL=after-commit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"after-commit.mjs","names":[],"sources":["../../../../../../../cascade/src/transactions/after-commit.ts"],"sourcesContent":["import { log } from \"@warlock.js/logger\";\nimport { databaseTransactionContext } from \"../context/database-transaction-context\";\n\ntype AfterCommitCallback = () => void | Promise<void>;\n\nfunction reportFailure(error: unknown): void {\n log.error(\"database\", \"afterCommit\", `[cascade] afterCommit callback failed: ${error}`);\n}\n\n/**\n * Run a callback only once the current transaction has COMMITTED.\n *\n * Model events (`saved`, `created`, `updated`, `deleted`) fire INSIDE the\n * transaction, before COMMIT. Anything that must observe committed data\n * (cache invalidation, sitemap regeneration, webhooks) belongs here.\n *\n * - Inside a transaction: queued, run after the outermost COMMIT, in order,\n * awaited one by one. Discarded on rollback or a failed COMMIT.\n * - Outside a transaction: run on the next microtask (not awaited by the caller).\n * - A callback that throws is logged; it never affects the transaction result\n * or the remaining callbacks.\n * - Callbacks run after the transaction context has exited, so any DB work\n * they do is outside the finished transaction.\n *\n * @example\n * Product.events().onSaved(() => afterCommit(() => regenerateSitemap()));\n */\nexport function afterCommit(fn: AfterCommitCallback): void {\n if (databaseTransactionContext.hasActiveTransaction()) {\n databaseTransactionContext.addAfterCommit(fn);\n return;\n }\n\n Promise.resolve().then(fn).catch(reportFailure);\n}\n\n/**\n * Run committed callbacks sequentially. Never throws.\n * Drivers call this after a successful COMMIT and after the context exited.\n */\nexport async function flushAfterCommit(callbacks: AfterCommitCallback[]): Promise<void> {\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error) {\n reportFailure(error);\n }\n }\n}\n"],"mappings":";;;;AAKA,SAAS,cAAc,OAAsB;CAC3C,IAAI,MAAM,YAAY,eAAe,0CAA0C,OAAO;AACxF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,YAAY,IAA+B;CACzD,IAAI,2BAA2B,qBAAqB,GAAG;EACrD,2BAA2B,eAAe,EAAE;EAC5C;CACF;CAEA,QAAQ,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,aAAa;AAChD;;;;;AAMA,eAAsB,iBAAiB,WAAiD;CACtF,KAAK,MAAM,YAAY,WACrB,IAAI;EACF,MAAM,SAAS;CACjB,SAAS,OAAO;EACd,cAAc,KAAK;CACrB;AAEJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"connect-to-database.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/connect-to-database.ts"],"mappings":";;;;;;;;KAUY,cAAA;;;;AAAc;AAmB1B;;;;AAAsD;AAwCtD;;;;;;;;KAxCY,kBAAA,GAAqB,OAAO,CAAC,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0RhC;AAgDT;;;KAlSY,iBAAA;EAmSiC;;;;EA1R3C,MAAA,GAAS,cAAA;EA2RD;;;;;EApRR,IAAA;EAmRA;;;;EA7QA,SAAA;EA2Wc;;;EAtWd,QAAA;EAsWgC;;;;AAAoC;AAUtE;EAxWE,OAAA;EAwW+B;;;;;;;EA3V/B,GAAA;EA8VQ;;;;EAxVR,IAAA;EAsVyC;;;;EAhVzC,IAAA;EAkVS;;AAAC;EA7UV,QAAA;;;;EAKA,QAAA;;;;;EAMA,UAAA;;;;;;;EAYA,aAAA,GAAgB,cAAA;;;;;;;EAYhB,aAAA,GAAgB,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmDhB,YAAA,GAAe,kBAAA;;;;;;;;;;;;;;;;EAiBf,gBAAA,GAAmB,iBAAA;;;;;;;;;;;;;;;;;EAkBnB,eAAA,GAAkB,gBAAA;;;;;;;;;;;EAgBlB,qBAAA,GAAwB,cAAA;;;;;;;;;;;EAYxB,iBAAA;;;;EASA,UAAA;;;;;;;;;;;;IAYE,aAAA;;;;;;IAOA,KAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDkB,iBAAA,6CACpB,OAAA,EAAS,iBAAA,CAAkB,cAAA,EAAgB,cAAA,IAC1C,OAAA,CAAQ,UAAA;;;;;;;;;;;;iBA6FK,iBAAA,WAA4B,cAAA,WAAyB,CAAC;;;;;iBAUhD,WAAA,UACpB,EAAA,GAAK,GAAA,EAAK,kBAAA,KAAuB,OAAA,CAAQ,CAAA,GACzC,OAAA,GAAU,MAAA,oBACT,OAAA,CAAQ,CAAA"}
1
+ {"version":3,"file":"connect-to-database.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/connect-to-database.ts"],"mappings":";;;;;;;;KAUY,cAAA;;;;AAAc;AAmB1B;;;;AAAsD;AAwCtD;;;;;;;;KAxCY,kBAAA,GAAqB,OAAO,CAAC,aAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0RhC;AAgDT;;;KAlSY,iBAAA;EAmSiC;;;;EA1R3C,MAAA,GAAS,cAAA;EA2RD;;;;;EApRR,IAAA;EAmRA;;;;EA7QA,SAAA;EA+Wc;;;EA1Wd,QAAA;EA0WgC;;;;AAAoC;AAUtE;EA5WE,OAAA;EA4W+B;;;;;;;EA/V/B,GAAA;EAkWQ;;;;EA5VR,IAAA;EA0VyC;;;;EApVzC,IAAA;EAsVS;;AAAC;EAjVV,QAAA;;;;EAKA,QAAA;;;;;EAMA,UAAA;;;;;;;EAYA,aAAA,GAAgB,cAAA;;;;;;;EAYhB,aAAA,GAAgB,cAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmDhB,YAAA,GAAe,kBAAA;;;;;;;;;;;;;;;;EAiBf,gBAAA,GAAmB,iBAAA;;;;;;;;;;;;;;;;;EAkBnB,eAAA,GAAkB,gBAAA;;;;;;;;;;;EAgBlB,qBAAA,GAAwB,cAAA;;;;;;;;;;;EAYxB,iBAAA;;;;EASA,UAAA;;;;;;;;;;;;IAYE,aAAA;;;;;;IAOA,KAAA;EAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgDkB,iBAAA,6CACpB,OAAA,EAAS,iBAAA,CAAkB,cAAA,EAAgB,cAAA,IAC1C,OAAA,CAAQ,UAAA;;;;;;;;;;;;iBAiGK,iBAAA,WAA4B,cAAA,WAAyB,CAAC;;;;;iBAUhD,WAAA,UACpB,EAAA,GAAK,GAAA,EAAK,kBAAA,KAAuB,OAAA,CAAQ,CAAA,GACzC,OAAA,GAAU,MAAA,oBACT,OAAA,CAAQ,CAAA"}
@@ -52,7 +52,7 @@ import "../drivers/postgres/index.mjs";
52
52
  async function connectToDatabase(options) {
53
53
  const driverType = options.driver ?? "mongodb";
54
54
  const dataSourceName = options.name ?? "default";
55
- const isDefault = options.isDefault ?? true;
55
+ const isDefault = options.isDefault ?? !dataSourceRegistry.hasDefault();
56
56
  let driver;
57
57
  switch (driverType) {
58
58
  case "mongodb":
@@ -94,12 +94,12 @@ async function connectToDatabase(options) {
94
94
  relationDefaults: options.relationOptions,
95
95
  migrations: options.migrations
96
96
  });
97
- dataSourceRegistry.register(dataSource);
97
+ const registeredSource = dataSourceRegistry.register(dataSource, { explicitDefault: options.isDefault === true });
98
98
  try {
99
99
  await driver.connect();
100
100
  } catch (error) {
101
- console.log(error);
102
- throw new Error(`Failed to connect to ${driverType} database: ${error instanceof Error ? error.message : String(error)}`);
101
+ dataSourceRegistry.unregister(registeredSource);
102
+ throw new Error(`Failed to connect to ${driverType} database: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
103
103
  }
104
104
  return dataSource;
105
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"connect-to-database.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/connect-to-database.ts"],"sourcesContent":["import { type DriverContract, type TransactionContext } from \"../contracts\";\nimport { DataSource } from \"../data-source/data-source\";\nimport { dataSourceRegistry } from \"../data-source/data-source-registry\";\nimport { MongoDbDriver } from \"../drivers/mongodb/mongodb-driver\";\nimport { PostgresDriver } from \"../drivers/postgres\";\nimport type { DeleteStrategy, MigrationDefaults, ModelDefaults, RelationDefaults } from \"../types\";\n\n/**\n * Supported database driver types.\n */\nexport type DatabaseDriver = \"mongodb\" | \"postgres\" | \"mysql\";\n\n/**\n * Default model configuration options.\n *\n * These settings will be applied to all models using this data source,\n * unless overridden by individual model static properties.\n *\n * This is a re-export of Partial<ModelDefaults> for backward compatibility\n * and to provide clearer naming in the connection config context.\n *\n * The full hierarchy is:\n * 1. Model static property (highest priority)\n * 2. Database config modelDefaults (this type)\n * 3. Driver defaults (e.g., snake_case for PostgreSQL, camelCase for MongoDB)\n * 4. Framework defaults (fallback values)\n *\n * @see ModelDefaults for complete type definition and documentation\n */\nexport type ModelDefaultConfig = Partial<ModelDefaults>;\n\n/**\n * Connection options for establishing a database connection.\n *\n * Generic type that separates concerns:\n * - Shared config (driver, name, database, connection details)\n * - Driver options (cascade-next driver-specific settings)\n * - Client options (native database client settings)\n * - Model options (default model behaviors)\n *\n * @template TDriverOptions - Driver-specific options (e.g., MongoDriverOptions)\n * @template TClientOptions - Native client options (e.g., MongoClientOptions from mongodb package)\n *\n * @example\n * ```typescript\n * // MongoDB\n * import type { MongoClientOptions } from \"mongodb\";\n * import type { MongoDriverOptions } from \"@warlock.js/cascade\";\n *\n * const config: ConnectionOptions<MongoDriverOptions, MongoClientOptions> = {\n * driver: \"mongodb\",\n * database: \"myapp\",\n * host: \"localhost\",\n * port: 27017,\n * driverOptions: {\n * autoGenerateId: true,\n * counterCollection: \"counters\",\n * },\n * clientOptions: {\n * minPoolSize: 5,\n * maxPoolSize: 10,\n * },\n * modelOptions: {\n * randomIncrement: true,\n * initialId: 1000,\n * },\n * };\n * ```\n */\nexport type ConnectionOptions<TDriverOptions = any, TClientOptions = any> = {\n // ============================================================================\n // SHARED CONFIGURATION (Framework-level)\n // ============================================================================\n\n /**\n * Database driver to use.\n * @default \"mongodb\"\n */\n driver?: DatabaseDriver;\n\n /**\n * Unique name for this data source.\n * Used for registration in DataSourceRegistry.\n * @default \"default\"\n */\n name?: string;\n\n /**\n * Whether this should be the default data source.\n * @default true\n */\n isDefault?: boolean;\n\n /**\n * Database name (required).\n */\n database: string;\n\n /**\n * Enable database operation logging (queries, execution time, parameters).\n * Highly recommended to keep this disabled in production to prevent sensitive data leakage.\n *\n * @default false\n */\n logging?: boolean;\n\n // ============================================================================\n // CONNECTION DETAILS (Shared across drivers)\n // ============================================================================\n\n /**\n * Database connection URI.\n * Alternative to specifying host/port separately.\n *\n * @example \"mongodb://localhost:27017/mydb\"\n * @example \"postgresql://user:pass@localhost:5432/mydb\"\n */\n uri?: string;\n\n /**\n * Database host.\n * @default \"localhost\"\n */\n host?: string;\n\n /**\n * Database port.\n * @default 27017 (MongoDB), 5432 (PostgreSQL)\n */\n port?: number;\n\n /**\n * Database username for authentication.\n */\n username?: string;\n\n /**\n * Database password for authentication.\n */\n password?: string;\n\n /**\n * Authentication source database.\n * Typically \"admin\" for MongoDB.\n */\n authSource?: string;\n\n // ============================================================================\n // DRIVER OPTIONS (Package-level, driver-specific)\n // ============================================================================\n\n /**\n * Driver-specific options.\n *\n * For MongoDB: { autoGenerateId, counterCollection, transactionOptions }\n * For PostgreSQL: { schema, ... }\n */\n driverOptions?: TDriverOptions;\n\n // ============================================================================\n // CLIENT OPTIONS (Native database client library)\n // ============================================================================\n\n /**\n * Native database client options.\n *\n * For MongoDB: MongoClientOptions from 'mongodb' package\n * For PostgreSQL: PoolConfig from 'pg' package\n */\n clientOptions?: TClientOptions;\n\n // ============================================================================\n // MODEL OPTIONS (Model defaults)\n // ============================================================================\n\n /**\n * Default model configuration for all models using this data source.\n *\n * These settings override driver defaults but are overridden by\n * individual model static properties.\n *\n * **Configuration Hierarchy (highest to lowest):**\n * 1. Model static property - `User.createdAtColumn = \"creation_date\"`\n * 2. **modelOptions (this)** - Database-wide overrides\n * 3. Driver defaults - PostgreSQL: snake_case, MongoDB: camelCase\n * 4. Framework defaults - Fallback values\n *\n * @example\n * ```typescript\n * // PostgreSQL database with custom settings\n * {\n * driver: \"postgres\",\n * modelOptions: {\n * // Override PostgreSQL default (snake_case) to use camelCase\n * namingConvention: \"camelCase\",\n * createdAtColumn: \"createdAt\",\n * updatedAtColumn: \"updatedAt\",\n *\n * // ID generation settings (for MongoDB)\n * randomIncrement: true,\n * initialId: 1000,\n *\n * // Deletion settings\n * deleteStrategy: \"soft\",\n * trashTable: \"archive\", // All models use same trash table\n * }\n * }\n *\n * // MongoDB database with defaults\n * {\n * driver: \"mongodb\",\n * modelOptions: {\n * // MongoDB already uses camelCase by default\n * randomIncrement: true,\n * initialId: 10000,\n * deleteStrategy: \"trash\", // Use RecycleBin\n * }\n * }\n * ```\n */\n modelOptions?: ModelDefaultConfig;\n\n /**\n * Migration-level defaults (UUID strategy, etc.).\n *\n * These defaults override driver migration defaults but can be\n * overridden by individual migration calls.\n *\n * @default undefined (uses driver defaults)\n *\n * @example\n * ```typescript\n * migrationDefaults: {\n * uuidStrategy: \"v7\", // Use UUID v7 for all migrations\n * }\n * ```\n */\n migrationOptions?: MigrationDefaults;\n\n /**\n * Defaults for relation conventions — foreign-key suffix and pivot-table\n * naming order. Controls how `@BelongsTo` / `@HasOne` / `@HasMany` /\n * `@BelongsToMany` infer column / table names when none are explicitly\n * configured on the decorator.\n *\n * @default undefined (uses framework defaults: `\"_id\"` suffix + alphabetical pivot)\n *\n * @example\n * ```typescript\n * relationOptions: {\n * foreignKeySuffix: \"_id\",\n * pivotTableNamingOrder: \"alphabetical\",\n * }\n * ```\n */\n relationOptions?: RelationDefaults;\n\n // ============================================================================\n // DATA SOURCE DEFAULTS\n // ============================================================================\n\n /**\n * Default delete strategy for models using this data source.\n *\n * - MongoDB: Typically `\"trash\"` (uses RecycleBin collection)\n * - PostgreSQL: Typically `\"permanent\"` or `\"soft\"`\n *\n * Can be overridden by model static property or destroy() options.\n *\n * @default undefined (falls back to \"permanent\")\n */\n defaultDeleteStrategy?: DeleteStrategy;\n\n /**\n * Default trash table/collection name for \"trash\" delete strategy.\n *\n * - MongoDB: Typically `\"RecycleBin\"`\n * - If not set, defaults to `{table}Trash` pattern\n *\n * Can be overridden by Model.trashTable static property.\n *\n * @default undefined (uses {table}Trash pattern)\n */\n defaultTrashTable?: string;\n\n // ============================================================================\n // MIGRATION OPTIONS\n // ============================================================================\n\n /**\n * Migration configuration options.\n */\n migrations?: {\n /**\n * Whether to wrap migrations in database transactions.\n *\n * Overrides driver defaults:\n * - PostgreSQL default: `true` (DDL is transactional)\n * - MongoDB default: `false` (DDL cannot be transactional)\n *\n * Individual migrations can override this with their own `transactional` property.\n *\n * @default undefined (uses driver default)\n */\n transactional?: boolean;\n\n /**\n * Name of the migrations tracking table/collection.\n *\n * @default \"_migrations\"\n */\n table?: string;\n };\n};\n\n/**\n * Connect to a database and register the data source.\n *\n * This is a high-level utility function that simplifies database connection\n * for small to medium projects. It handles driver instantiation, connection,\n * data source creation, and automatic registration.\n *\n * **Supported Drivers:**\n * - `mongodb` (default) - MongoDB driver with optional auto ID generation\n * - `postgres` - PostgreSQL driver (not yet implemented)\n * - `mysql` - MySQL driver (not yet implemented)\n *\n * **Features:**\n * - Automatic driver instantiation based on driver name\n * - Connection establishment and error handling\n * - DataSource creation and registration\n * - Support for MongoDB-specific features (ID generation, transactions)\n *\n * @param options - Connection configuration options\n * @returns A connected and registered DataSource instance\n * @throws {Error} If connection fails or driver is not implemented\n *\n * @example\n * ```typescript\n * // MongoDB with new structure\n * const dataSource = await connectToDatabase({\n * driver: \"mongodb\",\n * database: \"myapp\",\n * host: \"localhost\",\n * port: 27017,\n * driverOptions: {\n * autoGenerateId: true,\n * },\n * clientOptions: {\n * minPoolSize: 5,\n * maxPoolSize: 10,\n * },\n * modelOptions: {\n * randomIncrement: true,\n * initialId: 1000,\n * },\n * });\n * ```\n */\nexport async function connectToDatabase<TDriverOptions = any, TClientOptions = any>(\n options: ConnectionOptions<TDriverOptions, TClientOptions>,\n): Promise<DataSource> {\n // Default values\n const driverType = options.driver ?? \"mongodb\";\n const dataSourceName = options.name ?? \"default\";\n const isDefault = options.isDefault ?? true;\n\n // Create driver based on type\n let driver: DriverContract;\n\n switch (driverType) {\n case \"mongodb\": {\n driver = new MongoDbDriver(\n {\n database: options.database,\n uri: options.uri,\n host: options.host,\n port: options.port,\n username: options.username,\n password: options.password,\n authSource: options.authSource,\n logging: options.logging,\n clientOptions: options.clientOptions as any,\n },\n options.driverOptions as any,\n );\n break;\n }\n\n case \"postgres\": {\n driver = new PostgresDriver({\n database: options.database,\n connectionString: options.uri,\n host: options.host,\n port: options.port ?? 5432,\n user: options.username,\n password: options.password,\n logging: options.logging,\n // Spread any additional client options (pool settings, SSL, etc.)\n ...(options.clientOptions as object),\n });\n break;\n }\n\n case \"mysql\":\n throw new Error(\"MySQL driver is not yet implemented. Coming soon!\");\n\n default:\n throw new Error(\n `Unknown driver: \"${driverType}\". Supported drivers: mongodb, postgres, mysql`,\n );\n }\n\n // Create data source\n const dataSource = new DataSource({\n name: dataSourceName,\n driver,\n isDefault,\n defaultDeleteStrategy: options.defaultDeleteStrategy,\n defaultTrashTable: options.defaultTrashTable,\n modelDefaults: options.modelOptions,\n migrationDefaults: options.migrationOptions,\n relationDefaults: options.relationOptions,\n migrations: options.migrations,\n });\n\n // Register data source\n dataSourceRegistry.register(dataSource);\n\n // Connect to the database\n try {\n await driver.connect();\n } catch (error) {\n console.log(error);\n\n throw new Error(\n `Failed to connect to ${driverType} database: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n\n return dataSource;\n}\n\n/**\n * Get current driver instance.\n *\n * @example\n * ```typescript\n * const driver = getDatabaseDriver();\n *\n * // Pass type to return Postgres driver type\n * const pgDriver = getDatabaseDriver<PostgresDriver>();\n * ```\n */\nexport function getDatabaseDriver<T extends DriverContract = any>(): T {\n const driver = dataSourceRegistry.get().driver;\n\n return driver as unknown as T;\n}\n\n/**\n * Perform database transaction(s)\n * Shorthand to `dataSourceRegister.get().driver.transaction\n */\nexport async function transaction<T = any>(\n fn: (ctx: TransactionContext) => Promise<T>,\n options?: Record<string, unknown>,\n): Promise<T> {\n return getDatabaseDriver().transaction(fn, options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuWA,eAAsB,kBACpB,SACqB;CAErB,MAAM,aAAa,QAAQ,UAAU;CACrC,MAAM,iBAAiB,QAAQ,QAAQ;CACvC,MAAM,YAAY,QAAQ,aAAa;CAGvC,IAAI;CAEJ,QAAQ,YAAR;EACE,KAAK;GACH,SAAS,IAAI,cACX;IACE,UAAU,QAAQ;IAClB,KAAK,QAAQ;IACb,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,UAAU,QAAQ;IAClB,UAAU,QAAQ;IAClB,YAAY,QAAQ;IACpB,SAAS,QAAQ;IACjB,eAAe,QAAQ;GACzB,GACA,QAAQ,aACV;GACA;EAGF,KAAK;GACH,SAAS,IAAI,eAAe;IAC1B,UAAU,QAAQ;IAClB,kBAAkB,QAAQ;IAC1B,MAAM,QAAQ;IACd,MAAM,QAAQ,QAAQ;IACtB,MAAM,QAAQ;IACd,UAAU,QAAQ;IAClB,SAAS,QAAQ;IAEjB,GAAI,QAAQ;GACd,CAAC;GACD;EAGF,KAAK,SACH,MAAM,IAAI,MAAM,mDAAmD;EAErE,SACE,MAAM,IAAI,MACR,oBAAoB,WAAW,+CACjC;CACJ;CAGA,MAAM,aAAa,IAAI,WAAW;EAChC,MAAM;EACN;EACA;EACA,uBAAuB,QAAQ;EAC/B,mBAAmB,QAAQ;EAC3B,eAAe,QAAQ;EACvB,mBAAmB,QAAQ;EAC3B,kBAAkB,QAAQ;EAC1B,YAAY,QAAQ;CACtB,CAAC;CAGD,mBAAmB,SAAS,UAAU;CAGtC,IAAI;EACF,MAAM,OAAO,QAAQ;CACvB,SAAS,OAAO;EACd,QAAQ,IAAI,KAAK;EAEjB,MAAM,IAAI,MACR,wBAAwB,WAAW,aAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GACvG;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,oBAAuD;CAGrE,OAFe,mBAAmB,IAAI,CAAC,CAAC;AAG1C;;;;;AAMA,eAAsB,YACpB,IACA,SACY;CACZ,OAAO,kBAAkB,CAAC,CAAC,YAAY,IAAI,OAAO;AACpD"}
1
+ {"version":3,"file":"connect-to-database.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/connect-to-database.ts"],"sourcesContent":["import { type DriverContract, type TransactionContext } from \"../contracts\";\nimport { DataSource } from \"../data-source/data-source\";\nimport { dataSourceRegistry } from \"../data-source/data-source-registry\";\nimport { MongoDbDriver } from \"../drivers/mongodb/mongodb-driver\";\nimport { PostgresDriver } from \"../drivers/postgres\";\nimport type { DeleteStrategy, MigrationDefaults, ModelDefaults, RelationDefaults } from \"../types\";\n\n/**\n * Supported database driver types.\n */\nexport type DatabaseDriver = \"mongodb\" | \"postgres\" | \"mysql\";\n\n/**\n * Default model configuration options.\n *\n * These settings will be applied to all models using this data source,\n * unless overridden by individual model static properties.\n *\n * This is a re-export of Partial<ModelDefaults> for backward compatibility\n * and to provide clearer naming in the connection config context.\n *\n * The full hierarchy is:\n * 1. Model static property (highest priority)\n * 2. Database config modelDefaults (this type)\n * 3. Driver defaults (e.g., snake_case for PostgreSQL, camelCase for MongoDB)\n * 4. Framework defaults (fallback values)\n *\n * @see ModelDefaults for complete type definition and documentation\n */\nexport type ModelDefaultConfig = Partial<ModelDefaults>;\n\n/**\n * Connection options for establishing a database connection.\n *\n * Generic type that separates concerns:\n * - Shared config (driver, name, database, connection details)\n * - Driver options (cascade-next driver-specific settings)\n * - Client options (native database client settings)\n * - Model options (default model behaviors)\n *\n * @template TDriverOptions - Driver-specific options (e.g., MongoDriverOptions)\n * @template TClientOptions - Native client options (e.g., MongoClientOptions from mongodb package)\n *\n * @example\n * ```typescript\n * // MongoDB\n * import type { MongoClientOptions } from \"mongodb\";\n * import type { MongoDriverOptions } from \"@warlock.js/cascade\";\n *\n * const config: ConnectionOptions<MongoDriverOptions, MongoClientOptions> = {\n * driver: \"mongodb\",\n * database: \"myapp\",\n * host: \"localhost\",\n * port: 27017,\n * driverOptions: {\n * autoGenerateId: true,\n * counterCollection: \"counters\",\n * },\n * clientOptions: {\n * minPoolSize: 5,\n * maxPoolSize: 10,\n * },\n * modelOptions: {\n * randomIncrement: true,\n * initialId: 1000,\n * },\n * };\n * ```\n */\nexport type ConnectionOptions<TDriverOptions = any, TClientOptions = any> = {\n // ============================================================================\n // SHARED CONFIGURATION (Framework-level)\n // ============================================================================\n\n /**\n * Database driver to use.\n * @default \"mongodb\"\n */\n driver?: DatabaseDriver;\n\n /**\n * Unique name for this data source.\n * Used for registration in DataSourceRegistry.\n * @default \"default\"\n */\n name?: string;\n\n /**\n * Whether this should be the default data source.\n * @default true\n */\n isDefault?: boolean;\n\n /**\n * Database name (required).\n */\n database: string;\n\n /**\n * Enable database operation logging (queries, execution time, parameters).\n * Highly recommended to keep this disabled in production to prevent sensitive data leakage.\n *\n * @default false\n */\n logging?: boolean;\n\n // ============================================================================\n // CONNECTION DETAILS (Shared across drivers)\n // ============================================================================\n\n /**\n * Database connection URI.\n * Alternative to specifying host/port separately.\n *\n * @example \"mongodb://localhost:27017/mydb\"\n * @example \"postgresql://user:pass@localhost:5432/mydb\"\n */\n uri?: string;\n\n /**\n * Database host.\n * @default \"localhost\"\n */\n host?: string;\n\n /**\n * Database port.\n * @default 27017 (MongoDB), 5432 (PostgreSQL)\n */\n port?: number;\n\n /**\n * Database username for authentication.\n */\n username?: string;\n\n /**\n * Database password for authentication.\n */\n password?: string;\n\n /**\n * Authentication source database.\n * Typically \"admin\" for MongoDB.\n */\n authSource?: string;\n\n // ============================================================================\n // DRIVER OPTIONS (Package-level, driver-specific)\n // ============================================================================\n\n /**\n * Driver-specific options.\n *\n * For MongoDB: { autoGenerateId, counterCollection, transactionOptions }\n * For PostgreSQL: { schema, ... }\n */\n driverOptions?: TDriverOptions;\n\n // ============================================================================\n // CLIENT OPTIONS (Native database client library)\n // ============================================================================\n\n /**\n * Native database client options.\n *\n * For MongoDB: MongoClientOptions from 'mongodb' package\n * For PostgreSQL: PoolConfig from 'pg' package\n */\n clientOptions?: TClientOptions;\n\n // ============================================================================\n // MODEL OPTIONS (Model defaults)\n // ============================================================================\n\n /**\n * Default model configuration for all models using this data source.\n *\n * These settings override driver defaults but are overridden by\n * individual model static properties.\n *\n * **Configuration Hierarchy (highest to lowest):**\n * 1. Model static property - `User.createdAtColumn = \"creation_date\"`\n * 2. **modelOptions (this)** - Database-wide overrides\n * 3. Driver defaults - PostgreSQL: snake_case, MongoDB: camelCase\n * 4. Framework defaults - Fallback values\n *\n * @example\n * ```typescript\n * // PostgreSQL database with custom settings\n * {\n * driver: \"postgres\",\n * modelOptions: {\n * // Override PostgreSQL default (snake_case) to use camelCase\n * namingConvention: \"camelCase\",\n * createdAtColumn: \"createdAt\",\n * updatedAtColumn: \"updatedAt\",\n *\n * // ID generation settings (for MongoDB)\n * randomIncrement: true,\n * initialId: 1000,\n *\n * // Deletion settings\n * deleteStrategy: \"soft\",\n * trashTable: \"archive\", // All models use same trash table\n * }\n * }\n *\n * // MongoDB database with defaults\n * {\n * driver: \"mongodb\",\n * modelOptions: {\n * // MongoDB already uses camelCase by default\n * randomIncrement: true,\n * initialId: 10000,\n * deleteStrategy: \"trash\", // Use RecycleBin\n * }\n * }\n * ```\n */\n modelOptions?: ModelDefaultConfig;\n\n /**\n * Migration-level defaults (UUID strategy, etc.).\n *\n * These defaults override driver migration defaults but can be\n * overridden by individual migration calls.\n *\n * @default undefined (uses driver defaults)\n *\n * @example\n * ```typescript\n * migrationDefaults: {\n * uuidStrategy: \"v7\", // Use UUID v7 for all migrations\n * }\n * ```\n */\n migrationOptions?: MigrationDefaults;\n\n /**\n * Defaults for relation conventions — foreign-key suffix and pivot-table\n * naming order. Controls how `@BelongsTo` / `@HasOne` / `@HasMany` /\n * `@BelongsToMany` infer column / table names when none are explicitly\n * configured on the decorator.\n *\n * @default undefined (uses framework defaults: `\"_id\"` suffix + alphabetical pivot)\n *\n * @example\n * ```typescript\n * relationOptions: {\n * foreignKeySuffix: \"_id\",\n * pivotTableNamingOrder: \"alphabetical\",\n * }\n * ```\n */\n relationOptions?: RelationDefaults;\n\n // ============================================================================\n // DATA SOURCE DEFAULTS\n // ============================================================================\n\n /**\n * Default delete strategy for models using this data source.\n *\n * - MongoDB: Typically `\"trash\"` (uses RecycleBin collection)\n * - PostgreSQL: Typically `\"permanent\"` or `\"soft\"`\n *\n * Can be overridden by model static property or destroy() options.\n *\n * @default undefined (falls back to \"permanent\")\n */\n defaultDeleteStrategy?: DeleteStrategy;\n\n /**\n * Default trash table/collection name for \"trash\" delete strategy.\n *\n * - MongoDB: Typically `\"RecycleBin\"`\n * - If not set, defaults to `{table}Trash` pattern\n *\n * Can be overridden by Model.trashTable static property.\n *\n * @default undefined (uses {table}Trash pattern)\n */\n defaultTrashTable?: string;\n\n // ============================================================================\n // MIGRATION OPTIONS\n // ============================================================================\n\n /**\n * Migration configuration options.\n */\n migrations?: {\n /**\n * Whether to wrap migrations in database transactions.\n *\n * Overrides driver defaults:\n * - PostgreSQL default: `true` (DDL is transactional)\n * - MongoDB default: `false` (DDL cannot be transactional)\n *\n * Individual migrations can override this with their own `transactional` property.\n *\n * @default undefined (uses driver default)\n */\n transactional?: boolean;\n\n /**\n * Name of the migrations tracking table/collection.\n *\n * @default \"_migrations\"\n */\n table?: string;\n };\n};\n\n/**\n * Connect to a database and register the data source.\n *\n * This is a high-level utility function that simplifies database connection\n * for small to medium projects. It handles driver instantiation, connection,\n * data source creation, and automatic registration.\n *\n * **Supported Drivers:**\n * - `mongodb` (default) - MongoDB driver with optional auto ID generation\n * - `postgres` - PostgreSQL driver (not yet implemented)\n * - `mysql` - MySQL driver (not yet implemented)\n *\n * **Features:**\n * - Automatic driver instantiation based on driver name\n * - Connection establishment and error handling\n * - DataSource creation and registration\n * - Support for MongoDB-specific features (ID generation, transactions)\n *\n * @param options - Connection configuration options\n * @returns A connected and registered DataSource instance\n * @throws {Error} If connection fails or driver is not implemented\n *\n * @example\n * ```typescript\n * // MongoDB with new structure\n * const dataSource = await connectToDatabase({\n * driver: \"mongodb\",\n * database: \"myapp\",\n * host: \"localhost\",\n * port: 27017,\n * driverOptions: {\n * autoGenerateId: true,\n * },\n * clientOptions: {\n * minPoolSize: 5,\n * maxPoolSize: 10,\n * },\n * modelOptions: {\n * randomIncrement: true,\n * initialId: 1000,\n * },\n * });\n * ```\n */\nexport async function connectToDatabase<TDriverOptions = any, TClientOptions = any>(\n options: ConnectionOptions<TDriverOptions, TClientOptions>,\n): Promise<DataSource> {\n // Default values\n const driverType = options.driver ?? \"mongodb\";\n const dataSourceName = options.name ?? \"default\";\n const isDefault = options.isDefault ?? !dataSourceRegistry.hasDefault();\n\n // Create driver based on type\n let driver: DriverContract;\n\n switch (driverType) {\n case \"mongodb\": {\n driver = new MongoDbDriver(\n {\n database: options.database,\n uri: options.uri,\n host: options.host,\n port: options.port,\n username: options.username,\n password: options.password,\n authSource: options.authSource,\n logging: options.logging,\n clientOptions: options.clientOptions as any,\n },\n options.driverOptions as any,\n );\n break;\n }\n\n case \"postgres\": {\n driver = new PostgresDriver({\n database: options.database,\n connectionString: options.uri,\n host: options.host,\n port: options.port ?? 5432,\n user: options.username,\n password: options.password,\n logging: options.logging,\n // Spread any additional client options (pool settings, SSL, etc.)\n ...(options.clientOptions as object),\n });\n break;\n }\n\n case \"mysql\":\n throw new Error(\"MySQL driver is not yet implemented. Coming soon!\");\n\n default:\n throw new Error(\n `Unknown driver: \"${driverType}\". Supported drivers: mongodb, postgres, mysql`,\n );\n }\n\n // Create data source\n const dataSource = new DataSource({\n name: dataSourceName,\n driver,\n isDefault,\n defaultDeleteStrategy: options.defaultDeleteStrategy,\n defaultTrashTable: options.defaultTrashTable,\n modelDefaults: options.modelOptions,\n migrationDefaults: options.migrationOptions,\n relationDefaults: options.relationOptions,\n migrations: options.migrations,\n });\n\n // Register data source\n const registeredSource = dataSourceRegistry.register(dataSource, {\n explicitDefault: options.isDefault === true,\n });\n\n // Connect to the database\n try {\n await driver.connect();\n } catch (error) {\n // Do not leave a dead source registered as the default\n dataSourceRegistry.unregister(registeredSource);\n\n throw new Error(\n `Failed to connect to ${driverType} database: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n return dataSource;\n}\n\n/**\n * Get current driver instance.\n *\n * @example\n * ```typescript\n * const driver = getDatabaseDriver();\n *\n * // Pass type to return Postgres driver type\n * const pgDriver = getDatabaseDriver<PostgresDriver>();\n * ```\n */\nexport function getDatabaseDriver<T extends DriverContract = any>(): T {\n const driver = dataSourceRegistry.get().driver;\n\n return driver as unknown as T;\n}\n\n/**\n * Perform database transaction(s)\n * Shorthand to `dataSourceRegister.get().driver.transaction\n */\nexport async function transaction<T = any>(\n fn: (ctx: TransactionContext) => Promise<T>,\n options?: Record<string, unknown>,\n): Promise<T> {\n return getDatabaseDriver().transaction(fn, options);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuWA,eAAsB,kBACpB,SACqB;CAErB,MAAM,aAAa,QAAQ,UAAU;CACrC,MAAM,iBAAiB,QAAQ,QAAQ;CACvC,MAAM,YAAY,QAAQ,aAAa,CAAC,mBAAmB,WAAW;CAGtE,IAAI;CAEJ,QAAQ,YAAR;EACE,KAAK;GACH,SAAS,IAAI,cACX;IACE,UAAU,QAAQ;IAClB,KAAK,QAAQ;IACb,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,UAAU,QAAQ;IAClB,UAAU,QAAQ;IAClB,YAAY,QAAQ;IACpB,SAAS,QAAQ;IACjB,eAAe,QAAQ;GACzB,GACA,QAAQ,aACV;GACA;EAGF,KAAK;GACH,SAAS,IAAI,eAAe;IAC1B,UAAU,QAAQ;IAClB,kBAAkB,QAAQ;IAC1B,MAAM,QAAQ;IACd,MAAM,QAAQ,QAAQ;IACtB,MAAM,QAAQ;IACd,UAAU,QAAQ;IAClB,SAAS,QAAQ;IAEjB,GAAI,QAAQ;GACd,CAAC;GACD;EAGF,KAAK,SACH,MAAM,IAAI,MAAM,mDAAmD;EAErE,SACE,MAAM,IAAI,MACR,oBAAoB,WAAW,+CACjC;CACJ;CAGA,MAAM,aAAa,IAAI,WAAW;EAChC,MAAM;EACN;EACA;EACA,uBAAuB,QAAQ;EAC/B,mBAAmB,QAAQ;EAC3B,eAAe,QAAQ;EACvB,mBAAmB,QAAQ;EAC3B,kBAAkB,QAAQ;EAC1B,YAAY,QAAQ;CACtB,CAAC;CAGD,MAAM,mBAAmB,mBAAmB,SAAS,YAAY,EAC/D,iBAAiB,QAAQ,cAAc,KACzC,CAAC;CAGD,IAAI;EACF,MAAM,OAAO,QAAQ;CACvB,SAAS,OAAO;EAEd,mBAAmB,WAAW,gBAAgB;EAE9C,MAAM,IAAI,MACR,wBAAwB,WAAW,aAAa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,KACrG,EAAE,OAAO,MAAM,CACjB;CACF;CAEA,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,oBAAuD;CAGrE,OAFe,mBAAmB,IAAI,CAAC,CAAC;AAG1C;;;;;AAMA,eAAsB,YACpB,IACA,SACY;CACZ,OAAO,kBAAkB,CAAC,CAAC,YAAY,IAAI,OAAO;AACpD"}
@@ -9,7 +9,7 @@ type transformCallbackOptions = {
9
9
  isChanged: boolean;
10
10
  isNew: boolean;
11
11
  };
12
- type ModelTransformCallback = (options: transformCallbackOptions) => string;
12
+ type ModelTransformCallback = (options: transformCallbackOptions) => unknown | Promise<unknown>;
13
13
  /**
14
14
  * Transfer value before saving it into the database
15
15
  */
@@ -1 +1 @@
1
- {"version":3,"file":"database-writer.utils.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/database-writer.utils.ts"],"mappings":";;;;KAGK,wBAAA;EACH,KAAA,EAAO,KAAK;EACZ,MAAA;EACA,KAAA;EACA,SAAA;EACA,KAAA;AAAA;AAAA,KAGU,sBAAA,IAA0B,OAAiC,EAAxB,wBAAwB;;;;iBAKvD,mBAAA,CAAoB,QAAA,EAAU,sBAAA,GAAsB,mBAAA"}
1
+ {"version":3,"file":"database-writer.utils.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/database-writer.utils.ts"],"mappings":";;;;KAGK,wBAAA;EACH,KAAA,EAAO,KAAK;EACZ,MAAA;EACA,KAAA;EACA,SAAA;EACA,KAAA;AAAA;AAAA,KAGU,sBAAA,IAA0B,OAAA,EAAS,wBAAA,eAAuC,OAAO;;;;iBAK7E,mBAAA,CAAoB,QAAA,EAAU,sBAAA,GAAsB,mBAAA"}
@@ -5,6 +5,7 @@
5
5
  function useModelTransformer(callback) {
6
6
  const transformCallback = (data, { context }) => {
7
7
  const model = context.rootContext?.model;
8
+ if (!model) return data;
8
9
  const column = context.key;
9
10
  const value = data;
10
11
  const isChanged = model.isDirty(column);
@@ -1 +1 @@
1
- {"version":3,"file":"database-writer.utils.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/database-writer.utils.ts"],"sourcesContent":["import { type TransformerCallback } from \"@warlock.js/seal\";\nimport { type Model } from \"../model/model\";\n\ntype transformCallbackOptions = {\n model: Model;\n column: string;\n value: any;\n isChanged: boolean;\n isNew: boolean;\n};\n\nexport type ModelTransformCallback = (options: transformCallbackOptions) => string;\n\n/**\n * Transfer value before saving it into the database\n */\nexport function useModelTransformer(callback: ModelTransformCallback) {\n const transformCallback: TransformerCallback = (data, { context }) => {\n const model = context.rootContext?.model as Model;\n const column = context.key;\n const value = data;\n const isChanged = model.isDirty(column);\n const isNew = model.isNew;\n return callback({ model, column, value, isChanged, isNew });\n };\n\n return transformCallback;\n}\n"],"mappings":";;;;AAgBA,SAAgB,oBAAoB,UAAkC;CACpE,MAAM,qBAA0C,MAAM,EAAE,cAAc;EACpE,MAAM,QAAQ,QAAQ,aAAa;EACnC,MAAM,SAAS,QAAQ;EACvB,MAAM,QAAQ;EACd,MAAM,YAAY,MAAM,QAAQ,MAAM;EACtC,MAAM,QAAQ,MAAM;EACpB,OAAO,SAAS;GAAE;GAAO;GAAQ;GAAO;GAAW;EAAM,CAAC;CAC5D;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"database-writer.utils.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/database-writer.utils.ts"],"sourcesContent":["import { type TransformerCallback } from \"@warlock.js/seal\";\nimport { type Model } from \"../model/model\";\n\ntype transformCallbackOptions = {\n model: Model;\n column: string;\n value: any;\n isChanged: boolean;\n isNew: boolean;\n};\n\nexport type ModelTransformCallback = (options: transformCallbackOptions) => unknown | Promise<unknown>;\n\n/**\n * Transfer value before saving it into the database\n */\nexport function useModelTransformer(callback: ModelTransformCallback) {\n const transformCallback: TransformerCallback = (data, { context }) => {\n const model = context.rootContext?.model as Model | undefined;\n\n // No model (e.g. the schema is reused to validate a request body): nothing to transform\n if (!model) return data;\n\n const column = context.key;\n const value = data;\n const isChanged = model.isDirty(column);\n const isNew = model.isNew;\n return callback({ model, column, value, isChanged, isNew });\n };\n\n return transformCallback;\n}\n"],"mappings":";;;;AAgBA,SAAgB,oBAAoB,UAAkC;CACpE,MAAM,qBAA0C,MAAM,EAAE,cAAc;EACpE,MAAM,QAAQ,QAAQ,aAAa;EAGnC,IAAI,CAAC,OAAO,OAAO;EAEnB,MAAM,SAAS,QAAQ;EACvB,MAAM,QAAQ;EACd,MAAM,YAAY,MAAM,QAAQ,MAAM;EACtC,MAAM,QAAQ,MAAM;EACpB,OAAO,SAAS;GAAE;GAAO;GAAQ;GAAO;GAAW;EAAM,CAAC;CAC5D;CAEA,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"define-model.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/define-model.ts"],"mappings":";;;;;;;;AASA;KAAY,kBAAA,iBAAmC,WAAA;EAAjB;;;EAI5B,KAAA;EAiCa;;;;EA3Bb,IAAA;EA2FU;;;;EArFV,MAAA,EAAQ,eAAA;EAZR;;;;;;;;;;EAwBA,cAAA,GAAiB,cAAA;EAuDjB;;;;;;;EA9CA,UAAA,GAAa,UAAA;EAgEG;AA2DlB;;;;;EAnHE,cAAA;EAsHoB;;;;;EA/GpB,eAAA;EAoH8B;;;;;EA7G9B,SAAA;EA8G4B;;;;;;;;;;;;;;;;;;;;;;EAtF5B,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,OAAA,KAAY,MAAA;EAiFvB;;;;;;;;;;;;;;;;EA/DjB,OAAA,GAAU,MAAA;AAAA;;;;;;;;;;;;;;;;;AAgJwE;AAwBpF;;;;;;;;;;;;;;;;;;;;AAIO;;;;;;;;;;;;;;;;;;;iBAjHS,WAAA,iBACE,WAAA,2BACS,eAAA,GAAkB,eAAA,sBACvB,MAAA,qCACH,MAAA,oBAEjB,OAAA,EAAS,kBAAA,CAAmB,OAAA;EAC1B,MAAA,EAAQ,gBAAA;EACR,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,gBAAA,MAAsB,WAAA;EAC/D,OAAA,GAAU,QAAA,QAAgB,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,gBAAA,MAAsB,QAAA;AAAA,UACpE,WAAA,GA2EoB,OAAA,CAAO,KAAA,CAAA,MAAA,CAAA,gBAAA,OAAmB,KAAA,CAAK,KAAA,CAAA,MAAA,CAAA,gBAAA,KAAmB,WAAA,IAAW,IAAA,QAAA,KAAA,WAAA,QAAA;;;;;;;;;;;;;;;;;;KAwBxE,SAAA,WAAoB,UAAA,QAAkB,WAAA,KAAgB,CAAA,kBAC7D,IAAA,uBAED,CAAA,SAAU,KAAA,YACR,CAAA"}
1
+ {"version":3,"file":"define-model.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/define-model.ts"],"mappings":";;;;;;;;AASA;KAAY,kBAAA,iBAAmC,WAAA;EAAjB;;;EAI5B,KAAA;EAiCa;;;;EA3Bb,IAAA;EA2FU;;;;EArFV,MAAA,EAAQ,eAAA;EAZR;;;;;;;;;;EAwBA,cAAA,GAAiB,cAAA;EAuDjB;;;;;;;EA9CA,UAAA,GAAa,UAAA;EAgEG;AA2DlB;;;;;EAnHE,cAAA;EAsHoB;;;;;EA/GpB,eAAA;EAoH8B;;;;;EA7G9B,SAAA;EA8G4B;;;;;;;;;;;;;;;;;;;;;;EAtF5B,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,OAAA,KAAY,MAAA;EAiFvB;;;;;;;;;;;;;;;;EA/DjB,OAAA,GAAU,MAAA;AAAA;;;;;;;;;;;;;;;;;AA8IwE;AAwBpF;;;;;;;;;;;;;;;;;;;;AAIO;;;;;;;;;;;;;;;;;;;iBA/GS,WAAA,iBACE,WAAA,2BACS,eAAA,GAAkB,eAAA,sBACvB,MAAA,qCACH,MAAA,oBAEjB,OAAA,EAAS,kBAAA,CAAmB,OAAA;EAC1B,MAAA,EAAQ,gBAAA;EACR,UAAA,GAAa,QAAA,CAAS,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,gBAAA,MAAsB,WAAA;EAC/D,OAAA,GAAU,QAAA,QAAgB,KAAA,CAAM,KAAA,CAAM,MAAA,CAAO,gBAAA,MAAsB,QAAA;AAAA,UACpE,WAAA,GAyEoB,OAAA,CAAO,KAAA,CAAA,MAAA,CAAA,gBAAA,OAAmB,KAAA,CAAK,KAAA,CAAA,MAAA,CAAA,gBAAA,KAAmB,WAAA,IAAW,IAAA,QAAA,KAAA,WAAA,QAAA;;;;;;;;;;;;;;;;;;KAwBxE,SAAA,WAAoB,UAAA,QAAkB,WAAA,KAAgB,CAAA,kBAC7D,IAAA,uBAED,CAAA,SAAU,KAAA,YACR,CAAA"}
@@ -74,16 +74,6 @@ function defineModel(options) {
74
74
  */
75
75
  static deleteStrategy = options.deleteStrategy;
76
76
  /**
77
- * Strict mode for unknown fields. Defaults to `"strip"` to match the base
78
- * `Model` static default.
79
- */
80
- static strictMode = options.strictMode || "strip";
81
- /**
82
- * Auto-generate sequential IDs (NoSQL only). Defaults to `true` to match
83
- * the base `Model` static default.
84
- */
85
- static autoGenerateId = options.autoGenerateId ?? true;
86
- /**
87
77
  * Random increment for auto-generated IDs. When undefined, falls through
88
78
  * to the data-source default.
89
79
  */
@@ -94,6 +84,8 @@ function defineModel(options) {
94
84
  */
95
85
  static initialId = options.initialId;
96
86
  }
87
+ if (options.strictMode !== void 0) DefinedModel.strictMode = options.strictMode;
88
+ if (options.autoGenerateId !== void 0) DefinedModel.autoGenerateId = options.autoGenerateId;
97
89
  if (options.properties) Object.defineProperties(DefinedModel.prototype, Object.getOwnPropertyDescriptors(options.properties));
98
90
  if (options.name) registerModelInRegistry(options.name, DefinedModel);
99
91
  if (options.statics) Object.defineProperties(DefinedModel, Object.getOwnPropertyDescriptors(options.statics));
@@ -1 +1 @@
1
- {"version":3,"file":"define-model.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/define-model.ts"],"sourcesContent":["import type { Infer, ObjectValidator } from \"@warlock.js/seal\";\nimport type { ModelSchema } from \"../model/model\";\nimport { Model } from \"../model/model\";\nimport { registerModelInRegistry } from \"../model/register-model\";\nimport type { DeleteStrategy, StrictMode } from \"../types\";\n\n/**\n * Configuration options for defining a model.\n */\nexport type DefineModelOptions<TSchema extends ModelSchema> = {\n /**\n * The database table/collection name.\n */\n table: string;\n\n /**\n * Model name\n * If provided, it will be registered in the models registery\n */\n name?: string;\n\n /**\n * The validation schema for the model.\n * Use `v.object()` from @warlock.js/seal to define the schema.\n */\n schema: ObjectValidator;\n\n /**\n * Delete strategy for this model. Controls how `destroy()` handles records.\n *\n * - `\"trash\"` — Moves the record to a trash table/collection, then deletes from the source.\n * - `\"permanent\"` — Hard delete. The record is removed from the database.\n * - `\"soft\"` — Sets a `deletedAt` timestamp; the row stays in place.\n *\n * When omitted, the runtime resolves the strategy in this priority order:\n * `destroy()` call options → this model's static → data source `defaultDeleteStrategy` → `\"permanent\"`.\n */\n deleteStrategy?: DeleteStrategy;\n\n /**\n * Behavior when the model is asked to write a field that is not declared in `schema`.\n *\n * - `\"strip\"` — Drop unknown fields silently (default).\n * - `\"fail\"` — Throw a validation error on unknown fields.\n * - `\"allow\"` — Pass unknown fields through to the database as-is.\n */\n strictMode?: StrictMode;\n\n /**\n * Auto-generate a sequential `id` field on insert (NoSQL only).\n *\n * SQL drivers use native AUTO_INCREMENT and ignore this. When omitted, the\n * MongoDB driver defaults to `true`; the PostgreSQL driver defaults to `false`.\n */\n autoGenerateId?: boolean;\n\n /**\n * Use a random increment (1–10) instead of a fixed step when auto-generating IDs.\n *\n * When omitted, falls through to the driver/data-source default.\n */\n randomIncrement?: boolean;\n\n /**\n * Initial ID value for the first record when auto-generating IDs.\n *\n * When omitted, falls through to the driver/data-source default (typically `1`).\n */\n initialId?: number;\n\n /**\n * Optional: Custom instance properties (getters/setters/methods).\n * Define computed properties, custom getters, or instance methods.\n *\n * The `this` context will be the Model instance, giving you access to\n * all Model methods like `get()`, `set()`, `save()`, etc.\n *\n * @example\n * ```typescript\n * properties: {\n * get fullName(this: Model<UserSchema>) {\n * return `${this.get(\"firstName\")} ${this.get(\"lastName\")}`;\n * },\n * get isActive(this: Model<UserSchema>) {\n * return this.get(\"status\") === \"active\";\n * },\n * async sendEmail(this: Model<UserSchema>, subject: string) {\n * // this.get(), this.save(), etc. all work!\n * },\n * }\n * ```\n */\n properties?: ThisType<Model<TSchema>> & Record<string, any>;\n\n /**\n * Optional: Custom static methods.\n * Define class-level methods like custom finders or utilities.\n *\n * @example\n * ```typescript\n * statics: {\n * async findByEmail(email: string) {\n * return this.first({ email });\n * },\n * async findActive() {\n * return this.query().where(\"status\", \"active\").get();\n * },\n * }\n * ```\n */\n statics?: Record<string, any>;\n};\n\n/**\n * Define a model with a clean, concise API.\n *\n * This utility function creates a Model class with the specified configuration,\n * reducing boilerplate and providing a more declarative way to define models.\n *\n * @param options - Model configuration options\n * @returns A Model class with the specified configuration\n *\n * @example\n * ```typescript\n * import { defineModel } from \"@warlock.js/cascade\";\n * import { v } from \"@warlock.js/seal\";\n *\n * export const User = defineModel({\n * table: \"users\",\n * schema: v.object({\n * name: v.string().required().trim(),\n * email: v.string().email().required().lowercase(),\n * password: v.string().required().min(6),\n * role: v.string().default(\"user\"),\n * }),\n * deleteStrategy: \"soft\",\n * });\n *\n * // Usage\n * const user = await User.create({\n * name: \"John Doe\",\n * email: \"john@example.com\",\n * password: \"secret123\",\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With type inference\n * export const Post = defineModel({\n * table: \"posts\",\n * schema: v.object({\n * title: v.string().required(),\n * content: v.string().required(),\n * authorId: v.number().required(),\n * published: v.boolean().default(false),\n * }),\n * });\n *\n * // TypeScript knows the exact type!\n * const post = await Post.create({\n * title: \"Hello World\",\n * content: \"My first post\",\n * authorId: 1,\n * });\n *\n * console.log(post.title); // ✅ Type-safe!\n * ```\n */\nexport function defineModel<\n TSchema extends ModelSchema,\n TSchemaValidator extends ObjectValidator = ObjectValidator,\n TProperties extends Record<string, any> = {},\n TStatics extends Record<string, any> = {},\n>(\n options: DefineModelOptions<TSchema> & {\n schema: TSchemaValidator;\n properties?: ThisType<Model<Infer.Output<TSchemaValidator>>> & TProperties;\n statics?: ThisType<typeof Model<Infer.Output<TSchemaValidator>>> & TStatics;\n },\n) {\n type InferredSchema = Infer.Output<TSchemaValidator>;\n\n class DefinedModel extends Model<InferredSchema> {\n /**\n * Table/collection name\n */\n public static table = options.table;\n\n /**\n * Validation schema\n */\n public static schema = options.schema;\n\n /**\n * Delete strategy. When undefined, the runtime resolves via call options →\n * data source `defaultDeleteStrategy` → `\"permanent\"`.\n */\n public static deleteStrategy: DeleteStrategy | undefined = options.deleteStrategy;\n\n /**\n * Strict mode for unknown fields. Defaults to `\"strip\"` to match the base\n * `Model` static default.\n */\n public static strictMode: StrictMode = options.strictMode || \"strip\";\n\n /**\n * Auto-generate sequential IDs (NoSQL only). Defaults to `true` to match\n * the base `Model` static default.\n */\n public static autoGenerateId: boolean = options.autoGenerateId ?? true;\n\n /**\n * Random increment for auto-generated IDs. When undefined, falls through\n * to the data-source default.\n */\n public static randomIncrement: boolean | undefined = options.randomIncrement;\n\n /**\n * Initial ID value for the first record when auto-generating IDs. When\n * undefined, falls through to the data-source default.\n */\n public static initialId: number | undefined = options.initialId;\n }\n\n // Apply custom instance properties (getters/setters/methods)\n if (options.properties) {\n Object.defineProperties(\n DefinedModel.prototype,\n Object.getOwnPropertyDescriptors(options.properties),\n );\n }\n\n if (options.name) {\n registerModelInRegistry(options.name, DefinedModel);\n }\n\n // Apply custom static methods\n if (options.statics) {\n Object.defineProperties(DefinedModel, Object.getOwnPropertyDescriptors(options.statics));\n }\n\n // Return with proper type inference.\n //\n // The constructed type references the EXPORTED `Model` (instance + statics)\n // rather than the local `DefinedModel` class. `DefinedModel` is declared\n // inside this function, so emitting it into the public `.d.ts` would force the\n // declaration emitter to inline it as an anonymous class type — which fails\n // with TS4094 (\"anonymous class type may not be private or protected\", from\n // Model's `protected isActiveColumn`) and crashes the dts bundler on Model's\n // getters. `DefinedModel` adds no instance members over `Model<InferredSchema>`\n // and no statics beyond those already declared on `Model`, so this is a\n // faithful, emittable equivalent of the previously-inferred return type.\n type DefinedModelClass = {\n new (initialData?: Partial<InferredSchema>): Model<InferredSchema> & TProperties;\n } & Omit<typeof Model, \"new\"> &\n TStatics;\n\n return DefinedModel as unknown as DefinedModelClass;\n}\n\n/**\n * Type helper to infer the schema type from a defined model.\n *\n * @example\n * ```typescript\n * const User = defineModel({\n * table: \"users\",\n * schema: v.object({\n * name: v.string(),\n * email: v.string(),\n * }),\n * });\n *\n * type UserType = ModelType<typeof User>;\n * // { name: string; email: string; }\n * ```\n */\nexport type ModelType<T extends ReturnType<typeof defineModel>> = T extends new (\n ...args: any[]\n) => infer R\n ? R extends Model<infer S>\n ? S\n : never\n : never;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyKA,SAAgB,YAMd,SAKA;CAGA,MAAM,qBAAqB,MAAsB;;;;EAI/C,OAAc,QAAQ,QAAQ;;;;EAK9B,OAAc,SAAS,QAAQ;;;;;EAM/B,OAAc,iBAA6C,QAAQ;;;;;EAMnE,OAAc,aAAyB,QAAQ,cAAc;;;;;EAM7D,OAAc,iBAA0B,QAAQ,kBAAkB;;;;;EAMlE,OAAc,kBAAuC,QAAQ;;;;;EAM7D,OAAc,YAAgC,QAAQ;CACxD;CAGA,IAAI,QAAQ,YACV,OAAO,iBACL,aAAa,WACb,OAAO,0BAA0B,QAAQ,UAAU,CACrD;CAGF,IAAI,QAAQ,MACV,wBAAwB,QAAQ,MAAM,YAAY;CAIpD,IAAI,QAAQ,SACV,OAAO,iBAAiB,cAAc,OAAO,0BAA0B,QAAQ,OAAO,CAAC;CAmBzF,OAAO;AACT"}
1
+ {"version":3,"file":"define-model.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/define-model.ts"],"sourcesContent":["import type { Infer, ObjectValidator } from \"@warlock.js/seal\";\nimport type { ModelSchema } from \"../model/model\";\nimport { Model } from \"../model/model\";\nimport { registerModelInRegistry } from \"../model/register-model\";\nimport type { DeleteStrategy, StrictMode } from \"../types\";\n\n/**\n * Configuration options for defining a model.\n */\nexport type DefineModelOptions<TSchema extends ModelSchema> = {\n /**\n * The database table/collection name.\n */\n table: string;\n\n /**\n * Model name\n * If provided, it will be registered in the models registery\n */\n name?: string;\n\n /**\n * The validation schema for the model.\n * Use `v.object()` from @warlock.js/seal to define the schema.\n */\n schema: ObjectValidator;\n\n /**\n * Delete strategy for this model. Controls how `destroy()` handles records.\n *\n * - `\"trash\"` — Moves the record to a trash table/collection, then deletes from the source.\n * - `\"permanent\"` — Hard delete. The record is removed from the database.\n * - `\"soft\"` — Sets a `deletedAt` timestamp; the row stays in place.\n *\n * When omitted, the runtime resolves the strategy in this priority order:\n * `destroy()` call options → this model's static → data source `defaultDeleteStrategy` → `\"permanent\"`.\n */\n deleteStrategy?: DeleteStrategy;\n\n /**\n * Behavior when the model is asked to write a field that is not declared in `schema`.\n *\n * - `\"strip\"` — Drop unknown fields silently (default).\n * - `\"fail\"` — Throw a validation error on unknown fields.\n * - `\"allow\"` — Pass unknown fields through to the database as-is.\n */\n strictMode?: StrictMode;\n\n /**\n * Auto-generate a sequential `id` field on insert (NoSQL only).\n *\n * SQL drivers use native AUTO_INCREMENT and ignore this. When omitted, the\n * MongoDB driver defaults to `true`; the PostgreSQL driver defaults to `false`.\n */\n autoGenerateId?: boolean;\n\n /**\n * Use a random increment (1–10) instead of a fixed step when auto-generating IDs.\n *\n * When omitted, falls through to the driver/data-source default.\n */\n randomIncrement?: boolean;\n\n /**\n * Initial ID value for the first record when auto-generating IDs.\n *\n * When omitted, falls through to the driver/data-source default (typically `1`).\n */\n initialId?: number;\n\n /**\n * Optional: Custom instance properties (getters/setters/methods).\n * Define computed properties, custom getters, or instance methods.\n *\n * The `this` context will be the Model instance, giving you access to\n * all Model methods like `get()`, `set()`, `save()`, etc.\n *\n * @example\n * ```typescript\n * properties: {\n * get fullName(this: Model<UserSchema>) {\n * return `${this.get(\"firstName\")} ${this.get(\"lastName\")}`;\n * },\n * get isActive(this: Model<UserSchema>) {\n * return this.get(\"status\") === \"active\";\n * },\n * async sendEmail(this: Model<UserSchema>, subject: string) {\n * // this.get(), this.save(), etc. all work!\n * },\n * }\n * ```\n */\n properties?: ThisType<Model<TSchema>> & Record<string, any>;\n\n /**\n * Optional: Custom static methods.\n * Define class-level methods like custom finders or utilities.\n *\n * @example\n * ```typescript\n * statics: {\n * async findByEmail(email: string) {\n * return this.first({ email });\n * },\n * async findActive() {\n * return this.query().where(\"status\", \"active\").get();\n * },\n * }\n * ```\n */\n statics?: Record<string, any>;\n};\n\n/**\n * Define a model with a clean, concise API.\n *\n * This utility function creates a Model class with the specified configuration,\n * reducing boilerplate and providing a more declarative way to define models.\n *\n * @param options - Model configuration options\n * @returns A Model class with the specified configuration\n *\n * @example\n * ```typescript\n * import { defineModel } from \"@warlock.js/cascade\";\n * import { v } from \"@warlock.js/seal\";\n *\n * export const User = defineModel({\n * table: \"users\",\n * schema: v.object({\n * name: v.string().required().trim(),\n * email: v.string().email().required().lowercase(),\n * password: v.string().required().min(6),\n * role: v.string().default(\"user\"),\n * }),\n * deleteStrategy: \"soft\",\n * });\n *\n * // Usage\n * const user = await User.create({\n * name: \"John Doe\",\n * email: \"john@example.com\",\n * password: \"secret123\",\n * });\n * ```\n *\n * @example\n * ```typescript\n * // With type inference\n * export const Post = defineModel({\n * table: \"posts\",\n * schema: v.object({\n * title: v.string().required(),\n * content: v.string().required(),\n * authorId: v.number().required(),\n * published: v.boolean().default(false),\n * }),\n * });\n *\n * // TypeScript knows the exact type!\n * const post = await Post.create({\n * title: \"Hello World\",\n * content: \"My first post\",\n * authorId: 1,\n * });\n *\n * console.log(post.title); // ✅ Type-safe!\n * ```\n */\nexport function defineModel<\n TSchema extends ModelSchema,\n TSchemaValidator extends ObjectValidator = ObjectValidator,\n TProperties extends Record<string, any> = {},\n TStatics extends Record<string, any> = {},\n>(\n options: DefineModelOptions<TSchema> & {\n schema: TSchemaValidator;\n properties?: ThisType<Model<Infer.Output<TSchemaValidator>>> & TProperties;\n statics?: ThisType<typeof Model<Infer.Output<TSchemaValidator>>> & TStatics;\n },\n) {\n type InferredSchema = Infer.Output<TSchemaValidator>;\n\n class DefinedModel extends Model<InferredSchema> {\n /**\n * Table/collection name\n */\n public static table = options.table;\n\n /**\n * Validation schema\n */\n public static schema = options.schema;\n\n /**\n * Delete strategy. When undefined, the runtime resolves via call options →\n * data source `defaultDeleteStrategy` → `\"permanent\"`.\n */\n public static deleteStrategy: DeleteStrategy | undefined = options.deleteStrategy;\n\n /**\n * Random increment for auto-generated IDs. When undefined, falls through\n * to the data-source default.\n */\n public static randomIncrement: boolean | undefined = options.randomIncrement;\n\n /**\n * Initial ID value for the first record when auto-generating IDs. When\n * undefined, falls through to the data-source default.\n */\n public static initialId: number | undefined = options.initialId;\n }\n\n // Only set when provided: the base Model owns the framework fallback,\n // and an own `undefined` would shadow it and block data-source defaults.\n if (options.strictMode !== undefined) {\n DefinedModel.strictMode = options.strictMode;\n }\n\n if (options.autoGenerateId !== undefined) {\n DefinedModel.autoGenerateId = options.autoGenerateId;\n }\n\n // Apply custom instance properties (getters/setters/methods)\n if (options.properties) {\n Object.defineProperties(\n DefinedModel.prototype,\n Object.getOwnPropertyDescriptors(options.properties),\n );\n }\n\n if (options.name) {\n registerModelInRegistry(options.name, DefinedModel);\n }\n\n // Apply custom static methods\n if (options.statics) {\n Object.defineProperties(DefinedModel, Object.getOwnPropertyDescriptors(options.statics));\n }\n\n // Return with proper type inference.\n //\n // The constructed type references the EXPORTED `Model` (instance + statics)\n // rather than the local `DefinedModel` class. `DefinedModel` is declared\n // inside this function, so emitting it into the public `.d.ts` would force the\n // declaration emitter to inline it as an anonymous class type — which fails\n // with TS4094 (\"anonymous class type may not be private or protected\", from\n // Model's `protected isActiveColumn`) and crashes the dts bundler on Model's\n // getters. `DefinedModel` adds no instance members over `Model<InferredSchema>`\n // and no statics beyond those already declared on `Model`, so this is a\n // faithful, emittable equivalent of the previously-inferred return type.\n type DefinedModelClass = {\n new (initialData?: Partial<InferredSchema>): Model<InferredSchema> & TProperties;\n } & Omit<typeof Model, \"new\"> &\n TStatics;\n\n return DefinedModel as unknown as DefinedModelClass;\n}\n\n/**\n * Type helper to infer the schema type from a defined model.\n *\n * @example\n * ```typescript\n * const User = defineModel({\n * table: \"users\",\n * schema: v.object({\n * name: v.string(),\n * email: v.string(),\n * }),\n * });\n *\n * type UserType = ModelType<typeof User>;\n * // { name: string; email: string; }\n * ```\n */\nexport type ModelType<T extends ReturnType<typeof defineModel>> = T extends new (\n ...args: any[]\n) => infer R\n ? R extends Model<infer S>\n ? S\n : never\n : never;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyKA,SAAgB,YAMd,SAKA;CAGA,MAAM,qBAAqB,MAAsB;;;;EAI/C,OAAc,QAAQ,QAAQ;;;;EAK9B,OAAc,SAAS,QAAQ;;;;;EAM/B,OAAc,iBAA6C,QAAQ;;;;;EAMnE,OAAc,kBAAuC,QAAQ;;;;;EAM7D,OAAc,YAAgC,QAAQ;CACxD;CAIA,IAAI,QAAQ,eAAe,QACzB,aAAa,aAAa,QAAQ;CAGpC,IAAI,QAAQ,mBAAmB,QAC7B,aAAa,iBAAiB,QAAQ;CAIxC,IAAI,QAAQ,YACV,OAAO,iBACL,aAAa,WACb,OAAO,0BAA0B,QAAQ,UAAU,CACrD;CAGF,IAAI,QAAQ,MACV,wBAAwB,QAAQ,MAAM,YAAY;CAIpD,IAAI,QAAQ,SACV,OAAO,iBAAiB,cAAc,OAAO,0BAA0B,QAAQ,OAAO,CAAC;CAmBzF,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  //#region ../cascade/src/utils/is-valid-date-value.ts
2
- const isoRegex = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?)?$/;
2
+ const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?(Z|[+-]\d{2}:?\d{2})$/;
3
3
  /**
4
4
  * Check if the given value is a valid date value
5
5
  */
@@ -17,7 +17,8 @@ function isValidDateValue(value) {
17
17
  if (datePart === void 0) return false;
18
18
  const [y, m, d] = datePart.split("-").map(Number);
19
19
  if (y === void 0 || m === void 0 || d === void 0) return false;
20
- return date.getUTCFullYear() === y && date.getUTCMonth() + 1 === m && date.getUTCDate() === d;
20
+ const written = new Date(Date.UTC(y, m - 1, d));
21
+ return written.getUTCFullYear() === y && written.getUTCMonth() + 1 === m && written.getUTCDate() === d;
21
22
  }
22
23
 
23
24
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"is-valid-date-value.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/is-valid-date-value.ts"],"sourcesContent":["const isoRegex = /^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?Z?)?$/;\n\n/**\n * Check if the given value is a valid date value\n */\nexport function isValidDateValue(value: unknown): boolean {\n // ✅ Handle timestamps\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) return false;\n\n const date = new Date(value);\n return !Number.isNaN(date.getTime());\n }\n\n // ❌ Only allow strict ISO strings\n if (typeof value !== \"string\") return false;\n\n if (!isoRegex.test(value)) return false;\n\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return false;\n\n // 🔥 Critical step: prevent JS auto-correction\n // Example: \"2023-02-31\" → March 3 (WRONG but \"valid\")\n const datePart = value.split(\"T\")[0];\n if (datePart === undefined) return false;\n\n const [y, m, d] = datePart.split(\"-\").map(Number);\n if (y === undefined || m === undefined || d === undefined) return false;\n\n return date.getUTCFullYear() === y && date.getUTCMonth() + 1 === m && date.getUTCDate() === d;\n}\n"],"mappings":";AAAA,MAAM,WAAW;;;;AAKjB,SAAgB,iBAAiB,OAAyB;CAExD,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;EAEpC,MAAM,OAAO,IAAI,KAAK,KAAK;EAC3B,OAAO,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC;CACrC;CAGA,IAAI,OAAO,UAAU,UAAU,OAAO;CAEtC,IAAI,CAAC,SAAS,KAAK,KAAK,GAAG,OAAO;CAElC,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;CAIzC,MAAM,WAAW,MAAM,MAAM,GAAG,CAAC,CAAC;CAClC,IAAI,aAAa,QAAW,OAAO;CAEnC,MAAM,CAAC,GAAG,GAAG,KAAK,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAChD,IAAI,MAAM,UAAa,MAAM,UAAa,MAAM,QAAW,OAAO;CAElE,OAAO,KAAK,eAAe,MAAM,KAAK,KAAK,YAAY,IAAI,MAAM,KAAK,KAAK,WAAW,MAAM;AAC9F"}
1
+ {"version":3,"file":"is-valid-date-value.mjs","names":[],"sources":["../../../../../../../cascade/src/utils/is-valid-date-value.ts"],"sourcesContent":["// Strings must carry a time part AND a timezone (Z or ±hh:mm), so plain text such as\n// \"2024-01-15\" or a zone-less local datetime is never turned into a Date.\nconst isoRegex = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?(Z|[+-]\\d{2}:?\\d{2})$/;\n\n/**\n * Check if the given value is a valid date value\n */\nexport function isValidDateValue(value: unknown): boolean {\n // ✅ Handle timestamps\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) return false;\n\n const date = new Date(value);\n return !Number.isNaN(date.getTime());\n }\n\n // ❌ Only allow strict ISO strings\n if (typeof value !== \"string\") return false;\n\n if (!isoRegex.test(value)) return false;\n\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return false;\n\n // 🔥 Critical step: prevent JS auto-correction\n // Example: \"2023-02-31\" → March 3 (WRONG but \"valid\")\n // Checked on the written date part itself, so a timezone offset can't shift it.\n const datePart = value.split(\"T\")[0];\n if (datePart === undefined) return false;\n\n const [y, m, d] = datePart.split(\"-\").map(Number);\n if (y === undefined || m === undefined || d === undefined) return false;\n\n const written = new Date(Date.UTC(y, m - 1, d));\n\n return written.getUTCFullYear() === y && written.getUTCMonth() + 1 === m && written.getUTCDate() === d;\n}\n"],"mappings":";AAEA,MAAM,WAAW;;;;AAKjB,SAAgB,iBAAiB,OAAyB;CAExD,IAAI,OAAO,UAAU,UAAU;EAC7B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;EAEpC,MAAM,OAAO,IAAI,KAAK,KAAK;EAC3B,OAAO,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC;CACrC;CAGA,IAAI,OAAO,UAAU,UAAU,OAAO;CAEtC,IAAI,CAAC,SAAS,KAAK,KAAK,GAAG,OAAO;CAElC,MAAM,OAAO,IAAI,KAAK,KAAK;CAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,GAAG,OAAO;CAKzC,MAAM,WAAW,MAAM,MAAM,GAAG,CAAC,CAAC;CAClC,IAAI,aAAa,QAAW,OAAO;CAEnC,MAAM,CAAC,GAAG,GAAG,KAAK,SAAS,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAChD,IAAI,MAAM,UAAa,MAAM,UAAa,MAAM,QAAW,OAAO;CAElE,MAAM,UAAU,IAAI,KAAK,KAAK,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;CAE9C,OAAO,QAAQ,eAAe,MAAM,KAAK,QAAQ,YAAY,IAAI,MAAM,KAAK,QAAQ,WAAW,MAAM;AACvG"}
@@ -1 +1 @@
1
- {"version":3,"file":"once-connected.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/once-connected.ts"],"mappings":";;;;;AA0EA;;;;;;;;;;;;;;;;AAE6C;AAmI7C;;;;;;;;;;;;;;;;AAE6C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAvI7B,aAAA,CACd,0BAAA,EAA4B,UAAA,cAAwB,UAAA,EAAY,UAAA,YAChE,QAAA,IAAY,UAAA,EAAY,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmIV,gBAAA,CACd,0BAAA,EAA4B,UAAA,cAAwB,UAAA,EAAY,UAAA,YAChE,QAAA,IAAY,UAAA,EAAY,UAAA"}
1
+ {"version":3,"file":"once-connected.d.mts","names":[],"sources":["../../../../../../../cascade/src/utils/once-connected.ts"],"mappings":";;;;;AAqFA;;;;;;;;;;;;;;;;AAE6C;AAmI7C;;;;;;;;;;;;;;;;AAE6C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAvI7B,aAAA,CACd,0BAAA,EAA4B,UAAA,cAAwB,UAAA,EAAY,UAAA,YAChE,QAAA,IAAY,UAAA,EAAY,UAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAmIV,gBAAA,CACd,0BAAA,EAA4B,UAAA,cAAwB,UAAA,EAAY,UAAA,YAChE,QAAA,IAAY,UAAA,EAAY,UAAA"}
@@ -2,6 +2,16 @@ import { dataSourceRegistry } from "../data-source/data-source-registry.mjs";
2
2
 
3
3
  //#region ../cascade/src/utils/once-connected.ts
4
4
  /**
5
+ * Whether the given source is the registry's actual default (not just the `isDefault` option flag).
6
+ */
7
+ function isRegistryDefault(ds) {
8
+ try {
9
+ return dataSourceRegistry.get() === ds;
10
+ } catch {
11
+ return false;
12
+ }
13
+ }
14
+ /**
5
15
  * Execute a callback once the driver is connected.
6
16
  *
7
17
  * If the driver is already connected, the callback is executed immediately.
@@ -93,7 +103,7 @@ function onceConnected(dataSourceOrNameOrCallback, callback) {
93
103
  return;
94
104
  }
95
105
  const listener = (ds) => {
96
- if (typeof targetDataSource === "string" ? targetDataSource === "default" ? ds.isDefault : ds.name === targetDataSource : ds === targetDataSource) {
106
+ if (typeof targetDataSource === "string" ? targetDataSource === "default" ? isRegistryDefault(ds) : ds.name === targetDataSource : ds === targetDataSource) {
97
107
  dataSourceRegistry.off("connected", listener);
98
108
  actualCallback(ds);
99
109
  }
@@ -192,7 +202,7 @@ function onceDisconnected(dataSourceOrNameOrCallback, callback) {
192
202
  return;
193
203
  }
194
204
  const listener = (ds) => {
195
- if (typeof targetDataSource === "string" ? targetDataSource === "default" ? ds.isDefault : ds.name === targetDataSource : ds === targetDataSource) actualCallback(ds);
205
+ if (typeof targetDataSource === "string" ? targetDataSource === "default" ? isRegistryDefault(ds) : ds.name === targetDataSource : ds === targetDataSource) actualCallback(ds);
196
206
  else dataSourceRegistry.once("disconnected", listener);
197
207
  };
198
208
  dataSourceRegistry.once("disconnected", listener);