@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
@@ -2,12 +2,27 @@ import { dataSourceRegistry } from "../data-source/data-source-registry.mjs";
2
2
  import { parseCreatedAt } from "./parse-created-at.mjs";
3
3
  import { sortMigrations, sortMigrationsForRollback } from "./migration-order.mjs";
4
4
  import { SQLGrammar } from "./sql-grammar.mjs";
5
- import { colors } from "@mongez/copper";
6
5
  import { log } from "@warlock.js/logger";
6
+ import { colors } from "@mongez/copper";
7
7
  import fs from "fs";
8
8
  import path from "path";
9
9
 
10
10
  //#region ../cascade/src/migration/migration-runner.ts
11
+ /** Name of the lock document Mongo migrations claim inside the migrations collection. */
12
+ const MIGRATION_LOCK_NAME = "__warlock_migration_lock__";
13
+ const MIGRATION_LOCK_KEY = "warlock:migrations";
14
+ const MIGRATION_LOCK_TTL_MS = 600 * 1e3;
15
+ const MIGRATION_LOCK_WAIT_MS = 60 * 1e3;
16
+ const MIGRATION_LOCK_RETRY_MS = 1e3;
17
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
18
+ /**
19
+ * Derive a migration name from a file path: drop everything after the first dot
20
+ * and strip a trailing `-migration` / `_migration` suffix. Shared by every CLI
21
+ * so the same file is recorded under the same name.
22
+ */
23
+ function inferMigrationName(file) {
24
+ return (path.basename(file).split(".")[0] ?? "").replace(/-migration$/, "").replace(/_migration$/, "");
25
+ }
11
26
  /**
12
27
  * Migration runner that executes migrations.
13
28
  *
@@ -47,8 +62,8 @@ var MigrationRunner = class {
47
62
  migrations = [];
48
63
  /** Data source to use */
49
64
  dataSource;
50
- /** Cached migration driver */
51
- cachedMigrationDriver;
65
+ /** Cached migration drivers, one per data source */
66
+ migrationDrivers = /* @__PURE__ */ new Map();
52
67
  /** Table name for tracking migrations */
53
68
  migrationsTable;
54
69
  /** Whether to log operations */
