drizzle-multitenant 1.0.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.
@@ -0,0 +1,208 @@
1
+ import { C as Config } from '../types-DKVaTaIb.js';
2
+ import 'pg';
3
+ import 'drizzle-orm/node-postgres';
4
+
5
+ /**
6
+ * Migration file metadata
7
+ */
8
+ interface MigrationFile {
9
+ /** Migration file name */
10
+ name: string;
11
+ /** Full file path */
12
+ path: string;
13
+ /** SQL content */
14
+ sql: string;
15
+ /** Timestamp extracted from filename */
16
+ timestamp: number;
17
+ }
18
+ /**
19
+ * Migration status for a tenant
20
+ */
21
+ interface TenantMigrationStatus {
22
+ tenantId: string;
23
+ schemaName: string;
24
+ appliedCount: number;
25
+ pendingCount: number;
26
+ pendingMigrations: string[];
27
+ status: 'ok' | 'behind' | 'error';
28
+ error?: string;
29
+ }
30
+ /**
31
+ * Migration result for a single tenant
32
+ */
33
+ interface TenantMigrationResult {
34
+ tenantId: string;
35
+ schemaName: string;
36
+ success: boolean;
37
+ appliedMigrations: string[];
38
+ error?: string;
39
+ durationMs: number;
40
+ }
41
+ /**
42
+ * Aggregate migration results
43
+ */
44
+ interface MigrationResults {
45
+ total: number;
46
+ succeeded: number;
47
+ failed: number;
48
+ skipped: number;
49
+ details: TenantMigrationResult[];
50
+ }
51
+ /**
52
+ * Progress callback for migrations
53
+ */
54
+ type MigrationProgressCallback = (tenantId: string, status: 'starting' | 'migrating' | 'completed' | 'failed' | 'skipped', migrationName?: string) => void;
55
+ /**
56
+ * Error handler for migrations
57
+ */
58
+ type MigrationErrorHandler = (tenantId: string, error: Error) => 'continue' | 'abort';
59
+ /**
60
+ * Migration hooks
61
+ */
62
+ interface MigrationHooks {
63
+ /** Called before migrating a tenant */
64
+ beforeTenant?: (tenantId: string) => void | Promise<void>;
65
+ /** Called after migrating a tenant */
66
+ afterTenant?: (tenantId: string, result: TenantMigrationResult) => void | Promise<void>;
67
+ /** Called before applying a migration */
68
+ beforeMigration?: (tenantId: string, migrationName: string) => void | Promise<void>;
69
+ /** Called after applying a migration */
70
+ afterMigration?: (tenantId: string, migrationName: string, durationMs: number) => void | Promise<void>;
71
+ }
72
+ /**
73
+ * Migrator configuration
74
+ */
75
+ interface MigratorConfig {
76
+ /** Path to tenant migrations folder */
77
+ migrationsFolder: string;
78
+ /** Table name for tracking migrations */
79
+ migrationsTable?: string;
80
+ /** Function to discover tenant IDs */
81
+ tenantDiscovery: () => Promise<string[]>;
82
+ /** Migration hooks */
83
+ hooks?: MigrationHooks;
84
+ }
85
+ /**
86
+ * Migrate options
87
+ */
88
+ interface MigrateOptions {
89
+ /** Number of concurrent migrations */
90
+ concurrency?: number;
91
+ /** Progress callback */
92
+ onProgress?: MigrationProgressCallback;
93
+ /** Error handler */
94
+ onError?: MigrationErrorHandler;
95
+ /** Dry run mode */
96
+ dryRun?: boolean;
97
+ }
98
+ /**
99
+ * Tenant creation options
100
+ */
101
+ interface CreateTenantOptions {
102
+ /** Apply all migrations after creating schema */
103
+ migrate?: boolean;
104
+ }
105
+ /**
106
+ * Tenant drop options
107
+ */
108
+ interface DropTenantOptions {
109
+ /** Skip confirmation (force drop) */
110
+ force?: boolean;
111
+ /** Cascade drop */
112
+ cascade?: boolean;
113
+ }
114
+ /**
115
+ * Applied migration record
116
+ */
117
+ interface AppliedMigration {
118
+ id: number;
119
+ name: string;
120
+ appliedAt: Date;
121
+ }
122
+
123
+ /**
124
+ * Parallel migration engine for multi-tenant applications
125
+ */
126
+ declare class Migrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>> {
127
+ private readonly tenantConfig;
128
+ private readonly migratorConfig;
129
+ private readonly migrationsTable;
130
+ constructor(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig);
131
+ /**
132
+ * Migrate all tenants in parallel
133
+ */
134
+ migrateAll(options?: MigrateOptions): Promise<MigrationResults>;
135
+ /**
136
+ * Migrate a single tenant
137
+ */
138
+ migrateTenant(tenantId: string, migrations?: MigrationFile[], options?: {
139
+ dryRun?: boolean;
140
+ onProgress?: MigrateOptions['onProgress'];
141
+ }): Promise<TenantMigrationResult>;
142
+ /**
143
+ * Migrate specific tenants
144
+ */
145
+ migrateTenants(tenantIds: string[], options?: MigrateOptions): Promise<MigrationResults>;
146
+ /**
147
+ * Get migration status for all tenants
148
+ */
149
+ getStatus(): Promise<TenantMigrationStatus[]>;
150
+ /**
151
+ * Get migration status for a specific tenant
152
+ */
153
+ getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus>;
154
+ /**
155
+ * Create a new tenant schema and optionally apply migrations
156
+ */
157
+ createTenant(tenantId: string, options?: CreateTenantOptions): Promise<void>;
158
+ /**
159
+ * Drop a tenant schema
160
+ */
161
+ dropTenant(tenantId: string, options?: DropTenantOptions): Promise<void>;
162
+ /**
163
+ * Check if a tenant schema exists
164
+ */
165
+ tenantExists(tenantId: string): Promise<boolean>;
166
+ /**
167
+ * Load migration files from the migrations folder
168
+ */
169
+ private loadMigrations;
170
+ /**
171
+ * Create a pool for a specific schema
172
+ */
173
+ private createPool;
174
+ /**
175
+ * Ensure migrations table exists
176
+ */
177
+ private ensureMigrationsTable;
178
+ /**
179
+ * Check if migrations table exists
180
+ */
181
+ private migrationsTableExists;
182
+ /**
183
+ * Get applied migrations for a schema
184
+ */
185
+ private getAppliedMigrations;
186
+ /**
187
+ * Apply a migration to a schema
188
+ */
189
+ private applyMigration;
190
+ /**
191
+ * Create a skipped result
192
+ */
193
+ private createSkippedResult;
194
+ /**
195
+ * Create an error result
196
+ */
197
+ private createErrorResult;
198
+ /**
199
+ * Aggregate migration results
200
+ */
201
+ private aggregateResults;
202
+ }
203
+ /**
204
+ * Create a migrator instance
205
+ */
206
+ declare function createMigrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig): Migrator<TTenantSchema, TSharedSchema>;
207
+
208
+ export { type AppliedMigration, type CreateTenantOptions, type DropTenantOptions, type MigrateOptions, type MigrationErrorHandler, type MigrationFile, type MigrationHooks, type MigrationProgressCallback, type MigrationResults, Migrator, type MigratorConfig, type TenantMigrationResult, type TenantMigrationStatus, createMigrator };
@@ -0,0 +1,390 @@
1
+ import { readdir, readFile } from 'fs/promises';
2
+ import { join, basename } from 'path';
3
+ import { Pool } from 'pg';
4
+
5
+ // src/migrator/migrator.ts
6
+ var DEFAULT_MIGRATIONS_TABLE = "__drizzle_migrations";
7
+ var Migrator = class {
8
+ constructor(tenantConfig, migratorConfig) {
9
+ this.tenantConfig = tenantConfig;
10
+ this.migratorConfig = migratorConfig;
11
+ this.migrationsTable = migratorConfig.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE;
12
+ }
13
+ migrationsTable;
14
+ /**
15
+ * Migrate all tenants in parallel
16
+ */
17
+ async migrateAll(options = {}) {
18
+ const {
19
+ concurrency = 10,
20
+ onProgress,
21
+ onError,
22
+ dryRun = false
23
+ } = options;
24
+ const tenantIds = await this.migratorConfig.tenantDiscovery();
25
+ const migrations = await this.loadMigrations();
26
+ const results = [];
27
+ let aborted = false;
28
+ for (let i = 0; i < tenantIds.length && !aborted; i += concurrency) {
29
+ const batch = tenantIds.slice(i, i + concurrency);
30
+ const batchResults = await Promise.all(
31
+ batch.map(async (tenantId) => {
32
+ if (aborted) {
33
+ return this.createSkippedResult(tenantId);
34
+ }
35
+ try {
36
+ onProgress?.(tenantId, "starting");
37
+ const result = await this.migrateTenant(tenantId, migrations, { dryRun, onProgress });
38
+ onProgress?.(tenantId, result.success ? "completed" : "failed");
39
+ return result;
40
+ } catch (error) {
41
+ onProgress?.(tenantId, "failed");
42
+ const action = onError?.(tenantId, error);
43
+ if (action === "abort") {
44
+ aborted = true;
45
+ }
46
+ return this.createErrorResult(tenantId, error);
47
+ }
48
+ })
49
+ );
50
+ results.push(...batchResults);
51
+ }
52
+ if (aborted) {
53
+ const remaining = tenantIds.slice(results.length);
54
+ for (const tenantId of remaining) {
55
+ results.push(this.createSkippedResult(tenantId));
56
+ }
57
+ }
58
+ return this.aggregateResults(results);
59
+ }
60
+ /**
61
+ * Migrate a single tenant
62
+ */
63
+ async migrateTenant(tenantId, migrations, options = {}) {
64
+ const startTime = Date.now();
65
+ const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
66
+ const appliedMigrations = [];
67
+ const pool = await this.createPool(schemaName);
68
+ try {
69
+ await this.migratorConfig.hooks?.beforeTenant?.(tenantId);
70
+ await this.ensureMigrationsTable(pool, schemaName);
71
+ const allMigrations = migrations ?? await this.loadMigrations();
72
+ const applied = await this.getAppliedMigrations(pool, schemaName);
73
+ const appliedSet = new Set(applied.map((m) => m.name));
74
+ const pending = allMigrations.filter((m) => !appliedSet.has(m.name));
75
+ if (options.dryRun) {
76
+ return {
77
+ tenantId,
78
+ schemaName,
79
+ success: true,
80
+ appliedMigrations: pending.map((m) => m.name),
81
+ durationMs: Date.now() - startTime
82
+ };
83
+ }
84
+ for (const migration of pending) {
85
+ const migrationStart = Date.now();
86
+ options.onProgress?.(tenantId, "migrating", migration.name);
87
+ await this.migratorConfig.hooks?.beforeMigration?.(tenantId, migration.name);
88
+ await this.applyMigration(pool, schemaName, migration);
89
+ await this.migratorConfig.hooks?.afterMigration?.(
90
+ tenantId,
91
+ migration.name,
92
+ Date.now() - migrationStart
93
+ );
94
+ appliedMigrations.push(migration.name);
95
+ }
96
+ const result = {
97
+ tenantId,
98
+ schemaName,
99
+ success: true,
100
+ appliedMigrations,
101
+ durationMs: Date.now() - startTime
102
+ };
103
+ await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);
104
+ return result;
105
+ } catch (error) {
106
+ const result = {
107
+ tenantId,
108
+ schemaName,
109
+ success: false,
110
+ appliedMigrations,
111
+ error: error.message,
112
+ durationMs: Date.now() - startTime
113
+ };
114
+ await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);
115
+ return result;
116
+ } finally {
117
+ await pool.end();
118
+ }
119
+ }
120
+ /**
121
+ * Migrate specific tenants
122
+ */
123
+ async migrateTenants(tenantIds, options = {}) {
124
+ const migrations = await this.loadMigrations();
125
+ const results = [];
126
+ const { concurrency = 10, onProgress, onError } = options;
127
+ for (let i = 0; i < tenantIds.length; i += concurrency) {
128
+ const batch = tenantIds.slice(i, i + concurrency);
129
+ const batchResults = await Promise.all(
130
+ batch.map(async (tenantId) => {
131
+ try {
132
+ onProgress?.(tenantId, "starting");
133
+ const result = await this.migrateTenant(tenantId, migrations, { dryRun: options.dryRun ?? false, onProgress });
134
+ onProgress?.(tenantId, result.success ? "completed" : "failed");
135
+ return result;
136
+ } catch (error) {
137
+ onProgress?.(tenantId, "failed");
138
+ onError?.(tenantId, error);
139
+ return this.createErrorResult(tenantId, error);
140
+ }
141
+ })
142
+ );
143
+ results.push(...batchResults);
144
+ }
145
+ return this.aggregateResults(results);
146
+ }
147
+ /**
148
+ * Get migration status for all tenants
149
+ */
150
+ async getStatus() {
151
+ const tenantIds = await this.migratorConfig.tenantDiscovery();
152
+ const migrations = await this.loadMigrations();
153
+ const statuses = [];
154
+ for (const tenantId of tenantIds) {
155
+ statuses.push(await this.getTenantStatus(tenantId, migrations));
156
+ }
157
+ return statuses;
158
+ }
159
+ /**
160
+ * Get migration status for a specific tenant
161
+ */
162
+ async getTenantStatus(tenantId, migrations) {
163
+ const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
164
+ const pool = await this.createPool(schemaName);
165
+ try {
166
+ const allMigrations = migrations ?? await this.loadMigrations();
167
+ const tableExists = await this.migrationsTableExists(pool, schemaName);
168
+ if (!tableExists) {
169
+ return {
170
+ tenantId,
171
+ schemaName,
172
+ appliedCount: 0,
173
+ pendingCount: allMigrations.length,
174
+ pendingMigrations: allMigrations.map((m) => m.name),
175
+ status: allMigrations.length > 0 ? "behind" : "ok"
176
+ };
177
+ }
178
+ const applied = await this.getAppliedMigrations(pool, schemaName);
179
+ const appliedSet = new Set(applied.map((m) => m.name));
180
+ const pending = allMigrations.filter((m) => !appliedSet.has(m.name));
181
+ return {
182
+ tenantId,
183
+ schemaName,
184
+ appliedCount: applied.length,
185
+ pendingCount: pending.length,
186
+ pendingMigrations: pending.map((m) => m.name),
187
+ status: pending.length > 0 ? "behind" : "ok"
188
+ };
189
+ } catch (error) {
190
+ return {
191
+ tenantId,
192
+ schemaName,
193
+ appliedCount: 0,
194
+ pendingCount: 0,
195
+ pendingMigrations: [],
196
+ status: "error",
197
+ error: error.message
198
+ };
199
+ } finally {
200
+ await pool.end();
201
+ }
202
+ }
203
+ /**
204
+ * Create a new tenant schema and optionally apply migrations
205
+ */
206
+ async createTenant(tenantId, options = {}) {
207
+ const { migrate = true } = options;
208
+ const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
209
+ const pool = new Pool({
210
+ connectionString: this.tenantConfig.connection.url,
211
+ ...this.tenantConfig.connection.poolConfig
212
+ });
213
+ try {
214
+ await pool.query(`CREATE SCHEMA IF NOT EXISTS "${schemaName}"`);
215
+ if (migrate) {
216
+ await this.migrateTenant(tenantId);
217
+ }
218
+ } finally {
219
+ await pool.end();
220
+ }
221
+ }
222
+ /**
223
+ * Drop a tenant schema
224
+ */
225
+ async dropTenant(tenantId, options = {}) {
226
+ const { cascade = true } = options;
227
+ const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
228
+ const pool = new Pool({
229
+ connectionString: this.tenantConfig.connection.url,
230
+ ...this.tenantConfig.connection.poolConfig
231
+ });
232
+ try {
233
+ const cascadeSql = cascade ? "CASCADE" : "RESTRICT";
234
+ await pool.query(`DROP SCHEMA IF EXISTS "${schemaName}" ${cascadeSql}`);
235
+ } finally {
236
+ await pool.end();
237
+ }
238
+ }
239
+ /**
240
+ * Check if a tenant schema exists
241
+ */
242
+ async tenantExists(tenantId) {
243
+ const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);
244
+ const pool = new Pool({
245
+ connectionString: this.tenantConfig.connection.url,
246
+ ...this.tenantConfig.connection.poolConfig
247
+ });
248
+ try {
249
+ const result = await pool.query(
250
+ `SELECT 1 FROM information_schema.schemata WHERE schema_name = $1`,
251
+ [schemaName]
252
+ );
253
+ return result.rowCount !== null && result.rowCount > 0;
254
+ } finally {
255
+ await pool.end();
256
+ }
257
+ }
258
+ /**
259
+ * Load migration files from the migrations folder
260
+ */
261
+ async loadMigrations() {
262
+ const files = await readdir(this.migratorConfig.migrationsFolder);
263
+ const migrations = [];
264
+ for (const file of files) {
265
+ if (!file.endsWith(".sql")) continue;
266
+ const filePath = join(this.migratorConfig.migrationsFolder, file);
267
+ const content = await readFile(filePath, "utf-8");
268
+ const match = file.match(/^(\d+)_/);
269
+ const timestamp = match?.[1] ? parseInt(match[1], 10) : 0;
270
+ migrations.push({
271
+ name: basename(file, ".sql"),
272
+ path: filePath,
273
+ sql: content,
274
+ timestamp
275
+ });
276
+ }
277
+ return migrations.sort((a, b) => a.timestamp - b.timestamp);
278
+ }
279
+ /**
280
+ * Create a pool for a specific schema
281
+ */
282
+ async createPool(schemaName) {
283
+ return new Pool({
284
+ connectionString: this.tenantConfig.connection.url,
285
+ ...this.tenantConfig.connection.poolConfig,
286
+ options: `-c search_path="${schemaName}",public`
287
+ });
288
+ }
289
+ /**
290
+ * Ensure migrations table exists
291
+ */
292
+ async ensureMigrationsTable(pool, schemaName) {
293
+ await pool.query(`
294
+ CREATE TABLE IF NOT EXISTS "${schemaName}"."${this.migrationsTable}" (
295
+ id SERIAL PRIMARY KEY,
296
+ name VARCHAR(255) NOT NULL UNIQUE,
297
+ applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
298
+ )
299
+ `);
300
+ }
301
+ /**
302
+ * Check if migrations table exists
303
+ */
304
+ async migrationsTableExists(pool, schemaName) {
305
+ const result = await pool.query(
306
+ `SELECT 1 FROM information_schema.tables
307
+ WHERE table_schema = $1 AND table_name = $2`,
308
+ [schemaName, this.migrationsTable]
309
+ );
310
+ return result.rowCount !== null && result.rowCount > 0;
311
+ }
312
+ /**
313
+ * Get applied migrations for a schema
314
+ */
315
+ async getAppliedMigrations(pool, schemaName) {
316
+ const result = await pool.query(
317
+ `SELECT id, name, applied_at FROM "${schemaName}"."${this.migrationsTable}" ORDER BY id`
318
+ );
319
+ return result.rows.map((row) => ({
320
+ id: row.id,
321
+ name: row.name,
322
+ appliedAt: row.applied_at
323
+ }));
324
+ }
325
+ /**
326
+ * Apply a migration to a schema
327
+ */
328
+ async applyMigration(pool, schemaName, migration) {
329
+ const client = await pool.connect();
330
+ try {
331
+ await client.query("BEGIN");
332
+ await client.query(migration.sql);
333
+ await client.query(
334
+ `INSERT INTO "${schemaName}"."${this.migrationsTable}" (name) VALUES ($1)`,
335
+ [migration.name]
336
+ );
337
+ await client.query("COMMIT");
338
+ } catch (error) {
339
+ await client.query("ROLLBACK");
340
+ throw error;
341
+ } finally {
342
+ client.release();
343
+ }
344
+ }
345
+ /**
346
+ * Create a skipped result
347
+ */
348
+ createSkippedResult(tenantId) {
349
+ return {
350
+ tenantId,
351
+ schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),
352
+ success: false,
353
+ appliedMigrations: [],
354
+ error: "Skipped due to abort",
355
+ durationMs: 0
356
+ };
357
+ }
358
+ /**
359
+ * Create an error result
360
+ */
361
+ createErrorResult(tenantId, error) {
362
+ return {
363
+ tenantId,
364
+ schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),
365
+ success: false,
366
+ appliedMigrations: [],
367
+ error: error.message,
368
+ durationMs: 0
369
+ };
370
+ }
371
+ /**
372
+ * Aggregate migration results
373
+ */
374
+ aggregateResults(results) {
375
+ return {
376
+ total: results.length,
377
+ succeeded: results.filter((r) => r.success).length,
378
+ failed: results.filter((r) => !r.success && r.error !== "Skipped due to abort").length,
379
+ skipped: results.filter((r) => r.error === "Skipped due to abort").length,
380
+ details: results
381
+ };
382
+ }
383
+ };
384
+ function createMigrator(tenantConfig, migratorConfig) {
385
+ return new Migrator(tenantConfig, migratorConfig);
386
+ }
387
+
388
+ export { Migrator, createMigrator };
389
+ //# sourceMappingURL=index.js.map
390
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/migrator/migrator.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { readdir, readFile } from 'node:fs/promises';\nimport { join, basename } from 'node:path';\nimport { Pool } from 'pg';\nimport type { Config } from '../types.js';\nimport type {\n MigratorConfig,\n MigrationFile,\n MigrateOptions,\n TenantMigrationResult,\n MigrationResults,\n TenantMigrationStatus,\n AppliedMigration,\n CreateTenantOptions,\n DropTenantOptions,\n} from './types.js';\n\nconst DEFAULT_MIGRATIONS_TABLE = '__drizzle_migrations';\n\n/**\n * Parallel migration engine for multi-tenant applications\n */\nexport class Migrator<\n TTenantSchema extends Record<string, unknown>,\n TSharedSchema extends Record<string, unknown>,\n> {\n private readonly migrationsTable: string;\n\n constructor(\n private readonly tenantConfig: Config<TTenantSchema, TSharedSchema>,\n private readonly migratorConfig: MigratorConfig\n ) {\n this.migrationsTable = migratorConfig.migrationsTable ?? DEFAULT_MIGRATIONS_TABLE;\n }\n\n /**\n * Migrate all tenants in parallel\n */\n async migrateAll(options: MigrateOptions = {}): Promise<MigrationResults> {\n const {\n concurrency = 10,\n onProgress,\n onError,\n dryRun = false,\n } = options;\n\n const tenantIds = await this.migratorConfig.tenantDiscovery();\n const migrations = await this.loadMigrations();\n\n const results: TenantMigrationResult[] = [];\n let aborted = false;\n\n // Process tenants in batches\n for (let i = 0; i < tenantIds.length && !aborted; i += concurrency) {\n const batch = tenantIds.slice(i, i + concurrency);\n\n const batchResults = await Promise.all(\n batch.map(async (tenantId) => {\n if (aborted) {\n return this.createSkippedResult(tenantId);\n }\n\n try {\n onProgress?.(tenantId, 'starting');\n const result = await this.migrateTenant(tenantId, migrations, { dryRun, onProgress });\n onProgress?.(tenantId, result.success ? 'completed' : 'failed');\n return result;\n } catch (error) {\n onProgress?.(tenantId, 'failed');\n const action = onError?.(tenantId, error as Error);\n if (action === 'abort') {\n aborted = true;\n }\n return this.createErrorResult(tenantId, error as Error);\n }\n })\n );\n\n results.push(...batchResults);\n }\n\n // Mark remaining tenants as skipped if aborted\n if (aborted) {\n const remaining = tenantIds.slice(results.length);\n for (const tenantId of remaining) {\n results.push(this.createSkippedResult(tenantId));\n }\n }\n\n return this.aggregateResults(results);\n }\n\n /**\n * Migrate a single tenant\n */\n async migrateTenant(\n tenantId: string,\n migrations?: MigrationFile[],\n options: { dryRun?: boolean; onProgress?: MigrateOptions['onProgress'] } = {}\n ): Promise<TenantMigrationResult> {\n const startTime = Date.now();\n const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);\n const appliedMigrations: string[] = [];\n\n const pool = await this.createPool(schemaName);\n\n try {\n await this.migratorConfig.hooks?.beforeTenant?.(tenantId);\n\n // Ensure migrations table exists\n await this.ensureMigrationsTable(pool, schemaName);\n\n // Load migrations if not provided\n const allMigrations = migrations ?? await this.loadMigrations();\n\n // Get applied migrations\n const applied = await this.getAppliedMigrations(pool, schemaName);\n const appliedSet = new Set(applied.map((m) => m.name));\n\n // Filter pending migrations\n const pending = allMigrations.filter((m) => !appliedSet.has(m.name));\n\n if (options.dryRun) {\n return {\n tenantId,\n schemaName,\n success: true,\n appliedMigrations: pending.map((m) => m.name),\n durationMs: Date.now() - startTime,\n };\n }\n\n // Apply pending migrations\n for (const migration of pending) {\n const migrationStart = Date.now();\n options.onProgress?.(tenantId, 'migrating', migration.name);\n\n await this.migratorConfig.hooks?.beforeMigration?.(tenantId, migration.name);\n await this.applyMigration(pool, schemaName, migration);\n await this.migratorConfig.hooks?.afterMigration?.(\n tenantId,\n migration.name,\n Date.now() - migrationStart\n );\n\n appliedMigrations.push(migration.name);\n }\n\n const result: TenantMigrationResult = {\n tenantId,\n schemaName,\n success: true,\n appliedMigrations,\n durationMs: Date.now() - startTime,\n };\n\n await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);\n\n return result;\n } catch (error) {\n const result: TenantMigrationResult = {\n tenantId,\n schemaName,\n success: false,\n appliedMigrations,\n error: (error as Error).message,\n durationMs: Date.now() - startTime,\n };\n\n await this.migratorConfig.hooks?.afterTenant?.(tenantId, result);\n\n return result;\n } finally {\n await pool.end();\n }\n }\n\n /**\n * Migrate specific tenants\n */\n async migrateTenants(tenantIds: string[], options: MigrateOptions = {}): Promise<MigrationResults> {\n const migrations = await this.loadMigrations();\n const results: TenantMigrationResult[] = [];\n\n const { concurrency = 10, onProgress, onError } = options;\n\n for (let i = 0; i < tenantIds.length; i += concurrency) {\n const batch = tenantIds.slice(i, i + concurrency);\n\n const batchResults = await Promise.all(\n batch.map(async (tenantId) => {\n try {\n onProgress?.(tenantId, 'starting');\n const result = await this.migrateTenant(tenantId, migrations, { dryRun: options.dryRun ?? false, onProgress });\n onProgress?.(tenantId, result.success ? 'completed' : 'failed');\n return result;\n } catch (error) {\n onProgress?.(tenantId, 'failed');\n onError?.(tenantId, error as Error);\n return this.createErrorResult(tenantId, error as Error);\n }\n })\n );\n\n results.push(...batchResults);\n }\n\n return this.aggregateResults(results);\n }\n\n /**\n * Get migration status for all tenants\n */\n async getStatus(): Promise<TenantMigrationStatus[]> {\n const tenantIds = await this.migratorConfig.tenantDiscovery();\n const migrations = await this.loadMigrations();\n const statuses: TenantMigrationStatus[] = [];\n\n for (const tenantId of tenantIds) {\n statuses.push(await this.getTenantStatus(tenantId, migrations));\n }\n\n return statuses;\n }\n\n /**\n * Get migration status for a specific tenant\n */\n async getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus> {\n const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);\n const pool = await this.createPool(schemaName);\n\n try {\n const allMigrations = migrations ?? await this.loadMigrations();\n\n // Check if migrations table exists\n const tableExists = await this.migrationsTableExists(pool, schemaName);\n if (!tableExists) {\n return {\n tenantId,\n schemaName,\n appliedCount: 0,\n pendingCount: allMigrations.length,\n pendingMigrations: allMigrations.map((m) => m.name),\n status: allMigrations.length > 0 ? 'behind' : 'ok',\n };\n }\n\n const applied = await this.getAppliedMigrations(pool, schemaName);\n const appliedSet = new Set(applied.map((m) => m.name));\n const pending = allMigrations.filter((m) => !appliedSet.has(m.name));\n\n return {\n tenantId,\n schemaName,\n appliedCount: applied.length,\n pendingCount: pending.length,\n pendingMigrations: pending.map((m) => m.name),\n status: pending.length > 0 ? 'behind' : 'ok',\n };\n } catch (error) {\n return {\n tenantId,\n schemaName,\n appliedCount: 0,\n pendingCount: 0,\n pendingMigrations: [],\n status: 'error',\n error: (error as Error).message,\n };\n } finally {\n await pool.end();\n }\n }\n\n /**\n * Create a new tenant schema and optionally apply migrations\n */\n async createTenant(tenantId: string, options: CreateTenantOptions = {}): Promise<void> {\n const { migrate = true } = options;\n const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);\n\n const pool = new Pool({\n connectionString: this.tenantConfig.connection.url,\n ...this.tenantConfig.connection.poolConfig,\n });\n\n try {\n // Create schema\n await pool.query(`CREATE SCHEMA IF NOT EXISTS \"${schemaName}\"`);\n\n if (migrate) {\n // Apply all migrations\n await this.migrateTenant(tenantId);\n }\n } finally {\n await pool.end();\n }\n }\n\n /**\n * Drop a tenant schema\n */\n async dropTenant(tenantId: string, options: DropTenantOptions = {}): Promise<void> {\n const { cascade = true } = options;\n const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);\n\n const pool = new Pool({\n connectionString: this.tenantConfig.connection.url,\n ...this.tenantConfig.connection.poolConfig,\n });\n\n try {\n const cascadeSql = cascade ? 'CASCADE' : 'RESTRICT';\n await pool.query(`DROP SCHEMA IF EXISTS \"${schemaName}\" ${cascadeSql}`);\n } finally {\n await pool.end();\n }\n }\n\n /**\n * Check if a tenant schema exists\n */\n async tenantExists(tenantId: string): Promise<boolean> {\n const schemaName = this.tenantConfig.isolation.schemaNameTemplate(tenantId);\n\n const pool = new Pool({\n connectionString: this.tenantConfig.connection.url,\n ...this.tenantConfig.connection.poolConfig,\n });\n\n try {\n const result = await pool.query(\n `SELECT 1 FROM information_schema.schemata WHERE schema_name = $1`,\n [schemaName]\n );\n return result.rowCount !== null && result.rowCount > 0;\n } finally {\n await pool.end();\n }\n }\n\n /**\n * Load migration files from the migrations folder\n */\n private async loadMigrations(): Promise<MigrationFile[]> {\n const files = await readdir(this.migratorConfig.migrationsFolder);\n\n const migrations: MigrationFile[] = [];\n\n for (const file of files) {\n if (!file.endsWith('.sql')) continue;\n\n const filePath = join(this.migratorConfig.migrationsFolder, file);\n const content = await readFile(filePath, 'utf-8');\n\n // Extract timestamp from filename (e.g., 0001_migration_name.sql)\n const match = file.match(/^(\\d+)_/);\n const timestamp = match?.[1] ? parseInt(match[1], 10) : 0;\n\n migrations.push({\n name: basename(file, '.sql'),\n path: filePath,\n sql: content,\n timestamp,\n });\n }\n\n // Sort by timestamp\n return migrations.sort((a, b) => a.timestamp - b.timestamp);\n }\n\n /**\n * Create a pool for a specific schema\n */\n private async createPool(schemaName: string): Promise<Pool> {\n return new Pool({\n connectionString: this.tenantConfig.connection.url,\n ...this.tenantConfig.connection.poolConfig,\n options: `-c search_path=\"${schemaName}\",public`,\n });\n }\n\n /**\n * Ensure migrations table exists\n */\n private async ensureMigrationsTable(pool: Pool, schemaName: string): Promise<void> {\n await pool.query(`\n CREATE TABLE IF NOT EXISTS \"${schemaName}\".\"${this.migrationsTable}\" (\n id SERIAL PRIMARY KEY,\n name VARCHAR(255) NOT NULL UNIQUE,\n applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP\n )\n `);\n }\n\n /**\n * Check if migrations table exists\n */\n private async migrationsTableExists(pool: Pool, schemaName: string): Promise<boolean> {\n const result = await pool.query(\n `SELECT 1 FROM information_schema.tables\n WHERE table_schema = $1 AND table_name = $2`,\n [schemaName, this.migrationsTable]\n );\n return result.rowCount !== null && result.rowCount > 0;\n }\n\n /**\n * Get applied migrations for a schema\n */\n private async getAppliedMigrations(pool: Pool, schemaName: string): Promise<AppliedMigration[]> {\n const result = await pool.query<{ id: number; name: string; applied_at: Date }>(\n `SELECT id, name, applied_at FROM \"${schemaName}\".\"${this.migrationsTable}\" ORDER BY id`\n );\n return result.rows.map((row) => ({\n id: row.id,\n name: row.name,\n appliedAt: row.applied_at,\n }));\n }\n\n /**\n * Apply a migration to a schema\n */\n private async applyMigration(pool: Pool, schemaName: string, migration: MigrationFile): Promise<void> {\n const client = await pool.connect();\n\n try {\n await client.query('BEGIN');\n\n // Execute migration SQL\n await client.query(migration.sql);\n\n // Record migration\n await client.query(\n `INSERT INTO \"${schemaName}\".\"${this.migrationsTable}\" (name) VALUES ($1)`,\n [migration.name]\n );\n\n await client.query('COMMIT');\n } catch (error) {\n await client.query('ROLLBACK');\n throw error;\n } finally {\n client.release();\n }\n }\n\n /**\n * Create a skipped result\n */\n private createSkippedResult(tenantId: string): TenantMigrationResult {\n return {\n tenantId,\n schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),\n success: false,\n appliedMigrations: [],\n error: 'Skipped due to abort',\n durationMs: 0,\n };\n }\n\n /**\n * Create an error result\n */\n private createErrorResult(tenantId: string, error: Error): TenantMigrationResult {\n return {\n tenantId,\n schemaName: this.tenantConfig.isolation.schemaNameTemplate(tenantId),\n success: false,\n appliedMigrations: [],\n error: error.message,\n durationMs: 0,\n };\n }\n\n /**\n * Aggregate migration results\n */\n private aggregateResults(results: TenantMigrationResult[]): MigrationResults {\n return {\n total: results.length,\n succeeded: results.filter((r) => r.success).length,\n failed: results.filter((r) => !r.success && r.error !== 'Skipped due to abort').length,\n skipped: results.filter((r) => r.error === 'Skipped due to abort').length,\n details: results,\n };\n }\n}\n\n/**\n * Create a migrator instance\n */\nexport function createMigrator<\n TTenantSchema extends Record<string, unknown>,\n TSharedSchema extends Record<string, unknown>,\n>(\n tenantConfig: Config<TTenantSchema, TSharedSchema>,\n migratorConfig: MigratorConfig\n): Migrator<TTenantSchema, TSharedSchema> {\n return new Migrator(tenantConfig, migratorConfig);\n}\n"]}