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.
@@ -1,975 +1,154 @@
1
- import { readdir, readFile } from 'fs/promises';
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
- [schemaName, tableName]
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
- ${identifierCol},
793
- ${timestampCol}
9
+ ${s},
10
+ ${o}
794
11
  )
795
- `);
796
- }
797
- /**
798
- * Check if migrations table exists
799
- */
800
- async migrationsTableExists(pool, schemaName) {
801
- const result = await pool.query(
802
- `SELECT 1 FROM information_schema.tables
803
- WHERE table_schema = $1 AND table_name = $2`,
804
- [schemaName, this.migrationsTable]
805
- );
806
- return result.rowCount !== null && result.rowCount > 0;
807
- }
808
- /**
809
- * Get applied migrations for a schema
810
- */
811
- async getAppliedMigrations(pool, schemaName, format) {
812
- const identifierColumn = format.columns.identifier;
813
- const timestampColumn = format.columns.timestamp;
814
- const result = await pool.query(
815
- `SELECT id, "${identifierColumn}" as identifier, "${timestampColumn}" as applied_at
816
- FROM "${schemaName}"."${format.tableName}"
817
- ORDER BY id`
818
- );
819
- return result.rows.map((row) => {
820
- const appliedAt = format.columns.timestampType === "bigint" ? new Date(Number(row.applied_at)) : new Date(row.applied_at);
821
- return {
822
- id: row.id,
823
- identifier: row.identifier,
824
- // Set name or hash based on format
825
- ...format.columns.identifier === "name" ? { name: row.identifier } : { hash: row.identifier },
826
- appliedAt
827
- };
828
- });
829
- }
830
- /**
831
- * Check if a migration has been applied
832
- */
833
- isMigrationApplied(migration, appliedIdentifiers, format) {
834
- if (format.columns.identifier === "name") {
835
- return appliedIdentifiers.has(migration.name);
836
- }
837
- return appliedIdentifiers.has(migration.hash) || appliedIdentifiers.has(migration.name);
838
- }
839
- /**
840
- * Get or detect the format for a schema
841
- * Returns the configured format or auto-detects from existing table
842
- */
843
- async getOrDetectFormat(pool, schemaName) {
844
- const configuredFormat = this.migratorConfig.tableFormat ?? "auto";
845
- if (configuredFormat !== "auto") {
846
- return getFormatConfig(configuredFormat, this.migrationsTable);
847
- }
848
- const detected = await detectTableFormat(pool, schemaName, this.migrationsTable);
849
- if (detected) {
850
- return detected;
851
- }
852
- const defaultFormat = this.migratorConfig.defaultFormat ?? "name";
853
- return getFormatConfig(defaultFormat, this.migrationsTable);
854
- }
855
- /**
856
- * Apply a migration to a schema
857
- */
858
- async applyMigration(pool, schemaName, migration, format) {
859
- const client = await pool.connect();
860
- try {
861
- await client.query("BEGIN");
862
- await client.query(migration.sql);
863
- const { identifier, timestamp, timestampType } = format.columns;
864
- const identifierValue = identifier === "name" ? migration.name : migration.hash;
865
- const timestampValue = timestampType === "bigint" ? Date.now() : /* @__PURE__ */ new Date();
866
- await client.query(
867
- `INSERT INTO "${schemaName}"."${format.tableName}" ("${identifier}", "${timestamp}") VALUES ($1, $2)`,
868
- [identifierValue, timestampValue]
869
- );
870
- await client.query("COMMIT");
871
- } catch (error) {
872
- await client.query("ROLLBACK");
873
- throw error;
874
- } finally {
875
- client.release();
876
- }
877
- }
878
- /**
879
- * Record a migration as applied without executing SQL
880
- * Used by markAsApplied to sync tracking state
881
- */
882
- async recordMigration(pool, schemaName, migration, format) {
883
- const { identifier, timestamp, timestampType } = format.columns;
884
- const identifierValue = identifier === "name" ? migration.name : migration.hash;
885
- const timestampValue = timestampType === "bigint" ? Date.now() : /* @__PURE__ */ new Date();
886
- await pool.query(
887
- `INSERT INTO "${schemaName}"."${format.tableName}" ("${identifier}", "${timestamp}") VALUES ($1, $2)`,
888
- [identifierValue, timestampValue]
889
- );
890
- }
891
- /**
892
- * Create a skipped result
893
- */
894
- createSkippedResult(tenantId) {
895
- return {
896
- tenantId,
897
- schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),
898
- success: false,
899
- appliedMigrations: [],
900
- error: "Skipped due to abort",
901
- durationMs: 0
902
- };
903
- }
904
- /**
905
- * Create an error result
906
- */
907
- createErrorResult(tenantId, error) {
908
- return {
909
- tenantId,
910
- schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),
911
- success: false,
912
- appliedMigrations: [],
913
- error: error.message,
914
- durationMs: 0
915
- };
916
- }
917
- /**
918
- * Aggregate migration results
919
- */
920
- aggregateResults(results) {
921
- return {
922
- total: results.length,
923
- succeeded: results.filter((r) => r.success).length,
924
- failed: results.filter((r) => !r.success && r.error !== "Skipped due to abort").length,
925
- skipped: results.filter((r) => r.error === "Skipped due to abort").length,
926
- details: results
927
- };
928
- }
929
- /**
930
- * Create a skipped sync result
931
- */
932
- createSkippedSyncResult(tenantId) {
933
- return {
934
- tenantId,
935
- schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),
936
- success: false,
937
- markedMigrations: [],
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};