@@ -68,7 +83,7 @@ var MigrationRunner = class {
68
83
  */
69
84
  setDataSource(dataSource) {
70
85
  this.dataSource = dataSource;
71
- this.cachedMigrationDriver = void 0;
86
+ this.migrationDrivers.clear();
72
87
  return this;
73
88
  }
74
89
  /**
@@ -81,9 +96,140 @@ var MigrationRunner = class {
81
96
  /**
82
97
  * Get the migration driver.
83
98
  */
84
- getMigrationDriver() {
85
- if (!this.cachedMigrationDriver) this.cachedMigrationDriver = this.getDataSource().driver.migrationDriver();
86
- return this.cachedMigrationDriver;
99
+ getMigrationDriver(dataSource = this.getDataSource()) {
100
+ let driver = this.migrationDrivers.get(dataSource);
101
+ if (!driver) {
102
+ driver = dataSource.driver.migrationDriver();
103
+ this.migrationDrivers.set(dataSource, driver);
104
+ }
105
+ return driver;
106
+ }
107
+ /**
108
+ * Resolve a migration's declared data source (name or instance).
109
+ * Falls back to the runner's own data source when it declares none.
110
+ */
111
+ resolveDataSource(declared) {
112
+ if (!declared) return this.getDataSource();
113
+ if (typeof declared === "string") return dataSourceRegistry.get(declared);
114
+ return declared;
115
+ }
116
+ /** Data source a registered migration class runs against. */
117
+ sourceOf(MigrationClass) {
118
+ return this.resolveDataSource(new MigrationClass().dataSource);
119
+ }
120
+ /** Distinct data sources across the registered migrations (runner's own included). */
121
+ getSources() {
122
+ const sources = new Set([this.getDataSource()]);
123
+ for (const MigrationClass of this.migrations) sources.add(this.sourceOf(MigrationClass));
124
+ return [...sources];
125
+ }
126
+ /**
127
+ * Resolve whether a migration runs in a transaction:
128
+ * 1. migration-level flag (instance, else the static from `Migration.create/alter`)
129
+ * 2. `CONCURRENTLY` statements cannot run in a transaction block
130
+ * 3. data-source config
131
+ * 4. driver default
132
+ */
133
+ resolveTransactional(MigrationClass, migration, dataSource, statements = []) {
134
+ const explicit = migration.transactional ?? MigrationClass.transactional;
135
+ if (explicit !== void 0) return explicit;
136
+ if (statements.some((sql) => /\bCONCURRENTLY\b/i.test(sql))) return false;
137
+ return dataSource.migrations?.transactional ?? this.getMigrationDriver(dataSource).getDefaultTransactional();
138
+ }
139
+ /** Locks held by the run in progress (Mongo TTL heartbeat). */
140
+ activeLocks = [];
141
+ /** Extend every held lock's TTL; called between migrations. */
142
+ async refreshLocks() {
143
+ for (const lock of this.activeLocks) await lock.refresh();
144
+ }
145
+ /**
146
+ * Run `callback` while holding a migration lock on every given data source, so
147
+ * concurrent `migrate` invocations (replicas, init containers) serialize.
148
+ */
149
+ async withLock(sources, callback) {
150
+ const held = [];
151
+ try {
152
+ for (const dataSource of sources) {
153
+ const lock = await this.acquireLock(dataSource);
154
+ if (lock) held.push(lock);
155
+ }
156
+ this.activeLocks = held;
157
+ return await callback();
158
+ } finally {
159
+ this.activeLocks = [];
160
+ for (const lock of held.reverse()) try {
161
+ await lock.release();
162
+ } catch (error) {
163
+ log.warn("database", "migration", `Failed to release migration lock: ${error instanceof Error ? error.message : String(error)}`);
164
+ }
165
+ }
166
+ }
167
+ /** Acquire the lock for one data source; `undefined` when its driver has no lock support. */
168
+ async acquireLock(dataSource) {
169
+ const driver = dataSource.driver;
170
+ if (driver.name === "postgres" && driver.getClient) {
171
+ const client = await driver.getClient().connect?.();
172
+ if (!client) return void 0;
173
+ try {
174
+ await client.query("SELECT pg_advisory_lock(hashtext($1))", [MIGRATION_LOCK_KEY]);
175
+ } catch (error) {
176
+ client.release();
177
+ throw error;
178
+ }
179
+ return {
180
+ refresh: async () => {},
181
+ release: async () => {
182
+ try {
183
+ await client.query("SELECT pg_advisory_unlock(hashtext($1))", [MIGRATION_LOCK_KEY]);
184
+ } finally {
185
+ client.release();
186
+ }
187
+ }
188
+ };
189
+ }
190
+ if (driver.getDatabase) {
191
+ await this.getMigrationDriver(dataSource).ensureMigrationsTable(this.migrationsTable);
192
+ const collection = driver.getDatabase().collection(this.migrationsTable);
193
+ const owner = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
194
+ const deadline = Date.now() + MIGRATION_LOCK_WAIT_MS;
195
+ const claim = async () => {
196
+ const now = /* @__PURE__ */ new Date();
197
+ try {
198
+ await collection.findOneAndUpdate({
199
+ name: MIGRATION_LOCK_NAME,
200
+ $or: [{ expiresAt: { $lt: now } }, { owner }]
201
+ }, {
202
+ $set: {
203
+ owner,
204
+ expiresAt: new Date(now.getTime() + MIGRATION_LOCK_TTL_MS)
205
+ },
206
+ $setOnInsert: { batch: 0 }
207
+ }, { upsert: true });
208
+ return true;
209
+ } catch (error) {
210
+ if (error.code === 11e3) return false;
211
+ throw error;
212
+ }
213
+ };
214
+ while (!await claim()) {
215
+ if (Date.now() >= deadline) {
216
+ const holder = await collection.findOne({ name: MIGRATION_LOCK_NAME });
217
+ throw new Error(`Could not acquire the migration lock within ${MIGRATION_LOCK_WAIT_MS / 1e3}s; held by "${holder?.owner ?? "unknown"}" until ${holder?.expiresAt ?? "unknown"}.`);
218
+ }
219
+ await sleep(MIGRATION_LOCK_RETRY_MS);
220
+ }
221
+ return {
222
+ refresh: async () => {
223
+ await claim();
224
+ },
225
+ release: async () => {
226
+ await collection.deleteOne({
227
+ name: MIGRATION_LOCK_NAME,
228
+ owner
229
+ });
230
+ }
231
+ };
232
+ }
87
233
  }
88
234
  /**
89
235
  * Register a migration.
@@ -103,7 +249,12 @@ var MigrationRunner = class {
103
249
  register(MigrationClass) {
104
250
  const name = MigrationClass.migrationName;
105
251
  if (!name) throw new Error("Migration class must have a static 'migrationName' property set. Set it in CLI after importing: MigrationClass.migrationName = \"filename\";");
106
- if (!this.migrations.some((m) => m.migrationName === name)) this.migrations.push(MigrationClass);
252
+ const existing = this.migrations.find((m) => m.migrationName === name);
253
+ if (existing) {
254
+ if (existing !== MigrationClass) throw new Error(`Duplicate migration name "${name}": two different migration classes resolve to the same name. Rename one of the files (or set a unique static 'migrationName').`);
255
+ return this;
256
+ }
257
+ this.migrations.push(MigrationClass);
107
258
  return this;
108
259
  }
109
260
  /**
@@ -182,116 +333,144 @@ var MigrationRunner = class {
182
333
  * ```
183
334
  */
184
335
  async runAll(options = {}) {
336
+ if (options.dryRun) return this.runAllUnlocked(options);
337
+ return this.withLock(this.getSources(), () => this.runAllUnlocked(options));
338
+ }
339
+ async runAllUnlocked(options) {
185
340
  const { dryRun = false, record = true } = options;
186
341
  const results = [];
187
- const pending = await this.getPendingMigrations();
342
+ const pending = await this.getPendingMigrations({ readOnly: dryRun });
188
343
  if (pending.length === 0) {
189
344
  log.warn("database", "migration", "Nothing to migrate.");
190
345
  return results;
191
346
  }
192
- if (this.getDataSource().driver.supportsSqlSerialization === false) {
193
- const batch = await this.getNextBatchNumber();
194
- for (const MigrationClass of pending) {
195
- const result = await this.runMigration(MigrationClass, "up", {
196
- dryRun,
197
- record,
198
- batch
199
- });
200
- results.push(result);
201
- if (!result.success) break;
202
- }
203
- return results;
204
- }
205
- log.info("database", "migration", `Found ${pending.length} pending migration(s). Generating SQL pool...`);
206
- const nextBatch = await this.getNextBatchNumber();
207
- const taggedStatements = [];
347
+ const sources = /* @__PURE__ */ new Set();
208
348
  const migrationsData = [];
349
+ log.info("database", "migration", `Found ${pending.length} pending migration(s).`);
209
350
  const extensionChecks = [];
210
351
  for (const MigrationClass of pending) {
211
352
  const migration = this.createMigrationInstance(MigrationClass);
212
353
  const name = MigrationClass.migrationName;
354
+ const dataSource = this.resolveDataSource(migration.dataSource);
355
+ sources.add(dataSource);
356
+ if (dataSource.driver.supportsSqlSerialization === false) {
357
+ migrationsData.push({
358
+ MigrationClass,
359
+ migration,
360
+ name,
361
+ dataSource,
362
+ statements: [],
363
+ steps: [],
364
+ transactional: false,
365
+ direct: true
366
+ });
367
+ continue;
368
+ }
369
+ migration.setDriver(this.getMigrationDriver(dataSource));
370
+ migration.setMigrationDefaults(dataSource.migrationDefaults);
371
+ migration.setDryRun?.(dryRun);
213
372
  await migration.up();
214
- const upStatements = migration.toSQL();
373
+ const steps = migration.toSteps?.() ?? migration.toSQL();
374
+ const statements = steps.map((step) => typeof step === "string" ? step : "-- data step (withConnection) skipped");
375
+ migration.setDryRun?.(false);
215
376
  migrationsData.push({
216
377
  MigrationClass,
217
378
  migration,
218
- name
379
+ name,
380
+ dataSource,
381
+ statements,
382
+ steps,
383
+ transactional: !!dataSource.driver.transaction && this.resolveTransactional(MigrationClass, migration, dataSource, statements),
384
+ direct: false
219
385
  });
220
- for (const sql of upStatements) {
221
- const statementType = SQLGrammar.classify(sql);
222
- if (statementType === "CREATE_EXTENSION") {
223
- const ext = SQLGrammar.extractExtensionName(sql);
224
- if (ext) extensionChecks.push(this.informIfExtensionMissing(ext));
225
- }
226
- taggedStatements.push({
227
- sql,
228
- phase: SQLGrammar.phase(sql),
229
- statementType,
230
- createdAt: MigrationClass.createdAt,
231
- migrationName: name
232
- });
386
+ for (const sql of statements) if (SQLGrammar.classify(sql) === "CREATE_EXTENSION") {
387
+ const ext = SQLGrammar.extractExtensionName(sql);
388
+ if (ext) extensionChecks.push(this.informIfExtensionMissing(ext, dataSource));
233
389
  }
234
390
  }
235
391
  await Promise.all(extensionChecks);
236
- const sortedStatements = SQLGrammar.sort(taggedStatements);
237
392
  if (dryRun) {
238
393
  log.info("database", "migration", "Dry run enabled. Would execute the following statements:");
239
- for (const statement of sortedStatements) {
240
- console.log(`-- [${statement.statementType}] Phase ${statement.phase} [${statement.migrationName}]`);
241
- console.log(statement.sql + ";\n");
394
+ for (const data of migrationsData) for (const sql of data.statements) {
395
+ console.log(`-- [${SQLGrammar.classify(sql)}] Phase ${SQLGrammar.phase(sql)} [${data.name}]`);
396
+ console.log(sql + ";\n");
242
397
  }
243
398
  return [];
244
399
  }
245
- const driver = this.getDataSource().driver;
246
- let transactionFailed = false;
247
- let errorMessage = "";
248
- /** The migration name that owns the SQL statement that threw. */
249
- let failingMigrationName;
250
- const startTime = Date.now();
251
- /**
252
- * Execute all sorted statements, capturing which migration owns the
253
- * statement that throws — so we report a precise culprit instead of
254
- * blaming every migration in the batch.
255
- */
256
- const executeStatements = async () => {
257
- for (const statement of sortedStatements) try {
258
- await driver.query(statement.sql);
259
- } catch (err) {
260
- failingMigrationName = statement.migrationName;
261
- throw err;
262
- }
263
- if (record) for (const data of migrationsData) await this.recordMigration(data.name, nextBatch, data.MigrationClass.createdAt ? parseCreatedAt(data.MigrationClass.createdAt) : /* @__PURE__ */ new Date());
264
- };
265
- try {
266
- if (driver.transaction) await driver.transaction(executeStatements);
267
- else await executeStatements();
268
- } catch (err) {
269
- transactionFailed = true;
270
- errorMessage = err instanceof Error ? err.message : String(err);
271
- }
272
- const durationMs = Date.now() - startTime;
400
+ const batches = /* @__PURE__ */ new Map();
401
+ for (const dataSource of sources) batches.set(dataSource, await this.getNextBatchNumber(dataSource));
402
+ const runs = [];
273
403
  for (const data of migrationsData) {
274
- const isCulprit = transactionFailed && data.name === failingMigrationName;
275
- const wasSkipped = transactionFailed && !isCulprit;
276
- results.push({
277
- name: data.name,
278
- table: data.migration.table,
279
- direction: "up",
280
- success: !transactionFailed,
281
- error: isCulprit ? errorMessage : void 0,
282
- durationMs: Math.round(durationMs / migrationsData.length),
283
- executedAt: /* @__PURE__ */ new Date()
284
- });
285
- if (isCulprit) log.error("database", "migration", `${colors.magenta(data.name)}: ✗ Failed: ${errorMessage}`);
286
- else if (wasSkipped) log.warn("database", "migration", `${colors.magenta(data.name)}: rolled back (batch transaction failed)`);
287
- else log.success("database", "migration", `Migrated: ${colors.magenta(data.name)} successfully`);
404
+ const last = runs[runs.length - 1];
405
+ const lastFirst = last?.[0];
406
+ if (data.transactional && last && lastFirst?.transactional && lastFirst.dataSource === data.dataSource) last.push(data);
407
+ else runs.push([data]);
288
408
  }
289
- if (transactionFailed) {
290
- log.error("database", "migration", `Batch execution failed. Rollback performed if transactional.`);
291
- throw new Error("Migration batch failed: " + errorMessage);
409
+ for (const run of runs) {
410
+ const first = run[0];
411
+ if (!first) continue;
412
+ await this.refreshLocks();
413
+ const dataSource = first.dataSource;
414
+ const nextBatch = batches.get(dataSource);
415
+ const startTime = Date.now();
416
+ if (first.direct) {
417
+ const result = await this.runMigration(first.MigrationClass, "up", {
418
+ dryRun,
419
+ record,
420
+ batch: nextBatch
421
+ });
422
+ results.push(result);
423
+ if (!result.success) break;
424
+ continue;
425
+ }
426
+ const driver = dataSource.driver;
427
+ let failed = false;
428
+ let errorMessage = "";
429
+ /** The migration that owns the SQL statement that threw. */
430
+ let failingMigrationName;
431
+ const executeRun = async () => {
432
+ for (const data of run) {
433
+ for (const step of data.steps) try {
434
+ if (typeof step === "string") await driver.query(step);
435
+ else await step.data();
436
+ } catch (err) {
437
+ failingMigrationName = data.name;
438
+ throw err;
439
+ }
440
+ if (record) await this.recordMigration(data.name, nextBatch, data.MigrationClass.createdAt ? parseCreatedAt(data.MigrationClass.createdAt) : /* @__PURE__ */ new Date(), dataSource);
441
+ }
442
+ };
443
+ try {
444
+ if (first.transactional && driver.transaction) await driver.transaction(executeRun);
445
+ else await executeRun();
446
+ } catch (err) {
447
+ failed = true;
448
+ errorMessage = err instanceof Error ? err.message : String(err);
449
+ }
450
+ const durationMs = Date.now() - startTime;
451
+ for (const data of run) {
452
+ const isCulprit = failed && data.name === failingMigrationName;
453
+ const wasSkipped = failed && !isCulprit;
454
+ results.push({
455
+ name: data.name,
456
+ table: data.migration.table,
457
+ direction: "up",
458
+ success: !failed,
459
+ error: isCulprit ? errorMessage : void 0,
460
+ durationMs: Math.round(durationMs / run.length),
461
+ executedAt: /* @__PURE__ */ new Date()
462
+ });
463
+ if (isCulprit) log.error("database", "migration", `${colors.magenta(data.name)}: ✗ Failed: ${errorMessage}`);
464
+ else if (wasSkipped) log.warn("database", "migration", `${colors.magenta(data.name)}: rolled back (batch transaction failed)`);
465
+ else log.success("database", "migration", `Migrated: ${colors.magenta(data.name)} successfully`);
466
+ }
467
+ if (failed) {
468
+ log.error("database", "migration", `Batch execution failed. Rollback performed if transactional.`);
469
+ throw new Error("Migration batch failed: " + errorMessage);
470
+ }
292
471
  }
293
472
  const successCount = results.filter((r) => r.success).length;
294
- log.success("database", "migration", `Migration bulk phase execution complete: ${successCount}/${pending.length} migrations processed successfully.`);
473
+ log.success("database", "migration", `Migration execution complete: ${successCount}/${pending.length} migrations processed successfully.`);
295
474
  return results;
296
475
  }
297
476
  /**
@@ -300,17 +479,18 @@ var MigrationRunner = class {
300
479
  */
301
480
  async exportSQL(options = {}) {
302
481
  if (this.getDataSource().driver.supportsSqlSerialization === false) throw new Error("SQL export is not supported on this data source — its driver has no SQL dialect. Migrations on this driver execute native commands through the migration driver instead.");
303
- const migrationsToExport = options.pendingOnly ? await this.getPendingMigrations() : this.migrations;
482
+ const migrationsToExport = options.pendingOnly ? await this.getPendingMigrations({ readOnly: true }) : this.migrations;
304
483
  if (migrationsToExport.length === 0) {
305
484
  log.warn("database", "migration", "No migrations to export.");
306
485
  return;
307
486
  }
308
487
  log.info("database", "migration", `Exporting ${migrationsToExport.length} ${options.pendingOnly ? "pending " : ""}migration(s) to SQL files...`);
309
488
  const upStatements = [];
310
- const downStatements = [];
311
- for (const MigrationClass of migrationsToExport) {
489
+ const downGroups = [];
490
+ for (const [index, MigrationClass] of migrationsToExport.entries()) {
312
491
  const migration = this.createMigrationInstance(MigrationClass);
313
492
  const name = MigrationClass.migrationName;
493
+ migration.setDryRun?.(true);
314
494
  await migration.up();
315
495
  for (const sql of migration.toSQL()) upStatements.push({
316
496
  sql,
@@ -320,16 +500,25 @@ var MigrationRunner = class {
320
500
  migrationName: name
321
501
  });
322
502
  await migration.down();
323
- for (const sql of migration.toSQL()) downStatements.push({
503
+ const statements = [];
504
+ for (const sql of migration.toSQL()) statements.push({
324
505
  sql,
325
506
  phase: SQLGrammar.phase(sql),
326
507
  statementType: SQLGrammar.classify(sql),
327
508
  createdAt: MigrationClass.createdAt,
328
509
  migrationName: name
329
510
  });
511
+ downGroups.push({
512
+ createdAt: MigrationClass.createdAt,
513
+ index,
514
+ statements
515
+ });
330
516
  }
331
517
  const sortedUp = SQLGrammar.sort(upStatements);
332
- const sortedDown = downStatements.reverse();
518
+ const sortedDown = downGroups.sort((a, b) => {
519
+ const byDate = (a.createdAt ?? "").localeCompare(b.createdAt ?? "");
520
+ return byDate !== 0 ? byDate : a.index - b.index;
521
+ }).reverse().flatMap((group) => group.statements);
333
522
  const upSQLString = this.formatSQLForExport(sortedUp, options.compact);
334
523
  const downSQLString = this.formatSQLForExport(sortedDown, options.compact);
335
524
  const rootPath = process.cwd();
@@ -359,16 +548,24 @@ var MigrationRunner = class {
359
548
  * @returns Results for each migration
360
549
  */
361
550
  async rollbackBatches(batches, options = {}) {
551
+ if (options.dryRun) return this.rollbackBatchesUnlocked(batches, options);
552
+ return this.withLock(this.getSources(), () => this.rollbackBatchesUnlocked(batches, options));
553
+ }
554
+ async rollbackBatchesUnlocked(batches, options) {
362
555
  const dryRun = options.dryRun ?? false;
363
556
  const record = options.record ?? true;
364
557
  const results = [];
365
- const toRollback = await this.getMigrationsToRollback(batches);
558
+ const toRollback = await this.getMigrationsToRollback(batches, {
559
+ pruneMissing: options.pruneMissing,
560
+ readOnly: dryRun
561
+ });
366
562
  if (toRollback.length === 0) {
367
563
  log.warn("database", "migration", "Nothing to rollback.");
368
564
  return results;
369
565
  }
370
566
  log.info("database", "migration", `Rolling back ${toRollback.length} migration(s).`);
371
567
  for (const MigrationClass of toRollback) {
568
+ await this.refreshLocks();
372
569
  const result = await this.runMigration(MigrationClass, "down", {
373
570
  dryRun,
374
571
  record
@@ -387,13 +584,7 @@ var MigrationRunner = class {
387
584
  * @returns Results for each migration
388
585
  */
389
586
  async rollbackAll(options = {}) {
390
- const executed = await this.getExecutedMigrations();
391
- if (executed.length === 0) {
392
- log.warn("database", "migration", "Nothing to rollback.");
393
- return [];
394
- }
395
- const maxBatch = Math.max(...executed.map((r) => r.batch));
396
- return this.rollbackBatches(maxBatch, options);
587
+ return this.rollbackBatches(Number.POSITIVE_INFINITY, options);
397
588
  }
398
589
  /**
399
590
  * Reset and re-run: rollback all then run all.
@@ -410,12 +601,15 @@ var MigrationRunner = class {
410
601
  * Get status of all registered migrations.
411
602
  */
412
603
  async status() {
413
- const executed = await this.getExecutedMigrations();
414
- const executedMap = new Map(executed.map((r) => [r.name, r]));
604
+ const executedBySource = /* @__PURE__ */ new Map();
605
+ for (const dataSource of this.getSources()) {
606
+ const executed = await this.getExecutedMigrations(dataSource, { readOnly: true });
607
+ executedBySource.set(dataSource, new Map(executed.map((r) => [r.name, r])));
608
+ }
415
609
  return this.migrations.map((MigrationClass) => {
416
610
  const instance = new MigrationClass();
417
611
  const name = MigrationClass.migrationName;
418
- const record = executedMap.get(name);
612
+ const record = executedBySource.get(this.sourceOf(MigrationClass))?.get(name);
419
613
  return {
420
614
  name,
421
615
  table: instance.table,
@@ -435,9 +629,9 @@ var MigrationRunner = class {
435
629
  * @example
436
630
  * await this.informIfExtensionMissing("vector");
437
631
  */
438
- async informIfExtensionMissing(extension) {
632
+ async informIfExtensionMissing(extension, dataSource = this.getDataSource()) {
439
633
  try {
440
- const migrationDriver = this.getMigrationDriver();
634
+ const migrationDriver = this.getMigrationDriver(dataSource);
441
635
  if (!await migrationDriver.isExtensionAvailable(extension)) {
442
636
  const hr = "─".repeat(60);
443
637
  console.log(`\n${colors.yellow(hr)}`);
@@ -473,35 +667,38 @@ var MigrationRunner = class {
473
667
  log.info("database", "migration", `${direction === "up" ? "Migrating" : "Rolling back"}: ${colors.magenta(name)}...`);
474
668
  try {
475
669
  if (!dryRun) {
476
- const driver = this.getMigrationDriver();
670
+ const dataSource = this.resolveDataSource(migration.dataSource);
671
+ const driver = this.getMigrationDriver(dataSource);
477
672
  migration.setDriver(driver);
478
- migration.setMigrationDefaults(this.getDataSource().migrationDefaults);
479
- const shouldUseTransaction = migration.transactional ?? this.getDataSource().migrations?.transactional ?? driver.getDefaultTransactional();
673
+ migration.setMigrationDefaults(dataSource.migrationDefaults);
480
674
  if (direction === "up") await migration.up();
481
675
  else await migration.down();
482
- const databaseDriver = this.getDataSource().driver;
676
+ const databaseDriver = dataSource.driver;
483
677
  const directExecution = databaseDriver.supportsSqlSerialization === false;
484
- const sqlStatements = directExecution ? [] : migration.toSQL();
678
+ const steps = directExecution ? [] : migration.toSteps?.() ?? migration.toSQL();
679
+ const sqlStatements = steps.filter((step) => typeof step === "string");
680
+ const shouldUseTransaction = this.resolveTransactional(MigrationClass, migration, dataSource, sqlStatements);
485
681
  const applyMigration = async () => {
486
682
  if (directExecution) {
487
683
  await migration.execute();
488
684
  return;
489
685
  }
490
- for (const sql of sqlStatements) await databaseDriver.query(sql);
686
+ for (const step of steps) if (typeof step === "string") await databaseDriver.query(step);
687
+ else await step.data();
491
688
  };
492
689
  if (shouldUseTransaction && databaseDriver.transaction) await databaseDriver.transaction(async () => {
493
690
  await applyMigration();
494
691
  if (record) if (direction === "up") {
495
- const batch = options.batch ?? await this.getNextBatchNumber();
496
- await this.recordMigration(name, batch, MigrationClass.createdAt ? parseCreatedAt(MigrationClass.createdAt) : /* @__PURE__ */ new Date());
497
- } else await this.removeMigrationRecord(name);
692
+ const batch = options.batch ?? await this.getNextBatchNumber(dataSource);
693
+ await this.recordMigration(name, batch, MigrationClass.createdAt ? parseCreatedAt(MigrationClass.createdAt) : /* @__PURE__ */ new Date(), dataSource);
694
+ } else await this.removeMigrationRecord(name, dataSource);
498
695
  });
499
696
  else {
500
697
  await applyMigration();
501
698
  if (record) if (direction === "up") {
502
- const batch = options.batch ?? await this.getNextBatchNumber();
503
- await this.recordMigration(name, batch, MigrationClass.createdAt ? parseCreatedAt(MigrationClass.createdAt) : /* @__PURE__ */ new Date());
504
- } else await this.removeMigrationRecord(name);
699
+ const batch = options.batch ?? await this.getNextBatchNumber(dataSource);
700
+ await this.recordMigration(name, batch, MigrationClass.createdAt ? parseCreatedAt(MigrationClass.createdAt) : /* @__PURE__ */ new Date(), dataSource);
701
+ } else await this.removeMigrationRecord(name, dataSource);
505
702
  }
506
703
  }
507
704
  } catch (err) {
@@ -532,8 +729,9 @@ var MigrationRunner = class {
532
729
  */
533
730
  createMigrationInstance(MigrationClass) {
534
731
  const migration = new MigrationClass();
535
- migration.setDriver(this.getMigrationDriver());
536
- migration.setMigrationDefaults(this.getDataSource().migrationDefaults);
732
+ const dataSource = this.resolveDataSource(migration.dataSource);
733
+ migration.setDriver(this.getMigrationDriver(dataSource));
734
+ migration.setMigrationDefaults(dataSource.migrationDefaults);
537
735
  return migration;
538
736
  }
539
737
  /**
@@ -585,10 +783,13 @@ var MigrationRunner = class {
585
783
  *
586
784
  * @see listPendingMigrations
587
785
  */
588
- async getPendingMigrations() {
589
- const executed = await this.getExecutedMigrations();
590
- const executedNames = new Set(executed.map((r) => r.name));
591
- return this.migrations.filter((m) => !executedNames.has(m.migrationName)).sort(sortMigrations);
786
+ async getPendingMigrations(options = {}) {
787
+ const executedNames = /* @__PURE__ */ new Map();
788
+ for (const dataSource of this.getSources()) {
789
+ const executed = await this.getExecutedMigrations(dataSource, options);
790
+ executedNames.set(dataSource, new Set(executed.map((r) => r.name)));
791
+ }
792
+ return this.migrations.filter((m) => !executedNames.get(this.sourceOf(m))?.has(m.migrationName)).sort(sortMigrations);
592
793
  }
593
794
  /**
594
795
  * Get migrations to rollback, newest-first.
@@ -604,30 +805,52 @@ var MigrationRunner = class {
604
805
  * what this method used to do after a `.reverse()` — silently restored the
605
806
  * forward `up` order and made the reverse dead code.)
606
807
  */
607
- async getMigrationsToRollback(batches) {
608
- const executed = await this.getExecutedMigrations();
609
- if (executed.length === 0) return [];
610
- const batchNumbers = [...new Set(executed.map((r) => r.batch))].sort((a, b) => b - a).slice(0, batches);
611
- return executed.filter((r) => batchNumbers.includes(r.batch)).map((r) => this.migrations.find((m) => m.migrationName === r.name)).filter((m) => !!m).sort(sortMigrationsForRollback);
808
+ async getMigrationsToRollback(batches, options = {}) {
809
+ const migrations = [];
810
+ const batchOf = /* @__PURE__ */ new Map();
811
+ const orphans = [];
812
+ for (const dataSource of this.getSources()) {
813
+ const executed = await this.getExecutedMigrations(dataSource, { readOnly: options.readOnly });
814
+ if (executed.length === 0) continue;
815
+ const batchNumbers = [...new Set(executed.map((r) => r.batch))].sort((a, b) => b - a).slice(0, batches);
816
+ for (const record of executed) {
817
+ if (!batchNumbers.includes(record.batch)) continue;
818
+ const MigrationClass = this.migrations.find((m) => m.migrationName === record.name && this.sourceOf(m) === dataSource);
819
+ if (MigrationClass) {
820
+ migrations.push(MigrationClass);
821
+ batchOf.set(MigrationClass, record.batch);
822
+ } else orphans.push({
823
+ name: record.name,
824
+ dataSource
825
+ });
826
+ }
827
+ }
828
+ if (orphans.length > 0) {
829
+ if (!options.pruneMissing) throw new Error(`Cannot roll back: ${orphans.length} executed migration(s) have no registered file: ${orphans.map((o) => o.name).join(", ")}. They were renamed, deleted, or not matched by the migration pattern. Restore the file(s), or pass pruneMissing (--prune-missing) to delete those records.`);
830
+ if (!options.readOnly) for (const orphan of orphans) {
831
+ await this.removeMigrationRecord(orphan.name, orphan.dataSource);
832
+ log.warn("database", "migration", `Pruned orphaned migration record: ${orphan.name}`);
833
+ }
834
+ }
835
+ return migrations.sort((a, b) => (batchOf.get(b) ?? 0) - (batchOf.get(a) ?? 0) || sortMigrationsForRollback(a, b));
612
836
  }
613
837
  /**
614
838
  * Get executed migration records.
615
839
  */
616
- async getExecutedMigrations() {
617
- const driver = this.getDataSource().driver;
618
- try {
619
- await this.getMigrationDriver().ensureMigrationsTable(this.migrationsTable);
620
- return await driver.queryBuilder(this.migrationsTable).orderBy("batch", "asc").orderBy("name", "asc").get();
621
- } catch {
622
- return [];
623
- }
840
+ async getExecutedMigrations(dataSource = this.getDataSource(), options = {}) {
841
+ const driver = dataSource.driver;
842
+ const migrationDriver = this.getMigrationDriver(dataSource);
843
+ if (options.readOnly) {
844
+ if (!await migrationDriver.tableExists(this.migrationsTable)) return [];
845
+ } else await migrationDriver.ensureMigrationsTable(this.migrationsTable);
846
+ return (await driver.queryBuilder(this.migrationsTable).orderBy("batch", "asc").orderBy("name", "asc").get()).filter((record) => record.name !== MIGRATION_LOCK_NAME);
624
847
  }
625
848
  /**
626
849
  * Record a migration.
627
850
  */
628
- async recordMigration(name, batch, createdAt) {
629
- const driver = this.getDataSource().driver;
630
- await this.getMigrationDriver().ensureMigrationsTable(this.migrationsTable);
851
+ async recordMigration(name, batch, createdAt, dataSource = this.getDataSource()) {
852
+ const driver = dataSource.driver;
853
+ await this.getMigrationDriver(dataSource).ensureMigrationsTable(this.migrationsTable);
631
854
  await driver.insert(this.migrationsTable, {
632
855
  name,
633
856
  batch,
@@ -638,14 +861,14 @@ var MigrationRunner = class {
638
861
  /**
639
862
  * Remove a migration record.
640
863
  */
641
- async removeMigrationRecord(name) {
642
- await this.getDataSource().driver.delete(this.migrationsTable, { name });
864
+ async removeMigrationRecord(name, dataSource = this.getDataSource()) {
865
+ await dataSource.driver.delete(this.migrationsTable, { name });
643
866
  }
644
867
  /**
645
868
  * Get next batch number.
646
869
  */
647
- async getNextBatchNumber() {
648
- const executed = await this.getExecutedMigrations();
870
+ async getNextBatchNumber(dataSource = this.getDataSource(), readOnly = false) {
871
+ const executed = await this.getExecutedMigrations(dataSource, { readOnly });
649
872
  if (executed.length === 0) return 1;
650
873
  return Math.max(...executed.map((r) => r.batch)) + 1;
651
874
  }
@@ -653,5 +876,5 @@ var MigrationRunner = class {
653
876
  const migrationRunner = new MigrationRunner();
654
877
 
655
878
  //#endregion
656
- export { MigrationRunner, migrationRunner };
879
+ export { MigrationRunner, inferMigrationName, migrationRunner };
657
880
  //# sourceMappingURL=migration-runner.mjs.map