drizzle-multitenant 1.1.0 → 1.3.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/README.md +28 -8
- package/dist/cli/index.js +2001 -2442
- package/dist/{context-Vki959ri.d.ts → context-BBLPNjmk.d.ts} +1 -1
- package/dist/cross-schema/index.js +1 -426
- package/dist/export/index.d.ts +395 -0
- package/dist/export/index.js +9 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +149 -2437
- package/dist/integrations/express.d.ts +3 -3
- package/dist/integrations/express.js +1 -110
- package/dist/integrations/fastify.d.ts +3 -3
- package/dist/integrations/fastify.js +1 -236
- package/dist/integrations/hono.js +0 -3
- package/dist/integrations/nestjs/index.d.ts +1 -1
- package/dist/integrations/nestjs/index.js +3 -10759
- package/dist/lint/index.d.ts +475 -0
- package/dist/lint/index.js +5 -0
- package/dist/metrics/index.d.ts +530 -0
- package/dist/metrics/index.js +3 -0
- package/dist/migrator/index.d.ts +1087 -270
- package/dist/migrator/index.js +149 -970
- package/dist/migrator-B7oPKe73.d.ts +1067 -0
- package/dist/scaffold/index.d.ts +330 -0
- package/dist/scaffold/index.js +277 -0
- package/dist/{types-BhK96FPC.d.ts → types-CGqsPe2Q.d.ts} +49 -1
- package/package.json +18 -1
- package/dist/cli/index.js.map +0 -1
- package/dist/cross-schema/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/express.js.map +0 -1
- package/dist/integrations/fastify.js.map +0 -1
- package/dist/integrations/hono.js.map +0 -1
- package/dist/integrations/nestjs/index.js.map +0 -1
- package/dist/migrator/index.js.map +0 -1
package/dist/migrator/index.js
CHANGED
|
@@ -1,975 +1,154 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, basename } from 'path';
|
|
3
|
-
import { createHash } from 'crypto';
|
|
4
|
-
import { Pool } from 'pg';
|
|
5
|
-
|
|
6
|
-
// src/migrator/migrator.ts
|
|
7
|
-
|
|
8
|
-
// src/migrator/table-format.ts
|
|
9
|
-
var DEFAULT_FORMAT = {
|
|
10
|
-
format: "name",
|
|
11
|
-
tableName: "__drizzle_migrations",
|
|
12
|
-
columns: {
|
|
13
|
-
identifier: "name",
|
|
14
|
-
timestamp: "applied_at",
|
|
15
|
-
timestampType: "timestamp"
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
var DRIZZLE_KIT_FORMAT = {
|
|
19
|
-
format: "drizzle-kit",
|
|
20
|
-
tableName: "__drizzle_migrations",
|
|
21
|
-
columns: {
|
|
22
|
-
identifier: "hash",
|
|
23
|
-
timestamp: "created_at",
|
|
24
|
-
timestampType: "bigint"
|
|
25
|
-
}
|
|
26
|
-
};
|
|
27
|
-
async function detectTableFormat(pool, schemaName, tableName) {
|
|
28
|
-
const tableExists = await pool.query(
|
|
29
|
-
`SELECT EXISTS (
|
|
1
|
+
import {readdir,readFile}from'fs/promises';import {join,basename}from'path';import {createHash}from'crypto';import {existsSync}from'fs';import {Pool}from'pg';import {drizzle}from'drizzle-orm/node-postgres';var ne={format:"name",tableName:"__drizzle_migrations",columns:{identifier:"name",timestamp:"applied_at",timestampType:"timestamp"}},ae={format:"drizzle-kit",tableName:"__drizzle_migrations",columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}};async function N(m,e,n){if(!(await m.query(`SELECT EXISTS (
|
|
30
2
|
SELECT 1 FROM information_schema.tables
|
|
31
3
|
WHERE table_schema = $1 AND table_name = $2
|
|
32
|
-
) as exists`,
|
|
33
|
-
[schemaName, tableName]
|
|
34
|
-
);
|
|
35
|
-
if (!tableExists.rows[0]?.exists) {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
const columnsResult = await pool.query(
|
|
39
|
-
`SELECT column_name, data_type
|
|
4
|
+
) as exists`,[e,n])).rows[0]?.exists)return null;let r=await m.query(`SELECT column_name, data_type
|
|
40
5
|
FROM information_schema.columns
|
|
41
|
-
WHERE table_schema = $1 AND table_name = $2`,
|
|
42
|
-
|
|
43
|
-
);
|
|
44
|
-
const columnMap = new Map(
|
|
45
|
-
columnsResult.rows.map((r) => [r.column_name, r.data_type])
|
|
46
|
-
);
|
|
47
|
-
if (columnMap.has("name")) {
|
|
48
|
-
return {
|
|
49
|
-
format: "name",
|
|
50
|
-
tableName,
|
|
51
|
-
columns: {
|
|
52
|
-
identifier: "name",
|
|
53
|
-
timestamp: columnMap.has("applied_at") ? "applied_at" : "created_at",
|
|
54
|
-
timestampType: "timestamp"
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
if (columnMap.has("hash")) {
|
|
59
|
-
const createdAtType = columnMap.get("created_at");
|
|
60
|
-
if (createdAtType === "bigint") {
|
|
61
|
-
return {
|
|
62
|
-
format: "drizzle-kit",
|
|
63
|
-
tableName,
|
|
64
|
-
columns: {
|
|
65
|
-
identifier: "hash",
|
|
66
|
-
timestamp: "created_at",
|
|
67
|
-
timestampType: "bigint"
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
return {
|
|
72
|
-
format: "hash",
|
|
73
|
-
tableName,
|
|
74
|
-
columns: {
|
|
75
|
-
identifier: "hash",
|
|
76
|
-
timestamp: "created_at",
|
|
77
|
-
timestampType: "timestamp"
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
return null;
|
|
82
|
-
}
|
|
83
|
-
function getFormatConfig(format, tableName = "__drizzle_migrations") {
|
|
84
|
-
switch (format) {
|
|
85
|
-
case "name":
|
|
86
|
-
return {
|
|
87
|
-
format: "name",
|
|
88
|
-
tableName,
|
|
89
|
-
columns: {
|
|
90
|
-
identifier: "name",
|
|
91
|
-
timestamp: "applied_at",
|
|
92
|
-
timestampType: "timestamp"
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
case "hash":
|
|
96
|
-
return {
|
|
97
|
-
format: "hash",
|
|
98
|
-
tableName,
|
|
99
|
-
columns: {
|
|
100
|
-
identifier: "hash",
|
|
101
|
-
timestamp: "created_at",
|
|
102
|
-
timestampType: "timestamp"
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
case "drizzle-kit":
|
|
106
|
-
return {
|
|
107
|
-
format: "drizzle-kit",
|
|
108
|
-
tableName,
|
|
109
|
-
columns: {
|
|
110
|
-
identifier: "hash",
|
|
111
|
-
timestamp: "created_at",
|
|
112
|
-
timestampType: "bigint"
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// src/migrator/migrator.ts
|
|
119
|
-
var DEFAULT_MIGRATIONS_TABLE = "__drizzle_migrations";
|
|
120
|
-
var Migrator = class {
|
|
121
|
-
constructor(tenantConfig, migratorConfig) {
|
|
122
|
-
this.tenantConfig = tenantConfig;
|
|
123
|
-
this.migratorConfig = migratorConfig;
|
|
124
|
-
this.migrationsTable = migratorConfig.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE;
|
|
125
|
-
}
|
|
126
|
-
migrationsTable;
|
|
127
|
-
/**
|
|
128
|
-
* Migrate all tenants in parallel
|
|
129
|
-
*/
|
|
130
|
-
async migrateAll(options = {}) {
|
|
131
|
-
const {
|
|
132
|
-
concurrency = 10,
|
|
133
|
-
onProgress,
|
|
134
|
-
onError,
|
|
135
|
-
dryRun = false
|
|
136
|
-
} = options;
|
|
137
|
-
const tenantIds = await this.migratorConfig.tenantDiscovery();
|
|
138
|
-
const migrations = await this.loadMigrations();
|
|
139
|
-
const results = [];
|
|
140
|
-
let aborted = false;
|
|
141
|
-
for (let i = 0; i < tenantIds.length && !aborted; i += concurrency) {
|
|
142
|
-
const batch = tenantIds.slice(i, i + concurrency);
|
|
143
|
-
const batchResults = await Promise.all(
|
|
144
|
-
batch.map(async (tenantId) => {
|
|
145
|
-
if (aborted) {
|
|
146
|
-
return this.createSkippedResult(tenantId);
|
|
147
|
-
}
|
|
148
|
-
try {
|
|
149
|
-
onProgress?.(tenantId, "starting");
|
|
150
|
-
const result = await this.migrateTenant(tenantId, migrations, { dryRun, onProgress });
|
|
151
|
-
onProgress?.(tenantId, result.success ? "completed" : "failed");
|
|
152
|
-
return result;
|
|
153
|
-
} catch (error) {
|
|
154
|
-
onProgress?.(tenantId, "failed");
|
|
155
|
-
const action = onError?.(tenantId, error);
|
|
156
|
-
if (action === "abort") {
|
|
157
|
-
aborted = true;
|
|
158
|
-
}
|
|
159
|
-
return this.createErrorResult(tenantId, error);
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
);
|
|
163
|
-
results.push(...batchResults);
|
|
164
|
-
}
|
|
165
|
-
if (aborted) {
|
|
166
|
-
const remaining = tenantIds.slice(results.length);
|
|
167
|
-
for (const tenantId of remaining) {
|
|
168
|
-
results.push(this.createSkippedResult(tenantId));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return this.aggregateResults(results);
|
|
172
|
-
}
|
|
173
|
-
/**
|
|
174
|
-
* Migrate a single tenant
|
|
175
|
-
*/
|
|
176
|
-
async migrateTenant(tenantId, migrations, options = {}) {
|
|
177
|
-
const startTime = Date.now();
|
|
178
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
179
|
-
const appliedMigrations = [];
|
|
180
|
-
const pool = await this.createPool(schemaName);
|
|
181
|
-
try {
|
|
182
|
-
await this.migratorConfig.hooks?.beforeTenant?.(tenantId);
|
|
183
|
-
const format = await this.getOrDetectFormat(pool, schemaName);
|
|
184
|
-
await this.ensureMigrationsTable(pool, schemaName, format);
|
|
185
|
-
const allMigrations = migrations ?? await this.loadMigrations();
|
|
186
|
-
const applied = await this.getAppliedMigrations(pool, schemaName, format);
|
|
187
|
-
const appliedSet = new Set(applied.map((m) => m.identifier));
|
|
188
|
-
const pending = allMigrations.filter(
|
|
189
|
-
(m) => !this.isMigrationApplied(m, appliedSet, format)
|
|
190
|
-
);
|
|
191
|
-
if (options.dryRun) {
|
|
192
|
-
return {
|
|
193
|
-
tenantId,
|
|
194
|
-
schemaName,
|
|
195
|
-
success: true,
|
|
196
|
-
appliedMigrations: pending.map((m) => m.name),
|
|
197
|
-
durationMs: Date.now() - startTime,
|
|
198
|
-
format: format.format
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
for (const migration of pending) {
|
|
202
|
-
const migrationStart = Date.now();
|
|
203
|
-
options.onProgress?.(tenantId, "migrating", migration.name);
|
|
204
|
-
await this.migratorConfig.hooks?.beforeMigration?.(tenantId, migration.name);
|
|
205
|
-
await this.applyMigration(pool, schemaName, migration, format);
|
|
206
|
-
await this.migratorConfig.hooks?.afterMigration?.(
|
|
207
|
-
tenantId,
|
|
208
|
-
migration.name,
|
|
209
|
-
Date.now() - migrationStart
|
|
210
|
-
);
|
|
211
|
-
appliedMigrations.push(migration.name);
|
|
212
|
-
}
|
|
213
|
-
const result = {
|
|
214
|
-
tenantId,
|
|
215
|
-
schemaName,
|
|
216
|
-
success: true,
|
|
217
|
-
appliedMigrations,
|
|
218
|
-
durationMs: Date.now() - startTime,
|
|
219
|
-
format: format.format
|
|
220
|
-
};
|
|
221
|
-
await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);
|
|
222
|
-
return result;
|
|
223
|
-
} catch (error) {
|
|
224
|
-
const result = {
|
|
225
|
-
tenantId,
|
|
226
|
-
schemaName,
|
|
227
|
-
success: false,
|
|
228
|
-
appliedMigrations,
|
|
229
|
-
error: error.message,
|
|
230
|
-
durationMs: Date.now() - startTime
|
|
231
|
-
};
|
|
232
|
-
await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);
|
|
233
|
-
return result;
|
|
234
|
-
} finally {
|
|
235
|
-
await pool.end();
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
/**
|
|
239
|
-
* Migrate specific tenants
|
|
240
|
-
*/
|
|
241
|
-
async migrateTenants(tenantIds, options = {}) {
|
|
242
|
-
const migrations = await this.loadMigrations();
|
|
243
|
-
const results = [];
|
|
244
|
-
const { concurrency = 10, onProgress, onError } = options;
|
|
245
|
-
for (let i = 0; i < tenantIds.length; i += concurrency) {
|
|
246
|
-
const batch = tenantIds.slice(i, i + concurrency);
|
|
247
|
-
const batchResults = await Promise.all(
|
|
248
|
-
batch.map(async (tenantId) => {
|
|
249
|
-
try {
|
|
250
|
-
onProgress?.(tenantId, "starting");
|
|
251
|
-
const result = await this.migrateTenant(tenantId, migrations, { dryRun: options.dryRun ?? false, onProgress });
|
|
252
|
-
onProgress?.(tenantId, result.success ? "completed" : "failed");
|
|
253
|
-
return result;
|
|
254
|
-
} catch (error) {
|
|
255
|
-
onProgress?.(tenantId, "failed");
|
|
256
|
-
onError?.(tenantId, error);
|
|
257
|
-
return this.createErrorResult(tenantId, error);
|
|
258
|
-
}
|
|
259
|
-
})
|
|
260
|
-
);
|
|
261
|
-
results.push(...batchResults);
|
|
262
|
-
}
|
|
263
|
-
return this.aggregateResults(results);
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Get migration status for all tenants
|
|
267
|
-
*/
|
|
268
|
-
async getStatus() {
|
|
269
|
-
const tenantIds = await this.migratorConfig.tenantDiscovery();
|
|
270
|
-
const migrations = await this.loadMigrations();
|
|
271
|
-
const statuses = [];
|
|
272
|
-
for (const tenantId of tenantIds) {
|
|
273
|
-
statuses.push(await this.getTenantStatus(tenantId, migrations));
|
|
274
|
-
}
|
|
275
|
-
return statuses;
|
|
276
|
-
}
|
|
277
|
-
/**
|
|
278
|
-
* Get migration status for a specific tenant
|
|
279
|
-
*/
|
|
280
|
-
async getTenantStatus(tenantId, migrations) {
|
|
281
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
282
|
-
const pool = await this.createPool(schemaName);
|
|
283
|
-
try {
|
|
284
|
-
const allMigrations = migrations ?? await this.loadMigrations();
|
|
285
|
-
const tableExists = await this.migrationsTableExists(pool, schemaName);
|
|
286
|
-
if (!tableExists) {
|
|
287
|
-
return {
|
|
288
|
-
tenantId,
|
|
289
|
-
schemaName,
|
|
290
|
-
appliedCount: 0,
|
|
291
|
-
pendingCount: allMigrations.length,
|
|
292
|
-
pendingMigrations: allMigrations.map((m) => m.name),
|
|
293
|
-
status: allMigrations.length > 0 ? "behind" : "ok",
|
|
294
|
-
format: null
|
|
295
|
-
// New tenant, no table yet
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
const format = await this.getOrDetectFormat(pool, schemaName);
|
|
299
|
-
const applied = await this.getAppliedMigrations(pool, schemaName, format);
|
|
300
|
-
const appliedSet = new Set(applied.map((m) => m.identifier));
|
|
301
|
-
const pending = allMigrations.filter(
|
|
302
|
-
(m) => !this.isMigrationApplied(m, appliedSet, format)
|
|
303
|
-
);
|
|
304
|
-
return {
|
|
305
|
-
tenantId,
|
|
306
|
-
schemaName,
|
|
307
|
-
appliedCount: applied.length,
|
|
308
|
-
pendingCount: pending.length,
|
|
309
|
-
pendingMigrations: pending.map((m) => m.name),
|
|
310
|
-
status: pending.length > 0 ? "behind" : "ok",
|
|
311
|
-
format: format.format
|
|
312
|
-
};
|
|
313
|
-
} catch (error) {
|
|
314
|
-
return {
|
|
315
|
-
tenantId,
|
|
316
|
-
schemaName,
|
|
317
|
-
appliedCount: 0,
|
|
318
|
-
pendingCount: 0,
|
|
319
|
-
pendingMigrations: [],
|
|
320
|
-
status: "error",
|
|
321
|
-
error: error.message,
|
|
322
|
-
format: null
|
|
323
|
-
};
|
|
324
|
-
} finally {
|
|
325
|
-
await pool.end();
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Create a new tenant schema and optionally apply migrations
|
|
330
|
-
*/
|
|
331
|
-
async createTenant(tenantId, options = {}) {
|
|
332
|
-
const { migrate = true } = options;
|
|
333
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
334
|
-
const pool = new Pool({
|
|
335
|
-
connectionString: this.tenantConfig.connection.url,
|
|
336
|
-
...this.tenantConfig.connection.poolConfig
|
|
337
|
-
});
|
|
338
|
-
try {
|
|
339
|
-
await pool.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
|
|
340
|
-
if (migrate) {
|
|
341
|
-
await this.migrateTenant(tenantId);
|
|
342
|
-
}
|
|
343
|
-
} finally {
|
|
344
|
-
await pool.end();
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
/**
|
|
348
|
-
* Drop a tenant schema
|
|
349
|
-
*/
|
|
350
|
-
async dropTenant(tenantId, options = {}) {
|
|
351
|
-
const { cascade = true } = options;
|
|
352
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
353
|
-
const pool = new Pool({
|
|
354
|
-
connectionString: this.tenantConfig.connection.url,
|
|
355
|
-
...this.tenantConfig.connection.poolConfig
|
|
356
|
-
});
|
|
357
|
-
try {
|
|
358
|
-
const cascadeSql = cascade ? "CASCADE" : "RESTRICT";
|
|
359
|
-
await pool.query(`DROP SCHEMA IF EXISTS "${schemaName}" ${cascadeSql}`);
|
|
360
|
-
} finally {
|
|
361
|
-
await pool.end();
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Check if a tenant schema exists
|
|
366
|
-
*/
|
|
367
|
-
async tenantExists(tenantId) {
|
|
368
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
369
|
-
const pool = new Pool({
|
|
370
|
-
connectionString: this.tenantConfig.connection.url,
|
|
371
|
-
...this.tenantConfig.connection.poolConfig
|
|
372
|
-
});
|
|
373
|
-
try {
|
|
374
|
-
const result = await pool.query(
|
|
375
|
-
`SELECT 1 FROM information_schema.schemata WHERE schema_name = $1`,
|
|
376
|
-
[schemaName]
|
|
377
|
-
);
|
|
378
|
-
return result.rowCount !== null && result.rowCount > 0;
|
|
379
|
-
} finally {
|
|
380
|
-
await pool.end();
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Mark migrations as applied without executing SQL
|
|
385
|
-
* Useful for syncing tracking state with already-applied migrations
|
|
386
|
-
*/
|
|
387
|
-
async markAsApplied(tenantId, options = {}) {
|
|
388
|
-
const startTime = Date.now();
|
|
389
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
390
|
-
const markedMigrations = [];
|
|
391
|
-
const pool = await this.createPool(schemaName);
|
|
392
|
-
try {
|
|
393
|
-
await this.migratorConfig.hooks?.beforeTenant?.(tenantId);
|
|
394
|
-
const format = await this.getOrDetectFormat(pool, schemaName);
|
|
395
|
-
await this.ensureMigrationsTable(pool, schemaName, format);
|
|
396
|
-
const allMigrations = await this.loadMigrations();
|
|
397
|
-
const applied = await this.getAppliedMigrations(pool, schemaName, format);
|
|
398
|
-
const appliedSet = new Set(applied.map((m) => m.identifier));
|
|
399
|
-
const pending = allMigrations.filter(
|
|
400
|
-
(m) => !this.isMigrationApplied(m, appliedSet, format)
|
|
401
|
-
);
|
|
402
|
-
for (const migration of pending) {
|
|
403
|
-
const migrationStart = Date.now();
|
|
404
|
-
options.onProgress?.(tenantId, "migrating", migration.name);
|
|
405
|
-
await this.migratorConfig.hooks?.beforeMigration?.(tenantId, migration.name);
|
|
406
|
-
await this.recordMigration(pool, schemaName, migration, format);
|
|
407
|
-
await this.migratorConfig.hooks?.afterMigration?.(
|
|
408
|
-
tenantId,
|
|
409
|
-
migration.name,
|
|
410
|
-
Date.now() - migrationStart
|
|
411
|
-
);
|
|
412
|
-
markedMigrations.push(migration.name);
|
|
413
|
-
}
|
|
414
|
-
const result = {
|
|
415
|
-
tenantId,
|
|
416
|
-
schemaName,
|
|
417
|
-
success: true,
|
|
418
|
-
appliedMigrations: markedMigrations,
|
|
419
|
-
durationMs: Date.now() - startTime,
|
|
420
|
-
format: format.format
|
|
421
|
-
};
|
|
422
|
-
await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);
|
|
423
|
-
return result;
|
|
424
|
-
} catch (error) {
|
|
425
|
-
const result = {
|
|
426
|
-
tenantId,
|
|
427
|
-
schemaName,
|
|
428
|
-
success: false,
|
|
429
|
-
appliedMigrations: markedMigrations,
|
|
430
|
-
error: error.message,
|
|
431
|
-
durationMs: Date.now() - startTime
|
|
432
|
-
};
|
|
433
|
-
await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);
|
|
434
|
-
return result;
|
|
435
|
-
} finally {
|
|
436
|
-
await pool.end();
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
/**
|
|
440
|
-
* Mark migrations as applied for all tenants without executing SQL
|
|
441
|
-
* Useful for syncing tracking state with already-applied migrations
|
|
442
|
-
*/
|
|
443
|
-
async markAllAsApplied(options = {}) {
|
|
444
|
-
const {
|
|
445
|
-
concurrency = 10,
|
|
446
|
-
onProgress,
|
|
447
|
-
onError
|
|
448
|
-
} = options;
|
|
449
|
-
const tenantIds = await this.migratorConfig.tenantDiscovery();
|
|
450
|
-
const results = [];
|
|
451
|
-
let aborted = false;
|
|
452
|
-
for (let i = 0; i < tenantIds.length && !aborted; i += concurrency) {
|
|
453
|
-
const batch = tenantIds.slice(i, i + concurrency);
|
|
454
|
-
const batchResults = await Promise.all(
|
|
455
|
-
batch.map(async (tenantId) => {
|
|
456
|
-
if (aborted) {
|
|
457
|
-
return this.createSkippedResult(tenantId);
|
|
458
|
-
}
|
|
459
|
-
try {
|
|
460
|
-
onProgress?.(tenantId, "starting");
|
|
461
|
-
const result = await this.markAsApplied(tenantId, { onProgress });
|
|
462
|
-
onProgress?.(tenantId, result.success ? "completed" : "failed");
|
|
463
|
-
return result;
|
|
464
|
-
} catch (error) {
|
|
465
|
-
onProgress?.(tenantId, "failed");
|
|
466
|
-
const action = onError?.(tenantId, error);
|
|
467
|
-
if (action === "abort") {
|
|
468
|
-
aborted = true;
|
|
469
|
-
}
|
|
470
|
-
return this.createErrorResult(tenantId, error);
|
|
471
|
-
}
|
|
472
|
-
})
|
|
473
|
-
);
|
|
474
|
-
results.push(...batchResults);
|
|
475
|
-
}
|
|
476
|
-
if (aborted) {
|
|
477
|
-
const remaining = tenantIds.slice(results.length);
|
|
478
|
-
for (const tenantId of remaining) {
|
|
479
|
-
results.push(this.createSkippedResult(tenantId));
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
return this.aggregateResults(results);
|
|
483
|
-
}
|
|
484
|
-
/**
|
|
485
|
-
* Get sync status for all tenants
|
|
486
|
-
* Detects divergences between migrations on disk and tracking in database
|
|
487
|
-
*/
|
|
488
|
-
async getSyncStatus() {
|
|
489
|
-
const tenantIds = await this.migratorConfig.tenantDiscovery();
|
|
490
|
-
const migrations = await this.loadMigrations();
|
|
491
|
-
const statuses = [];
|
|
492
|
-
for (const tenantId of tenantIds) {
|
|
493
|
-
statuses.push(await this.getTenantSyncStatus(tenantId, migrations));
|
|
494
|
-
}
|
|
495
|
-
return {
|
|
496
|
-
total: statuses.length,
|
|
497
|
-
inSync: statuses.filter((s) => s.inSync && !s.error).length,
|
|
498
|
-
outOfSync: statuses.filter((s) => !s.inSync && !s.error).length,
|
|
499
|
-
error: statuses.filter((s) => !!s.error).length,
|
|
500
|
-
details: statuses
|
|
501
|
-
};
|
|
502
|
-
}
|
|
503
|
-
/**
|
|
504
|
-
* Get sync status for a specific tenant
|
|
505
|
-
*/
|
|
506
|
-
async getTenantSyncStatus(tenantId, migrations) {
|
|
507
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
508
|
-
const pool = await this.createPool(schemaName);
|
|
509
|
-
try {
|
|
510
|
-
const allMigrations = migrations ?? await this.loadMigrations();
|
|
511
|
-
const migrationNames = new Set(allMigrations.map((m) => m.name));
|
|
512
|
-
const migrationHashes = new Set(allMigrations.map((m) => m.hash));
|
|
513
|
-
const tableExists = await this.migrationsTableExists(pool, schemaName);
|
|
514
|
-
if (!tableExists) {
|
|
515
|
-
return {
|
|
516
|
-
tenantId,
|
|
517
|
-
schemaName,
|
|
518
|
-
missing: allMigrations.map((m) => m.name),
|
|
519
|
-
orphans: [],
|
|
520
|
-
inSync: allMigrations.length === 0,
|
|
521
|
-
format: null
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
const format = await this.getOrDetectFormat(pool, schemaName);
|
|
525
|
-
const applied = await this.getAppliedMigrations(pool, schemaName, format);
|
|
526
|
-
const appliedIdentifiers = new Set(applied.map((m) => m.identifier));
|
|
527
|
-
const missing = allMigrations.filter((m) => !this.isMigrationApplied(m, appliedIdentifiers, format)).map((m) => m.name);
|
|
528
|
-
const orphans = applied.filter((m) => {
|
|
529
|
-
if (format.columns.identifier === "name") {
|
|
530
|
-
return !migrationNames.has(m.identifier);
|
|
531
|
-
}
|
|
532
|
-
return !migrationHashes.has(m.identifier) && !migrationNames.has(m.identifier);
|
|
533
|
-
}).map((m) => m.identifier);
|
|
534
|
-
return {
|
|
535
|
-
tenantId,
|
|
536
|
-
schemaName,
|
|
537
|
-
missing,
|
|
538
|
-
orphans,
|
|
539
|
-
inSync: missing.length === 0 && orphans.length === 0,
|
|
540
|
-
format: format.format
|
|
541
|
-
};
|
|
542
|
-
} catch (error) {
|
|
543
|
-
return {
|
|
544
|
-
tenantId,
|
|
545
|
-
schemaName,
|
|
546
|
-
missing: [],
|
|
547
|
-
orphans: [],
|
|
548
|
-
inSync: false,
|
|
549
|
-
format: null,
|
|
550
|
-
error: error.message
|
|
551
|
-
};
|
|
552
|
-
} finally {
|
|
553
|
-
await pool.end();
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
/**
|
|
557
|
-
* Mark missing migrations as applied for a tenant
|
|
558
|
-
*/
|
|
559
|
-
async markMissing(tenantId) {
|
|
560
|
-
const startTime = Date.now();
|
|
561
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
562
|
-
const markedMigrations = [];
|
|
563
|
-
const pool = await this.createPool(schemaName);
|
|
564
|
-
try {
|
|
565
|
-
const syncStatus = await this.getTenantSyncStatus(tenantId);
|
|
566
|
-
if (syncStatus.error) {
|
|
567
|
-
return {
|
|
568
|
-
tenantId,
|
|
569
|
-
schemaName,
|
|
570
|
-
success: false,
|
|
571
|
-
markedMigrations: [],
|
|
572
|
-
removedOrphans: [],
|
|
573
|
-
error: syncStatus.error,
|
|
574
|
-
durationMs: Date.now() - startTime
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
if (syncStatus.missing.length === 0) {
|
|
578
|
-
return {
|
|
579
|
-
tenantId,
|
|
580
|
-
schemaName,
|
|
581
|
-
success: true,
|
|
582
|
-
markedMigrations: [],
|
|
583
|
-
removedOrphans: [],
|
|
584
|
-
durationMs: Date.now() - startTime
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
const format = await this.getOrDetectFormat(pool, schemaName);
|
|
588
|
-
await this.ensureMigrationsTable(pool, schemaName, format);
|
|
589
|
-
const allMigrations = await this.loadMigrations();
|
|
590
|
-
const missingSet = new Set(syncStatus.missing);
|
|
591
|
-
for (const migration of allMigrations) {
|
|
592
|
-
if (missingSet.has(migration.name)) {
|
|
593
|
-
await this.recordMigration(pool, schemaName, migration, format);
|
|
594
|
-
markedMigrations.push(migration.name);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return {
|
|
598
|
-
tenantId,
|
|
599
|
-
schemaName,
|
|
600
|
-
success: true,
|
|
601
|
-
markedMigrations,
|
|
602
|
-
removedOrphans: [],
|
|
603
|
-
durationMs: Date.now() - startTime
|
|
604
|
-
};
|
|
605
|
-
} catch (error) {
|
|
606
|
-
return {
|
|
607
|
-
tenantId,
|
|
608
|
-
schemaName,
|
|
609
|
-
success: false,
|
|
610
|
-
markedMigrations,
|
|
611
|
-
removedOrphans: [],
|
|
612
|
-
error: error.message,
|
|
613
|
-
durationMs: Date.now() - startTime
|
|
614
|
-
};
|
|
615
|
-
} finally {
|
|
616
|
-
await pool.end();
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
/**
|
|
620
|
-
* Mark missing migrations as applied for all tenants
|
|
621
|
-
*/
|
|
622
|
-
async markAllMissing(options = {}) {
|
|
623
|
-
const { concurrency = 10, onProgress, onError } = options;
|
|
624
|
-
const tenantIds = await this.migratorConfig.tenantDiscovery();
|
|
625
|
-
const results = [];
|
|
626
|
-
let aborted = false;
|
|
627
|
-
for (let i = 0; i < tenantIds.length && !aborted; i += concurrency) {
|
|
628
|
-
const batch = tenantIds.slice(i, i + concurrency);
|
|
629
|
-
const batchResults = await Promise.all(
|
|
630
|
-
batch.map(async (tenantId) => {
|
|
631
|
-
if (aborted) {
|
|
632
|
-
return this.createSkippedSyncResult(tenantId);
|
|
633
|
-
}
|
|
634
|
-
try {
|
|
635
|
-
onProgress?.(tenantId, "starting");
|
|
636
|
-
const result = await this.markMissing(tenantId);
|
|
637
|
-
onProgress?.(tenantId, result.success ? "completed" : "failed");
|
|
638
|
-
return result;
|
|
639
|
-
} catch (error) {
|
|
640
|
-
onProgress?.(tenantId, "failed");
|
|
641
|
-
const action = onError?.(tenantId, error);
|
|
642
|
-
if (action === "abort") {
|
|
643
|
-
aborted = true;
|
|
644
|
-
}
|
|
645
|
-
return this.createErrorSyncResult(tenantId, error);
|
|
646
|
-
}
|
|
647
|
-
})
|
|
648
|
-
);
|
|
649
|
-
results.push(...batchResults);
|
|
650
|
-
}
|
|
651
|
-
return this.aggregateSyncResults(results);
|
|
652
|
-
}
|
|
653
|
-
/**
|
|
654
|
-
* Remove orphan migration records for a tenant
|
|
655
|
-
*/
|
|
656
|
-
async cleanOrphans(tenantId) {
|
|
657
|
-
const startTime = Date.now();
|
|
658
|
-
const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
|
|
659
|
-
const removedOrphans = [];
|
|
660
|
-
const pool = await this.createPool(schemaName);
|
|
661
|
-
try {
|
|
662
|
-
const syncStatus = await this.getTenantSyncStatus(tenantId);
|
|
663
|
-
if (syncStatus.error) {
|
|
664
|
-
return {
|
|
665
|
-
tenantId,
|
|
666
|
-
schemaName,
|
|
667
|
-
success: false,
|
|
668
|
-
markedMigrations: [],
|
|
669
|
-
removedOrphans: [],
|
|
670
|
-
error: syncStatus.error,
|
|
671
|
-
durationMs: Date.now() - startTime
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
if (syncStatus.orphans.length === 0) {
|
|
675
|
-
return {
|
|
676
|
-
tenantId,
|
|
677
|
-
schemaName,
|
|
678
|
-
success: true,
|
|
679
|
-
markedMigrations: [],
|
|
680
|
-
removedOrphans: [],
|
|
681
|
-
durationMs: Date.now() - startTime
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
const format = await this.getOrDetectFormat(pool, schemaName);
|
|
685
|
-
const identifierColumn = format.columns.identifier;
|
|
686
|
-
for (const orphan of syncStatus.orphans) {
|
|
687
|
-
await pool.query(
|
|
688
|
-
`DELETE FROM "${schemaName}"."${format.tableName}" WHERE "${identifierColumn}" = $1`,
|
|
689
|
-
[orphan]
|
|
690
|
-
);
|
|
691
|
-
removedOrphans.push(orphan);
|
|
692
|
-
}
|
|
693
|
-
return {
|
|
694
|
-
tenantId,
|
|
695
|
-
schemaName,
|
|
696
|
-
success: true,
|
|
697
|
-
markedMigrations: [],
|
|
698
|
-
removedOrphans,
|
|
699
|
-
durationMs: Date.now() - startTime
|
|
700
|
-
};
|
|
701
|
-
} catch (error) {
|
|
702
|
-
return {
|
|
703
|
-
tenantId,
|
|
704
|
-
schemaName,
|
|
705
|
-
success: false,
|
|
706
|
-
markedMigrations: [],
|
|
707
|
-
removedOrphans,
|
|
708
|
-
error: error.message,
|
|
709
|
-
durationMs: Date.now() - startTime
|
|
710
|
-
};
|
|
711
|
-
} finally {
|
|
712
|
-
await pool.end();
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Remove orphan migration records for all tenants
|
|
717
|
-
*/
|
|
718
|
-
async cleanAllOrphans(options = {}) {
|
|
719
|
-
const { concurrency = 10, onProgress, onError } = options;
|
|
720
|
-
const tenantIds = await this.migratorConfig.tenantDiscovery();
|
|
721
|
-
const results = [];
|
|
722
|
-
let aborted = false;
|
|
723
|
-
for (let i = 0; i < tenantIds.length && !aborted; i += concurrency) {
|
|
724
|
-
const batch = tenantIds.slice(i, i + concurrency);
|
|
725
|
-
const batchResults = await Promise.all(
|
|
726
|
-
batch.map(async (tenantId) => {
|
|
727
|
-
if (aborted) {
|
|
728
|
-
return this.createSkippedSyncResult(tenantId);
|
|
729
|
-
}
|
|
730
|
-
try {
|
|
731
|
-
onProgress?.(tenantId, "starting");
|
|
732
|
-
const result = await this.cleanOrphans(tenantId);
|
|
733
|
-
onProgress?.(tenantId, result.success ? "completed" : "failed");
|
|
734
|
-
return result;
|
|
735
|
-
} catch (error) {
|
|
736
|
-
onProgress?.(tenantId, "failed");
|
|
737
|
-
const action = onError?.(tenantId, error);
|
|
738
|
-
if (action === "abort") {
|
|
739
|
-
aborted = true;
|
|
740
|
-
}
|
|
741
|
-
return this.createErrorSyncResult(tenantId, error);
|
|
742
|
-
}
|
|
743
|
-
})
|
|
744
|
-
);
|
|
745
|
-
results.push(...batchResults);
|
|
746
|
-
}
|
|
747
|
-
return this.aggregateSyncResults(results);
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Load migration files from the migrations folder
|
|
751
|
-
*/
|
|
752
|
-
async loadMigrations() {
|
|
753
|
-
const files = await readdir(this.migratorConfig.migrationsFolder);
|
|
754
|
-
const migrations = [];
|
|
755
|
-
for (const file of files) {
|
|
756
|
-
if (!file.endsWith(".sql")) continue;
|
|
757
|
-
const filePath = join(this.migratorConfig.migrationsFolder, file);
|
|
758
|
-
const content = await readFile(filePath, "utf-8");
|
|
759
|
-
const match = file.match(/^(\d+)_/);
|
|
760
|
-
const timestamp = match?.[1] ? parseInt(match[1], 10) : 0;
|
|
761
|
-
const hash = createHash("sha256").update(content).digest("hex");
|
|
762
|
-
migrations.push({
|
|
763
|
-
name: basename(file, ".sql"),
|
|
764
|
-
path: filePath,
|
|
765
|
-
sql: content,
|
|
766
|
-
timestamp,
|
|
767
|
-
hash
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
return migrations.sort((a, b) => a.timestamp - b.timestamp);
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Create a pool for a specific schema
|
|
774
|
-
*/
|
|
775
|
-
async createPool(schemaName) {
|
|
776
|
-
return new Pool({
|
|
777
|
-
connectionString: this.tenantConfig.connection.url,
|
|
778
|
-
...this.tenantConfig.connection.poolConfig,
|
|
779
|
-
options: `-c search_path="${schemaName}",public`
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
/**
|
|
783
|
-
* Ensure migrations table exists with the correct format
|
|
784
|
-
*/
|
|
785
|
-
async ensureMigrationsTable(pool, schemaName, format) {
|
|
786
|
-
const { identifier, timestamp, timestampType } = format.columns;
|
|
787
|
-
const identifierCol = identifier === "name" ? "name VARCHAR(255) NOT NULL UNIQUE" : "hash TEXT NOT NULL";
|
|
788
|
-
const timestampCol = timestampType === "bigint" ? `${timestamp} BIGINT NOT NULL` : `${timestamp} TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP`;
|
|
789
|
-
await pool.query(`
|
|
790
|
-
CREATE TABLE IF NOT EXISTS "${schemaName}"."${format.tableName}" (
|
|
6
|
+
WHERE table_schema = $1 AND table_name = $2`,[e,n]),t=new Map(r.rows.map(i=>[i.column_name,i.data_type]));return t.has("name")?{format:"name",tableName:n,columns:{identifier:"name",timestamp:t.has("applied_at")?"applied_at":"created_at",timestampType:"timestamp"}}:t.has("hash")?t.get("created_at")==="bigint"?{format:"drizzle-kit",tableName:n,columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}}:{format:"hash",tableName:n,columns:{identifier:"hash",timestamp:"created_at",timestampType:"timestamp"}}:null}function w(m,e="__drizzle_migrations"){switch(m){case "name":return {format:"name",tableName:e,columns:{identifier:"name",timestamp:"applied_at",timestampType:"timestamp"}};case "hash":return {format:"hash",tableName:e,columns:{identifier:"hash",timestamp:"created_at",timestampType:"timestamp"}};case "drizzle-kit":return {format:"drizzle-kit",tableName:e,columns:{identifier:"hash",timestamp:"created_at",timestampType:"bigint"}}}}var re="__drizzle_migrations",D=class{constructor(e,n){this.config=e;this.migrationsTable=n??re;}migrationsTable;getSchemaName(e){return this.config.isolation.schemaNameTemplate(e)}async createPool(e){return new Pool({connectionString:this.config.connection.url,...this.config.connection.poolConfig,options:`-c search_path="${e}",public`})}async createRootPool(){return new Pool({connectionString:this.config.connection.url,...this.config.connection.poolConfig})}async createSchema(e){let n=this.getSchemaName(e),a=await this.createRootPool();try{await a.query(`CREATE SCHEMA IF NOT EXISTS "${n}"`);}finally{await a.end();}}async dropSchema(e,n={}){let{cascade:a=true}=n,r=this.getSchemaName(e),t=await this.createRootPool();try{let i=a?"CASCADE":"RESTRICT";await t.query(`DROP SCHEMA IF EXISTS "${r}" ${i}`);}finally{await t.end();}}async schemaExists(e){let n=this.getSchemaName(e),a=await this.createRootPool();try{let r=await a.query("SELECT 1 FROM information_schema.schemata WHERE schema_name = $1",[n]);return r.rowCount!==null&&r.rowCount>0}finally{await a.end();}}async listSchemas(e){let n=await this.createRootPool();try{let a=e?"SELECT schema_name FROM information_schema.schemata WHERE schema_name LIKE $1 ORDER BY schema_name":"SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast') ORDER BY schema_name";return (await n.query(a,e?[e]:[])).rows.map(t=>t.schema_name)}finally{await n.end();}}async ensureMigrationsTable(e,n,a){let{identifier:r,timestamp:t,timestampType:i}=a.columns,s=r==="name"?"name VARCHAR(255) NOT NULL UNIQUE":"hash TEXT NOT NULL",o=i==="bigint"?`${t} BIGINT NOT NULL`:`${t} TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP`;await e.query(`
|
|
7
|
+
CREATE TABLE IF NOT EXISTS "${n}"."${a.tableName}" (
|
|
791
8
|
id SERIAL PRIMARY KEY,
|
|
792
|
-
${
|
|
793
|
-
${
|
|
9
|
+
${s},
|
|
10
|
+
${o}
|
|
794
11
|
)
|
|
795
|
-
`);
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
removedOrphans: [],
|
|
939
|
-
error: "Skipped due to abort",
|
|
940
|
-
durationMs: 0
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
/**
|
|
944
|
-
* Create an error sync result
|
|
945
|
-
*/
|
|
946
|
-
createErrorSyncResult(tenantId, error) {
|
|
947
|
-
return {
|
|
948
|
-
tenantId,
|
|
949
|
-
schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),
|
|
950
|
-
success: false,
|
|
951
|
-
markedMigrations: [],
|
|
952
|
-
removedOrphans: [],
|
|
953
|
-
error: error.message,
|
|
954
|
-
durationMs: 0
|
|
955
|
-
};
|
|
956
|
-
}
|
|
957
|
-
/**
|
|
958
|
-
* Aggregate sync results
|
|
959
|
-
*/
|
|
960
|
-
aggregateSyncResults(results) {
|
|
961
|
-
return {
|
|
962
|
-
total: results.length,
|
|
963
|
-
succeeded: results.filter((r) => r.success).length,
|
|
964
|
-
failed: results.filter((r) => !r.success).length,
|
|
965
|
-
details: results
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
function createMigrator(tenantConfig, migratorConfig) {
|
|
970
|
-
return new Migrator(tenantConfig, migratorConfig);
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
export { DEFAULT_FORMAT, DRIZZLE_KIT_FORMAT, Migrator, createMigrator, detectTableFormat, getFormatConfig };
|
|
974
|
-
//# sourceMappingURL=index.js.map
|
|
975
|
-
//# sourceMappingURL=index.js.map
|
|
12
|
+
`);}async migrationsTableExists(e,n){let a=await e.query(`SELECT 1 FROM information_schema.tables
|
|
13
|
+
WHERE table_schema = $1 AND table_name = $2`,[n,this.migrationsTable]);return a.rowCount!==null&&a.rowCount>0}getMigrationsTableName(){return this.migrationsTable}};function ie(m,e){return new D(m,e)}async function v(m,e,n){return (await m.query(`SELECT
|
|
14
|
+
column_name,
|
|
15
|
+
data_type,
|
|
16
|
+
udt_name,
|
|
17
|
+
is_nullable,
|
|
18
|
+
column_default,
|
|
19
|
+
character_maximum_length,
|
|
20
|
+
numeric_precision,
|
|
21
|
+
numeric_scale,
|
|
22
|
+
ordinal_position
|
|
23
|
+
FROM information_schema.columns
|
|
24
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
25
|
+
ORDER BY ordinal_position`,[e,n])).rows.map(r=>({name:r.column_name,dataType:r.data_type,udtName:r.udt_name,isNullable:r.is_nullable==="YES",columnDefault:r.column_default,characterMaximumLength:r.character_maximum_length,numericPrecision:r.numeric_precision,numericScale:r.numeric_scale,ordinalPosition:r.ordinal_position}))}function L(m){return m===null?null:m.replace(/^'(.+)'::.+$/,"$1").replace(/^(.+)::.+$/,"$1").trim()}function q(m,e){let n=[],a=new Map(m.map(t=>[t.name,t])),r=new Map(e.map(t=>[t.name,t]));for(let t of m){let i=r.get(t.name);if(!i){n.push({column:t.name,type:"missing",expected:t.dataType,description:`Column "${t.name}" (${t.dataType}) is missing`});continue}t.udtName!==i.udtName&&n.push({column:t.name,type:"type_mismatch",expected:t.udtName,actual:i.udtName,description:`Column "${t.name}" type mismatch: expected "${t.udtName}", got "${i.udtName}"`}),t.isNullable!==i.isNullable&&n.push({column:t.name,type:"nullable_mismatch",expected:t.isNullable,actual:i.isNullable,description:`Column "${t.name}" nullable mismatch: expected ${t.isNullable?"NULL":"NOT NULL"}, got ${i.isNullable?"NULL":"NOT NULL"}`});let s=L(t.columnDefault),o=L(i.columnDefault);s!==o&&n.push({column:t.name,type:"default_mismatch",expected:t.columnDefault,actual:i.columnDefault,description:`Column "${t.name}" default mismatch: expected "${t.columnDefault??"none"}", got "${i.columnDefault??"none"}"`});}for(let t of e)a.has(t.name)||n.push({column:t.name,type:"extra",actual:t.dataType,description:`Extra column "${t.name}" (${t.dataType}) not in reference`});return n}async function j(m,e,n){let a=await m.query(`SELECT indexname, indexdef
|
|
26
|
+
FROM pg_indexes
|
|
27
|
+
WHERE schemaname = $1 AND tablename = $2
|
|
28
|
+
ORDER BY indexname`,[e,n]),r=await m.query(`SELECT
|
|
29
|
+
i.relname as indexname,
|
|
30
|
+
a.attname as column_name,
|
|
31
|
+
ix.indisunique as is_unique,
|
|
32
|
+
ix.indisprimary as is_primary
|
|
33
|
+
FROM pg_class t
|
|
34
|
+
JOIN pg_index ix ON t.oid = ix.indrelid
|
|
35
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
36
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
|
|
37
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
38
|
+
WHERE n.nspname = $1 AND t.relname = $2
|
|
39
|
+
ORDER BY i.relname, a.attnum`,[e,n]),t=new Map;for(let i of r.rows){let s=t.get(i.indexname);s?s.columns.push(i.column_name):t.set(i.indexname,{columns:[i.column_name],isUnique:i.is_unique,isPrimary:i.is_primary});}return a.rows.map(i=>{let s=t.get(i.indexname);return {name:i.indexname,columns:s?.columns??[],isUnique:s?.isUnique??false,isPrimary:s?.isPrimary??false,definition:i.indexdef}})}function I(m,e){let n=[],a=new Map(m.map(t=>[t.name,t])),r=new Map(e.map(t=>[t.name,t]));for(let t of m){let i=r.get(t.name);if(!i){n.push({index:t.name,type:"missing",expected:t.definition,description:`Index "${t.name}" is missing`});continue}let s=t.columns.sort().join(","),o=i.columns.sort().join(",");(s!==o||t.isUnique!==i.isUnique)&&n.push({index:t.name,type:"definition_mismatch",expected:t.definition,actual:i.definition,description:`Index "${t.name}" definition differs`});}for(let t of e)a.has(t.name)||n.push({index:t.name,type:"extra",actual:t.definition,description:`Extra index "${t.name}" not in reference`});return n}async function B(m,e,n){let a=await m.query(`SELECT
|
|
40
|
+
tc.constraint_name,
|
|
41
|
+
tc.constraint_type,
|
|
42
|
+
kcu.column_name,
|
|
43
|
+
ccu.table_schema as foreign_table_schema,
|
|
44
|
+
ccu.table_name as foreign_table_name,
|
|
45
|
+
ccu.column_name as foreign_column_name,
|
|
46
|
+
cc.check_clause
|
|
47
|
+
FROM information_schema.table_constraints tc
|
|
48
|
+
LEFT JOIN information_schema.key_column_usage kcu
|
|
49
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
50
|
+
AND tc.table_schema = kcu.table_schema
|
|
51
|
+
LEFT JOIN information_schema.constraint_column_usage ccu
|
|
52
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
53
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
54
|
+
LEFT JOIN information_schema.check_constraints cc
|
|
55
|
+
ON tc.constraint_name = cc.constraint_name
|
|
56
|
+
AND tc.constraint_type = 'CHECK'
|
|
57
|
+
WHERE tc.table_schema = $1 AND tc.table_name = $2
|
|
58
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,n]),r=new Map;for(let t of a.rows){let i=r.get(t.constraint_name);if(i)t.column_name&&!i.columns.includes(t.column_name)&&i.columns.push(t.column_name),t.foreign_column_name&&i.foreignColumns&&!i.foreignColumns.includes(t.foreign_column_name)&&i.foreignColumns.push(t.foreign_column_name);else {let s={name:t.constraint_name,type:t.constraint_type,columns:t.column_name?[t.column_name]:[]};t.foreign_table_name&&(s.foreignTable=t.foreign_table_name),t.foreign_column_name&&(s.foreignColumns=[t.foreign_column_name]),t.check_clause&&(s.checkExpression=t.check_clause),r.set(t.constraint_name,s);}}return Array.from(r.values())}function z(m,e){let n=[],a=new Map(m.map(t=>[t.name,t])),r=new Map(e.map(t=>[t.name,t]));for(let t of m){let i=r.get(t.name);if(!i){n.push({constraint:t.name,type:"missing",expected:`${t.type} on (${t.columns.join(", ")})`,description:`Constraint "${t.name}" (${t.type}) is missing`});continue}let s=t.columns.sort().join(","),o=i.columns.sort().join(",");(t.type!==i.type||s!==o)&&n.push({constraint:t.name,type:"definition_mismatch",expected:`${t.type} on (${t.columns.join(", ")})`,actual:`${i.type} on (${i.columns.join(", ")})`,description:`Constraint "${t.name}" definition differs`});}for(let t of e)a.has(t.name)||n.push({constraint:t.name,type:"extra",actual:`${t.type} on (${t.columns.join(", ")})`,description:`Extra constraint "${t.name}" (${t.type}) not in reference`});return n}var se="__drizzle_migrations",O=class{constructor(e,n,a){this.tenantConfig=e;this.schemaManager=n;this.driftConfig=a;this.migrationsTable=a.migrationsTable??se;}migrationsTable;getSchemaName(e){return this.tenantConfig.isolation.schemaNameTemplate(e)}async createPool(e){return this.schemaManager.createPool(e)}async detectDrift(e={}){let n=Date.now(),{concurrency:a=10,includeIndexes:r=true,includeConstraints:t=true,excludeTables:i=[this.migrationsTable],onProgress:s}=e,o=e.tenantIds??await this.driftConfig.tenantDiscovery();if(o.length===0)return {referenceTenant:"",total:0,noDrift:0,withDrift:0,error:0,details:[],timestamp:new Date().toISOString(),durationMs:Date.now()-n};let l=e.referenceTenant??o[0];s?.(l,"starting"),s?.(l,"introspecting");let c=await this.introspectSchema(l,{includeIndexes:r,includeConstraints:t,excludeTables:i});if(!c)return {referenceTenant:l,total:o.length,noDrift:0,withDrift:0,error:o.length,details:o.map(p=>({tenantId:p,schemaName:this.getSchemaName(p),hasDrift:false,tables:[],issueCount:0,error:p===l?"Failed to introspect reference tenant":"Reference tenant introspection failed"})),timestamp:new Date().toISOString(),durationMs:Date.now()-n};s?.(l,"completed");let u=o.filter(p=>p!==l),g=[];g.push({tenantId:l,schemaName:c.schemaName,hasDrift:false,tables:[],issueCount:0});for(let p=0;p<u.length;p+=a){let h=u.slice(p,p+a),y=await Promise.all(h.map(async d=>{try{s?.(d,"starting"),s?.(d,"introspecting");let f=await this.introspectSchema(d,{includeIndexes:r,includeConstraints:t,excludeTables:i});if(!f)return s?.(d,"failed"),{tenantId:d,schemaName:this.getSchemaName(d),hasDrift:!1,tables:[],issueCount:0,error:"Failed to introspect schema"};s?.(d,"comparing");let P=this.compareSchemas(c,f,{includeIndexes:r,includeConstraints:t});return s?.(d,"completed"),P}catch(f){return s?.(d,"failed"),{tenantId:d,schemaName:this.getSchemaName(d),hasDrift:false,tables:[],issueCount:0,error:f.message}}}));g.push(...y);}return {referenceTenant:l,total:g.length,noDrift:g.filter(p=>!p.hasDrift&&!p.error).length,withDrift:g.filter(p=>p.hasDrift&&!p.error).length,error:g.filter(p=>!!p.error).length,details:g,timestamp:new Date().toISOString(),durationMs:Date.now()-n}}async compareTenant(e,n,a={}){let{includeIndexes:r=true,includeConstraints:t=true,excludeTables:i=[this.migrationsTable]}=a,s=await this.introspectSchema(n,{includeIndexes:r,includeConstraints:t,excludeTables:i});if(!s)return {tenantId:e,schemaName:this.getSchemaName(e),hasDrift:false,tables:[],issueCount:0,error:"Failed to introspect reference tenant"};let o=await this.introspectSchema(e,{includeIndexes:r,includeConstraints:t,excludeTables:i});return o?this.compareSchemas(s,o,{includeIndexes:r,includeConstraints:t}):{tenantId:e,schemaName:this.getSchemaName(e),hasDrift:false,tables:[],issueCount:0,error:"Failed to introspect tenant schema"}}async introspectSchema(e,n={}){let a=this.getSchemaName(e),r=await this.createPool(a);try{let t=await this.introspectTables(r,a,n);return {tenantId:e,schemaName:a,tables:t,introspectedAt:new Date}}catch{return null}finally{await r.end();}}compareSchemas(e,n,a={}){let{includeIndexes:r=true,includeConstraints:t=true}=a,i=[],s=0,o=new Map(e.tables.map(c=>[c.name,c])),l=new Map(n.tables.map(c=>[c.name,c]));for(let c of e.tables){let u=l.get(c.name);if(!u){i.push({table:c.name,status:"missing",columns:c.columns.map(d=>({column:d.name,type:"missing",expected:d.dataType,description:`Column "${d.name}" (${d.dataType}) is missing`})),indexes:[],constraints:[]}),s+=c.columns.length;continue}let g=q(c.columns,u.columns),p=r?I(c.indexes,u.indexes):[],h=t?z(c.constraints,u.constraints):[],y=g.length+p.length+h.length;s+=y,y>0&&i.push({table:c.name,status:"drifted",columns:g,indexes:p,constraints:h});}for(let c of n.tables)o.has(c.name)||(i.push({table:c.name,status:"extra",columns:c.columns.map(u=>({column:u.name,type:"extra",actual:u.dataType,description:`Extra column "${u.name}" (${u.dataType}) not in reference`})),indexes:[],constraints:[]}),s+=c.columns.length);return {tenantId:n.tenantId,schemaName:n.schemaName,hasDrift:s>0,tables:i,issueCount:s}}async introspectTables(e,n,a){let{includeIndexes:r=true,includeConstraints:t=true,excludeTables:i=[]}=a,s=await e.query(`SELECT table_name
|
|
59
|
+
FROM information_schema.tables
|
|
60
|
+
WHERE table_schema = $1
|
|
61
|
+
AND table_type = 'BASE TABLE'
|
|
62
|
+
ORDER BY table_name`,[n]),o=[];for(let l of s.rows){if(i.includes(l.table_name))continue;let c=await v(e,n,l.table_name),u=r?await j(e,n,l.table_name):[],g=t?await B(e,n,l.table_name):[];o.push({name:l.table_name,columns:c,indexes:u,constraints:g});}return o}};var R=class{constructor(e,n){this.config=e;this.deps=n;}async seedTenant(e,n){let a=Date.now(),r=this.deps.schemaNameTemplate(e),t=await this.deps.createPool(r);try{let i=drizzle(t,{schema:this.deps.tenantSchema});return await n(i,e),{tenantId:e,schemaName:r,success:!0,durationMs:Date.now()-a}}catch(i){return {tenantId:e,schemaName:r,success:false,error:i.message,durationMs:Date.now()-a}}finally{await t.end();}}async seedAll(e,n={}){let{concurrency:a=10,onProgress:r,onError:t}=n,i=await this.config.tenantDiscovery(),s=[],o=false;for(let l=0;l<i.length&&!o;l+=a){let c=i.slice(l,l+a),u=await Promise.all(c.map(async g=>{if(o)return this.createSkippedResult(g);try{r?.(g,"starting"),r?.(g,"seeding");let p=await this.seedTenant(g,e);return r?.(g,p.success?"completed":"failed"),p}catch(p){return r?.(g,"failed"),t?.(g,p)==="abort"&&(o=true),this.createErrorResult(g,p)}}));s.push(...u);}if(o){let l=i.slice(s.length);for(let c of l)s.push(this.createSkippedResult(c));}return this.aggregateResults(s)}async seedTenants(e,n,a={}){let{concurrency:r=10,onProgress:t,onError:i}=a,s=[];for(let o=0;o<e.length;o+=r){let l=e.slice(o,o+r),c=await Promise.all(l.map(async u=>{try{t?.(u,"starting"),t?.(u,"seeding");let g=await this.seedTenant(u,n);return t?.(u,g.success?"completed":"failed"),g}catch(g){return t?.(u,"failed"),i?.(u,g),this.createErrorResult(u,g)}}));s.push(...c);}return this.aggregateResults(s)}createSkippedResult(e){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,error:"Skipped due to abort",durationMs:0}}createErrorResult(e,n){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,error:n.message,durationMs:0}}aggregateResults(e){return {total:e.length,succeeded:e.filter(n=>n.success).length,failed:e.filter(n=>!n.success&&n.error!=="Skipped due to abort").length,skipped:e.filter(n=>n.error==="Skipped due to abort").length,details:e}}};function ce(m,e){return new R(m,e)}var le="public",A=class{constructor(e,n){this.config=e;this.deps=n;this.schemaName=e.schemaName??le;}schemaName;async seed(e){let n=Date.now(),a=await this.deps.createPool();try{this.config.hooks?.onStart?.();let r=drizzle(a,{schema:this.deps.sharedSchema});return await e(r),this.config.hooks?.onComplete?.(),{schemaName:this.schemaName,success:!0,durationMs:Date.now()-n}}catch(r){return this.config.hooks?.onError?.(r),{schemaName:this.schemaName,success:false,error:r.message,durationMs:Date.now()-n}}finally{await a.end();}}};var x=class{constructor(e,n){this.config=e;this.deps=n;}async getSyncStatus(){let e=await this.config.tenantDiscovery(),n=await this.deps.loadMigrations(),a=[];for(let r of e)a.push(await this.getTenantSyncStatus(r,n));return {total:a.length,inSync:a.filter(r=>r.inSync&&!r.error).length,outOfSync:a.filter(r=>!r.inSync&&!r.error).length,error:a.filter(r=>!!r.error).length,details:a}}async getTenantSyncStatus(e,n){let a=this.deps.schemaNameTemplate(e),r=await this.deps.createPool(a);try{let t=n??await this.deps.loadMigrations(),i=new Set(t.map(h=>h.name)),s=new Set(t.map(h=>h.hash));if(!await this.deps.migrationsTableExists(r,a))return {tenantId:e,schemaName:a,missing:t.map(h=>h.name),orphans:[],inSync:t.length===0,format:null};let l=await this.deps.getOrDetectFormat(r,a),c=await this.getAppliedMigrations(r,a,l),u=new Set(c.map(h=>h.identifier)),g=t.filter(h=>!this.isMigrationApplied(h,u,l)).map(h=>h.name),p=c.filter(h=>(l.columns.identifier==="name"||!s.has(h.identifier))&&!i.has(h.identifier)).map(h=>h.identifier);return {tenantId:e,schemaName:a,missing:g,orphans:p,inSync:g.length===0&&p.length===0,format:l.format}}catch(t){return {tenantId:e,schemaName:a,missing:[],orphans:[],inSync:false,format:null,error:t.message}}finally{await r.end();}}async markMissing(e){let n=Date.now(),a=this.deps.schemaNameTemplate(e),r=[],t=await this.deps.createPool(a);try{let i=await this.getTenantSyncStatus(e);if(i.error)return {tenantId:e,schemaName:a,success:!1,markedMigrations:[],removedOrphans:[],error:i.error,durationMs:Date.now()-n};if(i.missing.length===0)return {tenantId:e,schemaName:a,success:!0,markedMigrations:[],removedOrphans:[],durationMs:Date.now()-n};let s=await this.deps.getOrDetectFormat(t,a);await this.deps.ensureMigrationsTable(t,a,s);let o=await this.deps.loadMigrations(),l=new Set(i.missing);for(let c of o)l.has(c.name)&&(await this.recordMigration(t,a,c,s),r.push(c.name));return {tenantId:e,schemaName:a,success:!0,markedMigrations:r,removedOrphans:[],durationMs:Date.now()-n}}catch(i){return {tenantId:e,schemaName:a,success:false,markedMigrations:r,removedOrphans:[],error:i.message,durationMs:Date.now()-n}}finally{await t.end();}}async markAllMissing(e={}){let{concurrency:n=10,onProgress:a,onError:r}=e,t=await this.config.tenantDiscovery(),i=[],s=false;for(let o=0;o<t.length&&!s;o+=n){let l=t.slice(o,o+n),c=await Promise.all(l.map(async u=>{if(s)return this.createSkippedSyncResult(u);try{a?.(u,"starting");let g=await this.markMissing(u);return a?.(u,g.success?"completed":"failed"),g}catch(g){return a?.(u,"failed"),r?.(u,g)==="abort"&&(s=true),this.createErrorSyncResult(u,g)}}));i.push(...c);}return this.aggregateSyncResults(i)}async cleanOrphans(e){let n=Date.now(),a=this.deps.schemaNameTemplate(e),r=[],t=await this.deps.createPool(a);try{let i=await this.getTenantSyncStatus(e);if(i.error)return {tenantId:e,schemaName:a,success:!1,markedMigrations:[],removedOrphans:[],error:i.error,durationMs:Date.now()-n};if(i.orphans.length===0)return {tenantId:e,schemaName:a,success:!0,markedMigrations:[],removedOrphans:[],durationMs:Date.now()-n};let s=await this.deps.getOrDetectFormat(t,a),o=s.columns.identifier;for(let l of i.orphans)await t.query(`DELETE FROM "${a}"."${s.tableName}" WHERE "${o}" = $1`,[l]),r.push(l);return {tenantId:e,schemaName:a,success:!0,markedMigrations:[],removedOrphans:r,durationMs:Date.now()-n}}catch(i){return {tenantId:e,schemaName:a,success:false,markedMigrations:[],removedOrphans:r,error:i.message,durationMs:Date.now()-n}}finally{await t.end();}}async cleanAllOrphans(e={}){let{concurrency:n=10,onProgress:a,onError:r}=e,t=await this.config.tenantDiscovery(),i=[],s=false;for(let o=0;o<t.length&&!s;o+=n){let l=t.slice(o,o+n),c=await Promise.all(l.map(async u=>{if(s)return this.createSkippedSyncResult(u);try{a?.(u,"starting");let g=await this.cleanOrphans(u);return a?.(u,g.success?"completed":"failed"),g}catch(g){return a?.(u,"failed"),r?.(u,g)==="abort"&&(s=true),this.createErrorSyncResult(u,g)}}));i.push(...c);}return this.aggregateSyncResults(i)}async getAppliedMigrations(e,n,a){let r=a.columns.identifier,t=a.columns.timestamp;return (await e.query(`SELECT id, "${r}" as identifier, "${t}" as applied_at
|
|
63
|
+
FROM "${n}"."${a.tableName}"
|
|
64
|
+
ORDER BY id`)).rows.map(s=>{let o=a.columns.timestampType==="bigint"?new Date(Number(s.applied_at)):new Date(s.applied_at);return {identifier:s.identifier,appliedAt:o}})}isMigrationApplied(e,n,a){return a.columns.identifier==="name"?n.has(e.name):n.has(e.hash)||n.has(e.name)}async recordMigration(e,n,a,r){let{identifier:t,timestamp:i,timestampType:s}=r.columns,o=t==="name"?a.name:a.hash,l=s==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${n}"."${r.tableName}" ("${t}", "${i}") VALUES ($1, $2)`,[o,l]);}createSkippedSyncResult(e){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,markedMigrations:[],removedOrphans:[],error:"Skipped due to abort",durationMs:0}}createErrorSyncResult(e,n){return {tenantId:e,schemaName:this.deps.schemaNameTemplate(e),success:false,markedMigrations:[],removedOrphans:[],error:n.message,durationMs:0}}aggregateSyncResults(e){return {total:e.length,succeeded:e.filter(n=>n.success).length,failed:e.filter(n=>!n.success).length,details:e}}};function ue(m,e){return new x(m,e)}var T=class{constructor(e,n){this.config=e;this.deps=n;}async migrateTenant(e,n,a={}){let r=Date.now(),t=this.deps.schemaNameTemplate(e),i=[],s=await this.deps.createPool(t);try{await this.config.hooks?.beforeTenant?.(e);let o=await this.deps.getOrDetectFormat(s,t);await this.deps.ensureMigrationsTable(s,t,o);let l=n??await this.deps.loadMigrations(),c=await this.getAppliedMigrations(s,t,o),u=new Set(c.map(h=>h.identifier)),g=l.filter(h=>!this.isMigrationApplied(h,u,o));if(a.dryRun)return {tenantId:e,schemaName:t,success:!0,appliedMigrations:g.map(h=>h.name),durationMs:Date.now()-r,format:o.format};for(let h of g){let y=Date.now();a.onProgress?.(e,"migrating",h.name),await this.config.hooks?.beforeMigration?.(e,h.name),await this.applyMigration(s,t,h,o),await this.config.hooks?.afterMigration?.(e,h.name,Date.now()-y),i.push(h.name);}let p={tenantId:e,schemaName:t,success:!0,appliedMigrations:i,durationMs:Date.now()-r,format:o.format};return await this.config.hooks?.afterTenant?.(e,p),p}catch(o){let l={tenantId:e,schemaName:t,success:false,appliedMigrations:i,error:o.message,durationMs:Date.now()-r};return await this.config.hooks?.afterTenant?.(e,l),l}finally{await s.end();}}async markAsApplied(e,n={}){let a=Date.now(),r=this.deps.schemaNameTemplate(e),t=[],i=await this.deps.createPool(r);try{await this.config.hooks?.beforeTenant?.(e);let s=await this.deps.getOrDetectFormat(i,r);await this.deps.ensureMigrationsTable(i,r,s);let o=await this.deps.loadMigrations(),l=await this.getAppliedMigrations(i,r,s),c=new Set(l.map(p=>p.identifier)),u=o.filter(p=>!this.isMigrationApplied(p,c,s));for(let p of u){let h=Date.now();n.onProgress?.(e,"migrating",p.name),await this.config.hooks?.beforeMigration?.(e,p.name),await this.recordMigration(i,r,p,s),await this.config.hooks?.afterMigration?.(e,p.name,Date.now()-h),t.push(p.name);}let g={tenantId:e,schemaName:r,success:!0,appliedMigrations:t,durationMs:Date.now()-a,format:s.format};return await this.config.hooks?.afterTenant?.(e,g),g}catch(s){let o={tenantId:e,schemaName:r,success:false,appliedMigrations:t,error:s.message,durationMs:Date.now()-a};return await this.config.hooks?.afterTenant?.(e,o),o}finally{await i.end();}}async getTenantStatus(e,n){let a=this.deps.schemaNameTemplate(e),r=await this.deps.createPool(a);try{let t=n??await this.deps.loadMigrations();if(!await this.deps.migrationsTableExists(r,a))return {tenantId:e,schemaName:a,appliedCount:0,pendingCount:t.length,pendingMigrations:t.map(u=>u.name),status:t.length>0?"behind":"ok",format:null};let s=await this.deps.getOrDetectFormat(r,a),o=await this.getAppliedMigrations(r,a,s),l=new Set(o.map(u=>u.identifier)),c=t.filter(u=>!this.isMigrationApplied(u,l,s));return {tenantId:e,schemaName:a,appliedCount:o.length,pendingCount:c.length,pendingMigrations:c.map(u=>u.name),status:c.length>0?"behind":"ok",format:s.format}}catch(t){return {tenantId:e,schemaName:a,appliedCount:0,pendingCount:0,pendingMigrations:[],status:"error",error:t.message,format:null}}finally{await r.end();}}async executeMigration(e,n,a,r,t){t?.markOnly?(t.onProgress?.("recording"),await this.recordMigration(e,n,a,r)):(t?.onProgress?.("applying"),await this.applyMigration(e,n,a,r));}async executeMigrations(e,n,a,r,t){let i=[];for(let s of a)await this.executeMigration(e,n,s,r,t),i.push(s.name);return i}async recordMigration(e,n,a,r){let{identifier:t,timestamp:i,timestampType:s}=r.columns,o=t==="name"?a.name:a.hash,l=s==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${n}"."${r.tableName}" ("${t}", "${i}") VALUES ($1, $2)`,[o,l]);}async getAppliedMigrations(e,n,a){let r=a.columns.identifier,t=a.columns.timestamp;return (await e.query(`SELECT id, "${r}" as identifier, "${t}" as applied_at
|
|
65
|
+
FROM "${n}"."${a.tableName}"
|
|
66
|
+
ORDER BY id`)).rows.map(s=>{let o=a.columns.timestampType==="bigint"?new Date(Number(s.applied_at)):new Date(s.applied_at);return {identifier:s.identifier,...a.columns.identifier==="name"?{name:s.identifier}:{hash:s.identifier},appliedAt:o}})}async getPendingMigrations(e,n,a,r){let t=await this.getAppliedMigrations(e,n,r),i=new Set(t.map(s=>s.identifier));return a.filter(s=>!this.isMigrationApplied(s,i,r))}isMigrationApplied(e,n,a){return a.columns.identifier==="name"?n.has(e.name):n.has(e.hash)||n.has(e.name)}async applyMigration(e,n,a,r){let t=await e.connect();try{await t.query("BEGIN"),await t.query(a.sql);let{identifier:i,timestamp:s,timestampType:o}=r.columns,l=i==="name"?a.name:a.hash,c=o==="bigint"?Date.now():new Date;await t.query(`INSERT INTO "${n}"."${r.tableName}" ("${i}", "${s}") VALUES ($1, $2)`,[l,c]),await t.query("COMMIT");}catch(i){throw await t.query("ROLLBACK"),i}finally{t.release();}}};function U(m,e){return new T(m,e)}var M=class{constructor(e,n,a){this.config=e;this.executor=n;this.loadMigrations=a;}async migrateAll(e={}){let{concurrency:n=10,onProgress:a,onError:r,dryRun:t=false}=e,i=await this.config.tenantDiscovery(),s=await this.loadMigrations(),o=[],l=false;for(let c=0;c<i.length&&!l;c+=n){let u=i.slice(c,c+n),g=await Promise.all(u.map(async p=>{if(l)return this.createSkippedResult(p);try{a?.(p,"starting");let h=await this.executor.migrateTenant(p,s,{dryRun:t,onProgress:a});return a?.(p,h.success?"completed":"failed"),h}catch(h){return a?.(p,"failed"),r?.(p,h)==="abort"&&(l=true),this.createErrorResult(p,h)}}));o.push(...g);}if(l){let c=i.slice(o.length);for(let u of c)o.push(this.createSkippedResult(u));}return this.aggregateResults(o)}async migrateTenants(e,n={}){let a=await this.loadMigrations(),r=[],{concurrency:t=10,onProgress:i,onError:s,dryRun:o=false}=n;for(let l=0;l<e.length;l+=t){let c=e.slice(l,l+t),u=await Promise.all(c.map(async g=>{try{i?.(g,"starting");let p=await this.executor.migrateTenant(g,a,{dryRun:o,onProgress:i});return i?.(g,p.success?"completed":"failed"),p}catch(p){return i?.(g,"failed"),s?.(g,p),this.createErrorResult(g,p)}}));r.push(...u);}return this.aggregateResults(r)}async markAllAsApplied(e={}){let{concurrency:n=10,onProgress:a,onError:r}=e,t=await this.config.tenantDiscovery(),i=[],s=false;for(let o=0;o<t.length&&!s;o+=n){let l=t.slice(o,o+n),c=await Promise.all(l.map(async u=>{if(s)return this.createSkippedResult(u);try{a?.(u,"starting");let g=await this.executor.markAsApplied(u,{onProgress:a});return a?.(u,g.success?"completed":"failed"),g}catch(g){return a?.(u,"failed"),r?.(u,g)==="abort"&&(s=true),this.createErrorResult(u,g)}}));i.push(...c);}if(s){let o=t.slice(i.length);for(let l of o)i.push(this.createSkippedResult(l));}return this.aggregateResults(i)}async getStatus(){let e=await this.config.tenantDiscovery(),n=await this.loadMigrations(),a=[];for(let r of e)a.push(await this.executor.getTenantStatus(r,n));return a}createSkippedResult(e){return {tenantId:e,schemaName:"",success:false,appliedMigrations:[],error:"Skipped due to abort",durationMs:0}}createErrorResult(e,n){return {tenantId:e,schemaName:"",success:false,appliedMigrations:[],error:n.message,durationMs:0}}aggregateResults(e){return {total:e.length,succeeded:e.filter(n=>n.success).length,failed:e.filter(n=>!n.success&&n.error!=="Skipped due to abort").length,skipped:e.filter(n=>n.error==="Skipped due to abort").length,details:e}}};function H(m,e,n){return new M(m,e,n)}async function Y(m,e,n=[]){let a=n.length>0?n.map((t,i)=>`$${i+2}`).join(", "):"''::text";return (await m.query(`SELECT table_name
|
|
67
|
+
FROM information_schema.tables
|
|
68
|
+
WHERE table_schema = $1
|
|
69
|
+
AND table_type = 'BASE TABLE'
|
|
70
|
+
AND table_name NOT IN (${a})
|
|
71
|
+
ORDER BY table_name`,[e,...n])).rows.map(t=>t.table_name)}async function ge(m,e,n){return (await m.query(`SELECT
|
|
72
|
+
column_name,
|
|
73
|
+
data_type,
|
|
74
|
+
udt_name,
|
|
75
|
+
is_nullable,
|
|
76
|
+
column_default,
|
|
77
|
+
character_maximum_length,
|
|
78
|
+
numeric_precision,
|
|
79
|
+
numeric_scale
|
|
80
|
+
FROM information_schema.columns
|
|
81
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
82
|
+
ORDER BY ordinal_position`,[e,n])).rows.map(r=>({columnName:r.column_name,dataType:r.data_type,udtName:r.udt_name,isNullable:r.is_nullable==="YES",columnDefault:r.column_default,characterMaximumLength:r.character_maximum_length,numericPrecision:r.numeric_precision,numericScale:r.numeric_scale}))}async function pe(m,e,n){let r=(await ge(m,e,n)).map(t=>{let i=t.udtName;t.dataType==="character varying"&&t.characterMaximumLength?i=`varchar(${t.characterMaximumLength})`:t.dataType==="character"&&t.characterMaximumLength?i=`char(${t.characterMaximumLength})`:t.dataType==="numeric"&&t.numericPrecision?i=`numeric(${t.numericPrecision}${t.numericScale?`, ${t.numericScale}`:""})`:t.dataType==="ARRAY"&&(i=t.udtName.replace(/^_/,"")+"[]");let s=`"${t.columnName}" ${i}`;if(t.isNullable||(s+=" NOT NULL"),t.columnDefault){let o=t.columnDefault.replace(new RegExp(`"?${e}"?\\.`,"g"),"");s+=` DEFAULT ${o}`;}return s});return `CREATE TABLE IF NOT EXISTS "${n}" (
|
|
83
|
+
${r.join(`,
|
|
84
|
+
`)}
|
|
85
|
+
)`}async function he(m,e,n,a){return (await m.query(`SELECT indexname, indexdef
|
|
86
|
+
FROM pg_indexes
|
|
87
|
+
WHERE schemaname = $1 AND tablename = $2
|
|
88
|
+
AND indexname NOT LIKE '%_pkey'`,[e,a])).rows.map(t=>t.indexdef.replace(new RegExp(`ON "${e}"\\."`,"g"),`ON "${n}"."`).replace(new RegExp(`"${e}"\\."`,"g"),`"${n}"."`))}async function de(m,e,n){let a=await m.query(`SELECT
|
|
89
|
+
tc.constraint_name,
|
|
90
|
+
kcu.column_name
|
|
91
|
+
FROM information_schema.table_constraints tc
|
|
92
|
+
JOIN information_schema.key_column_usage kcu
|
|
93
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
94
|
+
AND tc.table_schema = kcu.table_schema
|
|
95
|
+
WHERE tc.table_schema = $1
|
|
96
|
+
AND tc.table_name = $2
|
|
97
|
+
AND tc.constraint_type = 'PRIMARY KEY'
|
|
98
|
+
ORDER BY kcu.ordinal_position`,[e,n]);if(a.rows.length===0)return null;let r=a.rows.map(i=>`"${i.column_name}"`).join(", "),t=a.rows[0].constraint_name;return `ALTER TABLE "${n}" ADD CONSTRAINT "${t}" PRIMARY KEY (${r})`}async function fe(m,e,n,a){let r=await m.query(`SELECT
|
|
99
|
+
tc.constraint_name,
|
|
100
|
+
kcu.column_name,
|
|
101
|
+
ccu.table_name as foreign_table_name,
|
|
102
|
+
ccu.column_name as foreign_column_name,
|
|
103
|
+
rc.update_rule,
|
|
104
|
+
rc.delete_rule
|
|
105
|
+
FROM information_schema.table_constraints tc
|
|
106
|
+
JOIN information_schema.key_column_usage kcu
|
|
107
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
108
|
+
AND tc.table_schema = kcu.table_schema
|
|
109
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
110
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
111
|
+
AND tc.table_schema = ccu.table_schema
|
|
112
|
+
JOIN information_schema.referential_constraints rc
|
|
113
|
+
ON tc.constraint_name = rc.constraint_name
|
|
114
|
+
AND tc.table_schema = rc.constraint_schema
|
|
115
|
+
WHERE tc.table_schema = $1
|
|
116
|
+
AND tc.table_name = $2
|
|
117
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
118
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,a]),t=new Map;for(let i of r.rows){let s=t.get(i.constraint_name);s?(s.columns.push(i.column_name),s.foreignColumns.push(i.foreign_column_name)):t.set(i.constraint_name,{columns:[i.column_name],foreignTable:i.foreign_table_name,foreignColumns:[i.foreign_column_name],updateRule:i.update_rule,deleteRule:i.delete_rule});}return Array.from(t.entries()).map(([i,s])=>{let o=s.columns.map(u=>`"${u}"`).join(", "),l=s.foreignColumns.map(u=>`"${u}"`).join(", "),c=`ALTER TABLE "${n}"."${a}" `;return c+=`ADD CONSTRAINT "${i}" FOREIGN KEY (${o}) `,c+=`REFERENCES "${n}"."${s.foreignTable}" (${l})`,s.updateRule!=="NO ACTION"&&(c+=` ON UPDATE ${s.updateRule}`),s.deleteRule!=="NO ACTION"&&(c+=` ON DELETE ${s.deleteRule}`),c})}async function Se(m,e,n){let a=await m.query(`SELECT
|
|
119
|
+
tc.constraint_name,
|
|
120
|
+
kcu.column_name
|
|
121
|
+
FROM information_schema.table_constraints tc
|
|
122
|
+
JOIN information_schema.key_column_usage kcu
|
|
123
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
124
|
+
AND tc.table_schema = kcu.table_schema
|
|
125
|
+
WHERE tc.table_schema = $1
|
|
126
|
+
AND tc.table_name = $2
|
|
127
|
+
AND tc.constraint_type = 'UNIQUE'
|
|
128
|
+
ORDER BY tc.constraint_name, kcu.ordinal_position`,[e,n]),r=new Map;for(let t of a.rows){let i=r.get(t.constraint_name);i?i.push(t.column_name):r.set(t.constraint_name,[t.column_name]);}return Array.from(r.entries()).map(([t,i])=>{let s=i.map(o=>`"${o}"`).join(", ");return `ALTER TABLE "${n}" ADD CONSTRAINT "${t}" UNIQUE (${s})`})}async function ye(m,e,n){return (await m.query(`SELECT
|
|
129
|
+
tc.constraint_name,
|
|
130
|
+
cc.check_clause
|
|
131
|
+
FROM information_schema.table_constraints tc
|
|
132
|
+
JOIN information_schema.check_constraints cc
|
|
133
|
+
ON tc.constraint_name = cc.constraint_name
|
|
134
|
+
AND tc.constraint_schema = cc.constraint_schema
|
|
135
|
+
WHERE tc.table_schema = $1
|
|
136
|
+
AND tc.table_name = $2
|
|
137
|
+
AND tc.constraint_type = 'CHECK'
|
|
138
|
+
AND tc.constraint_name NOT LIKE '%_not_null'`,[e,n])).rows.map(r=>`ALTER TABLE "${n}" ADD CONSTRAINT "${r.constraint_name}" CHECK (${r.check_clause})`)}async function Te(m,e,n){let a=await m.query(`SELECT count(*) FROM "${e}"."${n}"`);return parseInt(a.rows[0].count,10)}async function W(m,e,n,a){let[r,t,i,s,o,l,c]=await Promise.all([pe(m,e,a),he(m,e,n,a),de(m,e,a),Se(m,e,a),ye(m,e,a),fe(m,e,n,a),Te(m,e,a)]);return {name:a,createDdl:r,indexDdls:t,constraintDdls:[...i?[i]:[],...s,...o,...l],rowCount:c}}async function Me(m,e,n){return (await m.query(`SELECT column_name
|
|
139
|
+
FROM information_schema.columns
|
|
140
|
+
WHERE table_schema = $1 AND table_name = $2
|
|
141
|
+
ORDER BY ordinal_position`,[e,n])).rows.map(r=>r.column_name)}function _e(m){return m===null?"NULL":typeof m=="string"?`'${m.replace(/'/g,"''")}'`:typeof m=="boolean"?m?"TRUE":"FALSE":String(m)}async function Ee(m,e,n,a,r){let t=await Me(m,e,a);if(t.length===0)return 0;let i=r?.[a]??{},s=t.map(u=>{if(u in i){let g=i[u];return `${_e(g)} as "${u}"`}return `"${u}"`}),o=t.map(u=>`"${u}"`).join(", "),l=s.join(", ");return (await m.query(`INSERT INTO "${n}"."${a}" (${o})
|
|
142
|
+
SELECT ${l}
|
|
143
|
+
FROM "${e}"."${a}"`)).rowCount??0}async function be(m,e,n){let a=await m.query(`SELECT DISTINCT
|
|
144
|
+
tc.table_name,
|
|
145
|
+
ccu.table_name as foreign_table_name
|
|
146
|
+
FROM information_schema.table_constraints tc
|
|
147
|
+
JOIN information_schema.constraint_column_usage ccu
|
|
148
|
+
ON tc.constraint_name = ccu.constraint_name
|
|
149
|
+
AND tc.table_schema = ccu.table_schema
|
|
150
|
+
WHERE tc.table_schema = $1
|
|
151
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
152
|
+
AND tc.table_name != ccu.table_name`,[e]),r=new Map,t=new Set(n);for(let c of n)r.set(c,new Set);for(let c of a.rows)t.has(c.table_name)&&t.has(c.foreign_table_name)&&r.get(c.table_name).add(c.foreign_table_name);let i=[],s=new Map,o=[];for(let c of n)s.set(c,0);for(let[c,u]of r)for(let g of u)s.set(g,(s.get(g)??0)+1);for(let[c,u]of s)u===0&&o.push(c);for(;o.length>0;){let c=o.shift();i.push(c);for(let[u,g]of r)if(g.has(c)){g.delete(c);let p=(s.get(u)??0)-1;s.set(u,p),p===0&&o.push(u);}}let l=n.filter(c=>!i.includes(c));return [...i,...l]}async function K(m,e,n,a,r,t){let i=0,s=await be(m,e,a);await m.query("SET session_replication_role = replica");try{for(let o=0;o<s.length;o++){let l=s[o];t?.("copying_data",{table:l,progress:o+1,total:s.length});let c=await Ee(m,e,n,l,r);i+=c;}}finally{await m.query("SET session_replication_role = DEFAULT");}return i}var we="__drizzle_migrations",_=class{constructor(e,n){this.deps=n;this.migrationsTable=e.migrationsTable??we;}migrationsTable;async cloneTenant(e,n,a={}){let r=Date.now(),{includeData:t=false,anonymize:i,excludeTables:s=[],onProgress:o}=a,l=this.deps.schemaNameTemplate(e),c=this.deps.schemaNameTemplate(n),u=[this.migrationsTable,...s],g=null,p=null;try{if(o?.("starting"),!await this.deps.schemaExists(e))return this.createErrorResult(e,n,c,`Source tenant "${e}" does not exist`,r);if(await this.deps.schemaExists(n))return this.createErrorResult(e,n,c,`Target tenant "${n}" already exists`,r);o?.("introspecting"),g=await this.deps.createPool(l);let d=await Y(g,l,u);if(d.length===0)return o?.("creating_schema"),await this.deps.createSchema(n),o?.("completed"),{sourceTenant:e,targetTenant:n,targetSchema:c,success:!0,tables:[],durationMs:Date.now()-r};let f=await Promise.all(d.map(S=>W(g,l,c,S)));await g.end(),g=null,o?.("creating_schema"),await this.deps.createSchema(n),p=await this.deps.createRootPool(),o?.("creating_tables");for(let S of f)await p.query(`SET search_path TO "${c}"; ${S.createDdl}`);o?.("creating_constraints");for(let S of f)for(let C of S.constraintDdls.filter(b=>!b.includes("FOREIGN KEY")))try{await p.query(`SET search_path TO "${c}"; ${C}`);}catch{}o?.("creating_indexes");for(let S of f)for(let C of S.indexDdls)try{await p.query(C);}catch{}let P=0;t&&(o?.("copying_data"),P=await K(p,l,c,d,i?.enabled?i.rules:void 0,o));for(let S of f)for(let C of S.constraintDdls.filter(b=>b.includes("FOREIGN KEY")))try{await p.query(C);}catch{}o?.("completed");let $={sourceTenant:e,targetTenant:n,targetSchema:c,success:!0,tables:d,durationMs:Date.now()-r};return t&&($.rowsCopied=P),$}catch(h){return a.onError?.(h),o?.("failed"),this.createErrorResult(e,n,c,h.message,r)}finally{g&&await g.end().catch(()=>{}),p&&await p.end().catch(()=>{});}}createErrorResult(e,n,a,r,t){return {sourceTenant:e,targetTenant:n,targetSchema:a,success:false,error:r,tables:[],durationMs:Date.now()-t}}};function V(m,e){return new _(m,e)}var De="public",E=class{constructor(e,n){this.config=e;this.deps=n;this.schemaName=e.schemaName??De;}schemaName;async migrate(e={}){let n=Date.now(),a=[],r=await this.deps.createPool();try{e.onProgress?.("starting"),await this.config.hooks?.beforeMigration?.();let t=await this.deps.getOrDetectFormat(r,this.schemaName);await this.deps.ensureMigrationsTable(r,this.schemaName,t);let i=await this.deps.loadMigrations(),s=await this.getAppliedMigrations(r,t),o=new Set(s.map(c=>c.identifier)),l=i.filter(c=>!this.isMigrationApplied(c,o,t));if(e.dryRun)return {schemaName:this.schemaName,success:!0,appliedMigrations:l.map(c=>c.name),durationMs:Date.now()-n,format:t.format};for(let c of l){let u=Date.now();e.onProgress?.("migrating",c.name),await this.applyMigration(r,c,t),await this.config.hooks?.afterMigration?.(c.name,Date.now()-u),a.push(c.name);}return e.onProgress?.("completed"),{schemaName:this.schemaName,success:!0,appliedMigrations:a,durationMs:Date.now()-n,format:t.format}}catch(t){return e.onProgress?.("failed"),{schemaName:this.schemaName,success:false,appliedMigrations:a,error:t.message,durationMs:Date.now()-n}}finally{await r.end();}}async markAsApplied(e={}){let n=Date.now(),a=[],r=await this.deps.createPool();try{e.onProgress?.("starting");let t=await this.deps.getOrDetectFormat(r,this.schemaName);await this.deps.ensureMigrationsTable(r,this.schemaName,t);let i=await this.deps.loadMigrations(),s=await this.getAppliedMigrations(r,t),o=new Set(s.map(c=>c.identifier)),l=i.filter(c=>!this.isMigrationApplied(c,o,t));for(let c of l)e.onProgress?.("migrating",c.name),await this.recordMigration(r,c,t),a.push(c.name);return e.onProgress?.("completed"),{schemaName:this.schemaName,success:!0,appliedMigrations:a,durationMs:Date.now()-n,format:t.format}}catch(t){return e.onProgress?.("failed"),{schemaName:this.schemaName,success:false,appliedMigrations:a,error:t.message,durationMs:Date.now()-n}}finally{await r.end();}}async getStatus(){let e=await this.deps.createPool();try{let n=await this.deps.loadMigrations();if(!await this.deps.migrationsTableExists(e,this.schemaName))return {schemaName:this.schemaName,appliedCount:0,pendingCount:n.length,pendingMigrations:n.map(o=>o.name),status:n.length>0?"behind":"ok",format:null};let r=await this.deps.getOrDetectFormat(e,this.schemaName),t=await this.getAppliedMigrations(e,r),i=new Set(t.map(o=>o.identifier)),s=n.filter(o=>!this.isMigrationApplied(o,i,r));return {schemaName:this.schemaName,appliedCount:t.length,pendingCount:s.length,pendingMigrations:s.map(o=>o.name),status:s.length>0?"behind":"ok",format:r.format}}catch(n){return {schemaName:this.schemaName,appliedCount:0,pendingCount:0,pendingMigrations:[],status:"error",error:n.message,format:null}}finally{await e.end();}}async getAppliedMigrations(e,n){let a=n.columns.identifier,r=n.columns.timestamp;return (await e.query(`SELECT id, "${a}" as identifier, "${r}" as applied_at
|
|
153
|
+
FROM "${this.schemaName}"."${n.tableName}"
|
|
154
|
+
ORDER BY id`)).rows.map(i=>{let s=n.columns.timestampType==="bigint"?new Date(Number(i.applied_at)):new Date(i.applied_at);return {identifier:i.identifier,...n.columns.identifier==="name"?{name:i.identifier}:{hash:i.identifier},appliedAt:s}})}isMigrationApplied(e,n,a){return a.columns.identifier==="name"?n.has(e.name):n.has(e.hash)||n.has(e.name)}async applyMigration(e,n,a){let r=await e.connect();try{await r.query("BEGIN"),await r.query(n.sql);let{identifier:t,timestamp:i,timestampType:s}=a.columns,o=t==="name"?n.name:n.hash,l=s==="bigint"?Date.now():new Date;await r.query(`INSERT INTO "${this.schemaName}"."${a.tableName}" ("${t}", "${i}") VALUES ($1, $2)`,[o,l]),await r.query("COMMIT");}catch(t){throw await r.query("ROLLBACK"),t}finally{r.release();}}async recordMigration(e,n,a){let{identifier:r,timestamp:t,timestampType:i}=a.columns,s=r==="name"?n.name:n.hash,o=i==="bigint"?Date.now():new Date;await e.query(`INSERT INTO "${this.schemaName}"."${a.tableName}" ("${r}", "${t}") VALUES ($1, $2)`,[s,o]);}};function G(m,e){return new E(m,e)}var xe="__drizzle_migrations",te="__drizzle_shared_migrations",F=class{constructor(e,n){this.migratorConfig=n;if(this.migrationsTable=n.migrationsTable??xe,this.schemaManager=new D(e,this.migrationsTable),this.driftDetector=new O(e,this.schemaManager,{migrationsTable:this.migrationsTable,tenantDiscovery:n.tenantDiscovery}),this.seeder=new R({tenantDiscovery:n.tenantDiscovery},{createPool:this.schemaManager.createPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,tenantSchema:e.schemas.tenant}),this.syncManager=new x({tenantDiscovery:n.tenantDiscovery,migrationsFolder:n.migrationsFolder,migrationsTable:this.migrationsTable},{createPool:this.schemaManager.createPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,migrationsTableExists:this.schemaManager.migrationsTableExists.bind(this.schemaManager),ensureMigrationsTable:this.schemaManager.ensureMigrationsTable.bind(this.schemaManager),getOrDetectFormat:this.getOrDetectFormat.bind(this),loadMigrations:this.loadMigrations.bind(this)}),this.migrationExecutor=new T({hooks:n.hooks},{createPool:this.schemaManager.createPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,migrationsTableExists:this.schemaManager.migrationsTableExists.bind(this.schemaManager),ensureMigrationsTable:this.schemaManager.ensureMigrationsTable.bind(this.schemaManager),getOrDetectFormat:this.getOrDetectFormat.bind(this),loadMigrations:this.loadMigrations.bind(this)}),this.batchExecutor=new M({tenantDiscovery:n.tenantDiscovery},this.migrationExecutor,this.loadMigrations.bind(this)),this.cloner=new _({migrationsTable:this.migrationsTable},{createPool:this.schemaManager.createPool.bind(this.schemaManager),createRootPool:this.schemaManager.createRootPool.bind(this.schemaManager),schemaNameTemplate:e.isolation.schemaNameTemplate,schemaExists:this.schemaManager.schemaExists.bind(this.schemaManager),createSchema:this.schemaManager.createSchema.bind(this.schemaManager)}),n.sharedMigrationsFolder&&existsSync(n.sharedMigrationsFolder)){let a=n.sharedMigrationsTable??te,r=n.sharedHooks,t={schemaName:"public",migrationsTable:a};(r?.beforeMigration||r?.afterApply)&&(t.hooks={},r.beforeMigration&&(t.hooks.beforeMigration=r.beforeMigration),r.afterApply&&(t.hooks.afterMigration=r.afterApply)),this.sharedMigrationExecutor=new E(t,{createPool:this.schemaManager.createRootPool.bind(this.schemaManager),migrationsTableExists:this.schemaManager.migrationsTableExists.bind(this.schemaManager),ensureMigrationsTable:this.schemaManager.ensureMigrationsTable.bind(this.schemaManager),getOrDetectFormat:this.getOrDetectSharedFormat.bind(this),loadMigrations:this.loadSharedMigrations.bind(this)});}else this.sharedMigrationExecutor=null;e.schemas.shared?this.sharedSeeder=new A({schemaName:"public"},{createPool:this.schemaManager.createRootPool.bind(this.schemaManager),sharedSchema:e.schemas.shared}):this.sharedSeeder=null;}migrationsTable;schemaManager;driftDetector;seeder;syncManager;migrationExecutor;batchExecutor;cloner;sharedMigrationExecutor;sharedSeeder;async migrateAll(e={}){return this.batchExecutor.migrateAll(e)}async migrateTenant(e,n,a={}){return this.migrationExecutor.migrateTenant(e,n,a)}async migrateTenants(e,n={}){return this.batchExecutor.migrateTenants(e,n)}async getStatus(){return this.batchExecutor.getStatus()}async getTenantStatus(e,n){return this.migrationExecutor.getTenantStatus(e,n)}async createTenant(e,n={}){let{migrate:a=true}=n;await this.schemaManager.createSchema(e),a&&await this.migrateTenant(e);}async dropTenant(e,n={}){await this.schemaManager.dropSchema(e,n);}async tenantExists(e){return this.schemaManager.schemaExists(e)}async cloneTenant(e,n,a={}){return this.cloner.cloneTenant(e,n,a)}async markAsApplied(e,n={}){return this.migrationExecutor.markAsApplied(e,n)}async markAllAsApplied(e={}){return this.batchExecutor.markAllAsApplied(e)}async getSyncStatus(){return this.syncManager.getSyncStatus()}async getTenantSyncStatus(e,n){return this.syncManager.getTenantSyncStatus(e,n)}async markMissing(e){return this.syncManager.markMissing(e)}async markAllMissing(e={}){return this.syncManager.markAllMissing(e)}async cleanOrphans(e){return this.syncManager.cleanOrphans(e)}async cleanAllOrphans(e={}){return this.syncManager.cleanAllOrphans(e)}async seedTenant(e,n){return this.seeder.seedTenant(e,n)}async seedAll(e,n={}){return this.seeder.seedAll(e,n)}async seedTenants(e,n,a={}){return this.seeder.seedTenants(e,n,a)}hasSharedSeeding(){return this.sharedSeeder!==null}async seedShared(e){return this.sharedSeeder?this.sharedSeeder.seed(e):{schemaName:"public",success:false,error:"Shared schema not configured. Set schemas.shared in tenant config.",durationMs:0}}async seedAllWithShared(e,n,a={}){let r=await this.seedShared(e),t=await this.seedAll(n,a);return {shared:r,tenants:t}}async loadMigrations(){let e=await readdir(this.migratorConfig.migrationsFolder),n=[];for(let a of e){if(!a.endsWith(".sql"))continue;let r=join(this.migratorConfig.migrationsFolder,a),t=await readFile(r,"utf-8"),i=a.match(/^(\d+)_/),s=i?.[1]?parseInt(i[1],10):0,o=createHash("sha256").update(t).digest("hex");n.push({name:basename(a,".sql"),path:r,sql:t,timestamp:s,hash:o});}return n.sort((a,r)=>a.timestamp-r.timestamp)}async getOrDetectFormat(e,n){let a=this.migratorConfig.tableFormat??"auto";if(a!=="auto")return w(a,this.migrationsTable);let r=await N(e,n,this.migrationsTable);if(r)return r;let t=this.migratorConfig.defaultFormat??"name";return w(t,this.migrationsTable)}async loadSharedMigrations(){if(!this.migratorConfig.sharedMigrationsFolder)return [];let e=await readdir(this.migratorConfig.sharedMigrationsFolder),n=[];for(let a of e){if(!a.endsWith(".sql"))continue;let r=join(this.migratorConfig.sharedMigrationsFolder,a),t=await readFile(r,"utf-8"),i=a.match(/^(\d+)_/),s=i?.[1]?parseInt(i[1],10):0,o=createHash("sha256").update(t).digest("hex");n.push({name:basename(a,".sql"),path:r,sql:t,timestamp:s,hash:o});}return n.sort((a,r)=>a.timestamp-r.timestamp)}async getOrDetectSharedFormat(e,n){let a=this.migratorConfig.sharedMigrationsTable??te,r=this.migratorConfig.tableFormat??"auto";if(r!=="auto")return w(r,a);let t=await N(e,n,a);if(t)return t;let i=this.migratorConfig.defaultFormat??"name";return w(i,a)}hasSharedMigrations(){return this.sharedMigrationExecutor!==null}async migrateShared(e={}){return this.sharedMigrationExecutor?this.sharedMigrationExecutor.migrate(e):{schemaName:"public",success:false,appliedMigrations:[],error:"Shared migrations not configured. Set sharedMigrationsFolder in migrator config.",durationMs:0}}async getSharedStatus(){return this.sharedMigrationExecutor?this.sharedMigrationExecutor.getStatus():{schemaName:"public",appliedCount:0,pendingCount:0,pendingMigrations:[],status:"error",error:"Shared migrations not configured. Set sharedMigrationsFolder in migrator config.",format:null}}async markSharedAsApplied(e={}){return this.sharedMigrationExecutor?this.sharedMigrationExecutor.markAsApplied(e):{schemaName:"public",success:false,appliedMigrations:[],error:"Shared migrations not configured. Set sharedMigrationsFolder in migrator config.",durationMs:0}}async migrateAllWithShared(e={}){let{sharedOptions:n,...a}=e,r=await this.migrateShared(n??{}),t=await this.migrateAll(a);return {shared:r,tenants:t}}async getSchemaDrift(e={}){return this.driftDetector.detectDrift(e)}async getTenantSchemaDrift(e,n,a={}){return this.driftDetector.compareTenant(e,n,a)}async introspectTenantSchema(e,n={}){return this.driftDetector.introspectSchema(e,n)}};function Ce(m,e){return new F(m,e)}export{M as BatchExecutor,_ as Cloner,ne as DEFAULT_FORMAT,ae as DRIZZLE_KIT_FORMAT,T as MigrationExecutor,F as Migrator,D as SchemaManager,R as Seeder,E as SharedMigrationExecutor,x as SyncManager,H as createBatchExecutor,V as createCloner,U as createMigrationExecutor,Ce as createMigrator,ie as createSchemaManager,ce as createSeeder,G as createSharedMigrationExecutor,ue as createSyncManager,N as detectTableFormat,w as getFormatConfig};
|