@warlock.js/cascade 5.20.0 → 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.
- package/CHANGELOG.md +30 -1
- package/cjs/index.cjs +2283 -1037
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/query-builder.contract.d.mts +30 -3
- package/esm/contracts/query-builder.contract.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.d.mts +33 -1
- package/esm/data-source/data-source-registry.d.mts.map +1 -1
- package/esm/data-source/data-source-registry.mjs +75 -5
- package/esm/data-source/data-source-registry.mjs.map +1 -1
- package/esm/database-dirty-tracker.d.mts +5 -0
- package/esm/database-dirty-tracker.d.mts.map +1 -1
- package/esm/database-dirty-tracker.mjs +16 -0
- package/esm/database-dirty-tracker.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.d.mts +12 -0
- package/esm/drivers/mongodb/mongodb-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-driver.mjs +47 -16
- package/esm/drivers/mongodb/mongodb-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs +15 -18
- package/esm/drivers/mongodb/mongodb-migration-driver.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts +13 -3
- package/esm/drivers/mongodb/mongodb-query-builder.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-builder.mjs +72 -19
- package/esm/drivers/mongodb/mongodb-query-builder.mjs.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts +6 -0
- package/esm/drivers/mongodb/mongodb-query-parser.d.mts.map +1 -1
- package/esm/drivers/mongodb/mongodb-query-parser.mjs +128 -30
- package/esm/drivers/mongodb/mongodb-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/index.d.mts +2 -3
- package/esm/drivers/postgres/index.mjs +1 -2
- package/esm/drivers/postgres/postgres-blueprint.mjs +4 -4
- package/esm/drivers/postgres/postgres-blueprint.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts +5 -1
- package/esm/drivers/postgres/postgres-dialect.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-dialect.mjs +38 -8
- package/esm/drivers/postgres/postgres-dialect.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-driver.d.mts +52 -29
- package/esm/drivers/postgres/postgres-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-driver.mjs +190 -57
- package/esm/drivers/postgres/postgres-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts +2 -1
- package/esm/drivers/postgres/postgres-migration-driver.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-migration-driver.mjs +8 -6
- package/esm/drivers/postgres/postgres-migration-driver.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.d.mts +18 -7
- package/esm/drivers/postgres/postgres-query-builder.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-builder.mjs +146 -31
- package/esm/drivers/postgres/postgres-query-builder.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts +23 -1
- package/esm/drivers/postgres/postgres-query-parser.d.mts.map +1 -1
- package/esm/drivers/postgres/postgres-query-parser.mjs +160 -43
- package/esm/drivers/postgres/postgres-query-parser.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-sql-serializer.mjs +3 -2
- package/esm/drivers/postgres/postgres-sql-serializer.mjs.map +1 -1
- package/esm/drivers/postgres/postgres-type-converters.mjs +57 -0
- package/esm/drivers/postgres/postgres-type-converters.mjs.map +1 -0
- package/esm/events/model-events.d.mts +1 -1
- package/esm/events/model-events.d.mts.map +1 -1
- package/esm/events/model-events.mjs.map +1 -1
- package/esm/index.d.mts +5 -6
- package/esm/index.mjs +4 -5
- package/esm/migration/column-builder.d.mts.map +1 -1
- package/esm/migration/column-builder.mjs +2 -2
- package/esm/migration/column-builder.mjs.map +1 -1
- package/esm/migration/index.d.mts +2 -2
- package/esm/migration/index.mjs +1 -1
- package/esm/migration/migration-order.mjs +5 -2
- package/esm/migration/migration-order.mjs.map +1 -1
- package/esm/migration/migration-runner.d.mts +50 -5
- package/esm/migration/migration-runner.d.mts.map +1 -1
- package/esm/migration/migration-runner.mjs +369 -146
- package/esm/migration/migration-runner.mjs.map +1 -1
- package/esm/migration/migration.d.mts +36 -3
- package/esm/migration/migration.d.mts.map +1 -1
- package/esm/migration/migration.mjs +62 -5
- package/esm/migration/migration.mjs.map +1 -1
- package/esm/model/methods/accessor-methods.mjs +1 -3
- package/esm/model/methods/accessor-methods.mjs.map +1 -1
- package/esm/model/methods/delete-methods.mjs +6 -2
- package/esm/model/methods/delete-methods.mjs.map +1 -1
- package/esm/model/methods/hydration-methods.mjs +16 -2
- package/esm/model/methods/hydration-methods.mjs.map +1 -1
- package/esm/model/methods/instance-event-methods.mjs +2 -1
- package/esm/model/methods/instance-event-methods.mjs.map +1 -1
- package/esm/model/methods/meta-methods.mjs +53 -16
- package/esm/model/methods/meta-methods.mjs.map +1 -1
- package/esm/model/methods/query-methods.mjs +86 -15
- package/esm/model/methods/query-methods.mjs.map +1 -1
- package/esm/model/methods/static-event-methods.mjs +16 -1
- package/esm/model/methods/static-event-methods.mjs.map +1 -1
- package/esm/model/methods/write-methods.d.mts.map +1 -1
- package/esm/model/methods/write-methods.mjs +50 -50
- package/esm/model/methods/write-methods.mjs.map +1 -1
- package/esm/model/model.d.mts +16 -1
- package/esm/model/model.d.mts.map +1 -1
- package/esm/model/model.mjs +39 -5
- package/esm/model/model.mjs.map +1 -1
- package/esm/model/register-model.d.mts +6 -1
- package/esm/model/register-model.d.mts.map +1 -1
- package/esm/model/register-model.mjs +16 -1
- package/esm/model/register-model.mjs.map +1 -1
- package/esm/query-builder/query-builder.d.mts +21 -2
- package/esm/query-builder/query-builder.d.mts.map +1 -1
- package/esm/query-builder/query-builder.mjs +97 -20
- package/esm/query-builder/query-builder.mjs.map +1 -1
- package/esm/relations/key-conventions.mjs +13 -3
- package/esm/relations/key-conventions.mjs.map +1 -1
- package/esm/relations/pivot-operations.mjs +1 -1
- package/esm/relations/relation-loader.d.mts +15 -5
- package/esm/relations/relation-loader.d.mts.map +1 -1
- package/esm/relations/relation-loader.mjs +51 -16
- package/esm/relations/relation-loader.mjs.map +1 -1
- package/esm/remover/database-remover.d.mts.map +1 -1
- package/esm/remover/database-remover.mjs +21 -7
- package/esm/remover/database-remover.mjs.map +1 -1
- package/esm/restorer/database-restorer.d.mts +12 -2
- package/esm/restorer/database-restorer.d.mts.map +1 -1
- package/esm/restorer/database-restorer.mjs +43 -13
- package/esm/restorer/database-restorer.mjs.map +1 -1
- package/esm/sync/model-events.mjs +63 -3
- package/esm/sync/model-events.mjs.map +1 -1
- package/esm/sync/model-sync-operation.d.mts.map +1 -1
- package/esm/sync/model-sync-operation.mjs +3 -2
- package/esm/sync/model-sync-operation.mjs.map +1 -1
- package/esm/sync/sync-manager.d.mts +3 -42
- package/esm/sync/sync-manager.d.mts.map +1 -1
- package/esm/sync/sync-manager.mjs +37 -126
- package/esm/sync/sync-manager.mjs.map +1 -1
- package/esm/utils/connect-to-database.d.mts.map +1 -1
- package/esm/utils/connect-to-database.mjs +4 -4
- package/esm/utils/connect-to-database.mjs.map +1 -1
- package/esm/utils/database-writer.utils.d.mts +1 -1
- package/esm/utils/database-writer.utils.d.mts.map +1 -1
- package/esm/utils/database-writer.utils.mjs +1 -0
- package/esm/utils/database-writer.utils.mjs.map +1 -1
- package/esm/utils/define-model.d.mts.map +1 -1
- package/esm/utils/define-model.mjs +2 -10
- package/esm/utils/define-model.mjs.map +1 -1
- package/esm/utils/is-valid-date-value.mjs +3 -2
- package/esm/utils/is-valid-date-value.mjs.map +1 -1
- package/esm/utils/once-connected.d.mts.map +1 -1
- package/esm/utils/once-connected.mjs +12 -2
- package/esm/utils/once-connected.mjs.map +1 -1
- package/esm/validation/mutators/embed-mutator.mjs +19 -11
- package/esm/validation/mutators/embed-mutator.mjs.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.d.mts +2 -2
- package/esm/validation/plugins/embed-validator-plugin.d.mts.map +1 -1
- package/esm/validation/plugins/embed-validator-plugin.mjs +8 -2
- package/esm/validation/plugins/embed-validator-plugin.mjs.map +1 -1
- package/esm/validation/rules/database-model-rule.mjs +6 -5
- package/esm/validation/rules/database-model-rule.mjs.map +1 -1
- package/esm/validation/rules/exists-rule.mjs +6 -4
- package/esm/validation/rules/exists-rule.mjs.map +1 -1
- package/esm/validation/rules/unique-rule.mjs +5 -2
- package/esm/validation/rules/unique-rule.mjs.map +1 -1
- package/esm/validation/validators/embed-validator.d.mts.map +1 -1
- package/esm/validation/validators/embed-validator.mjs +1 -1
- package/esm/validation/validators/embed-validator.mjs.map +1 -1
- package/esm/writer/database-writer.d.mts +27 -0
- package/esm/writer/database-writer.d.mts.map +1 -1
- package/esm/writer/database-writer.mjs +84 -17
- package/esm/writer/database-writer.mjs.map +1 -1
- package/package.json +4 -4
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts +0 -79
- package/esm/drivers/postgres/postgres-sync-adapter.d.mts.map +0 -1
- package/esm/drivers/postgres/postgres-sync-adapter.mjs +0 -162
- package/esm/drivers/postgres/postgres-sync-adapter.mjs.map +0 -1
|
@@ -8,6 +8,21 @@ 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
|
|
51
|
-
|
|
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.
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
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
|
|
240
|
-
console.log(`-- [${
|
|
241
|
-
console.log(
|
|
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
|
|
246
|
-
|
|
247
|
-
|
|
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
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
414
|
-
const
|
|
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 =
|
|
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
|
|
670
|
+
const dataSource = this.resolveDataSource(migration.dataSource);
|
|
671
|
+
const driver = this.getMigrationDriver(dataSource);
|
|
477
672
|
migration.setDriver(driver);
|
|
478
|
-
migration.setMigrationDefaults(
|
|
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 =
|
|
676
|
+
const databaseDriver = dataSource.driver;
|
|
483
677
|
const directExecution = databaseDriver.supportsSqlSerialization === false;
|
|
484
|
-
const
|
|
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
|
|
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
|
-
|
|
536
|
-
migration.
|
|
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
|
|
590
|
-
const
|
|
591
|
-
|
|
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
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
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 =
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
|
|
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 =
|
|
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
|
|
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
|