drizzle-multitenant 1.1.0 → 1.2.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,404 +1,1109 @@
1
- import { C as Config } from '../types-BhK96FPC.js';
1
+ import { m as DetectedFormat, S as SeedFunction, k as TenantSeedResult, j as SeedOptions, l as SeedResults, n as SyncStatus, b as MigrationFile, o as TenantSyncStatus, p as TenantSyncResult, q as SyncOptions, r as SyncResults, d as MigrateOptions, e as MigrationResults, g as MigrationHooks, h as MigrationProgressCallback, i as MigrationErrorHandler, T as TenantMigrationResult, f as TenantMigrationStatus, s as ClonerConfig, t as ClonerDependencies, u as CloneTenantOptions, v as CloneTenantResult } from '../migrator-BDgFzSh8.js';
2
+ export { V as AnonymizeOptions, W as AnonymizeRules, X as AnonymizeValue, A as AppliedMigration, R as CloneProgressCallback, U as CloneProgressStatus, J as ColumnDrift, E as ColumnInfo, L as ConstraintDrift, F as ConstraintInfo, C as CreateTenantOptions, y as DEFAULT_FORMAT, z as DRIZZLE_KIT_FORMAT, D as DropTenantOptions, K as IndexDrift, I as IndexInfo, M as Migrator, a as MigratorConfig, Q as SchemaDriftOptions, P as SchemaDriftStatus, N as TableDrift, B as TableFormat, G as TableSchema, H as TenantSchema, O as TenantSchemaDrift, c as createMigrator, w as detectTableFormat, x as getFormatConfig } from '../migrator-BDgFzSh8.js';
3
+ import * as pg from 'pg';
2
4
  import { Pool } from 'pg';
5
+ import { C as Config } from '../types-BhK96FPC.js';
6
+ import 'drizzle-orm/postgres-js';
3
7
  import 'drizzle-orm/node-postgres';
4
8
 
5
9
  /**
6
- * Table format for tracking migrations
7
- * - "name": drizzle-multitenant native (filename-based)
8
- * - "hash": SHA-256 hash with timestamp
9
- * - "drizzle-kit": Exact drizzle-kit format (hash + bigint timestamp)
10
- */
11
- type TableFormat = 'name' | 'hash' | 'drizzle-kit';
12
- /**
13
- * Detected table format information
14
- */
15
- interface DetectedFormat {
16
- /** The detected format type */
17
- format: TableFormat;
18
- /** The table name */
19
- tableName: string;
20
- /** Column configuration */
21
- columns: {
22
- /** Column used for identifying migrations */
23
- identifier: 'name' | 'hash';
24
- /** Column used for timestamp */
25
- timestamp: 'applied_at' | 'created_at';
26
- /** Data type of timestamp column */
27
- timestampType: 'timestamp' | 'bigint';
28
- };
10
+ * Options for creating a tenant schema
11
+ */
12
+ interface CreateSchemaOptions {
13
+ /** Whether to also run migrations after creating (handled by Migrator) */
14
+ migrate?: boolean;
29
15
  }
30
16
  /**
31
- * Default format configuration for new tables
17
+ * Options for dropping a tenant schema
32
18
  */
33
- declare const DEFAULT_FORMAT: DetectedFormat;
19
+ interface DropSchemaOptions {
20
+ /** Use CASCADE to drop all objects in schema */
21
+ cascade?: boolean;
22
+ /** Force drop without confirmation (used by CLI) */
23
+ force?: boolean;
24
+ }
34
25
  /**
35
- * drizzle-kit format configuration
26
+ * Manages PostgreSQL schema lifecycle for multi-tenant applications.
27
+ *
28
+ * Extracted from Migrator to follow Single Responsibility Principle.
29
+ * Handles schema creation, deletion, existence checks, and migrations table management.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * const schemaManager = new SchemaManager(config);
34
+ *
35
+ * // Create a new tenant schema
36
+ * await schemaManager.createSchema('tenant-123');
37
+ *
38
+ * // Check if schema exists
39
+ * const exists = await schemaManager.schemaExists('tenant-123');
40
+ *
41
+ * // Drop a tenant schema
42
+ * await schemaManager.dropSchema('tenant-123', { cascade: true });
43
+ * ```
36
44
  */
37
- declare const DRIZZLE_KIT_FORMAT: DetectedFormat;
45
+ declare class SchemaManager<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>> {
46
+ private readonly config;
47
+ private readonly migrationsTable;
48
+ constructor(config: Config<TTenantSchema, TSharedSchema>, migrationsTable?: string);
49
+ /**
50
+ * Get the schema name for a tenant ID using the configured template
51
+ *
52
+ * @param tenantId - The tenant identifier
53
+ * @returns The PostgreSQL schema name
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * const schemaName = schemaManager.getSchemaName('tenant-123');
58
+ * // Returns: 'tenant_tenant-123' (depends on schemaNameTemplate)
59
+ * ```
60
+ */
61
+ getSchemaName(tenantId: string): string;
62
+ /**
63
+ * Create a PostgreSQL pool for a specific schema
64
+ *
65
+ * The pool is configured with `search_path` set to the schema,
66
+ * allowing queries to run in tenant isolation.
67
+ *
68
+ * @param schemaName - The PostgreSQL schema name
69
+ * @returns A configured Pool instance
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const pool = await schemaManager.createPool('tenant_123');
74
+ * try {
75
+ * await pool.query('SELECT * FROM users'); // Queries tenant_123.users
76
+ * } finally {
77
+ * await pool.end();
78
+ * }
79
+ * ```
80
+ */
81
+ createPool(schemaName: string): Promise<Pool>;
82
+ /**
83
+ * Create a PostgreSQL pool without schema-specific search_path
84
+ *
85
+ * Used for operations that need to work across schemas or
86
+ * before a schema exists (like creating the schema itself).
87
+ *
88
+ * @returns A Pool instance connected to the database
89
+ */
90
+ createRootPool(): Promise<Pool>;
91
+ /**
92
+ * Create a new tenant schema in the database
93
+ *
94
+ * @param tenantId - The tenant identifier
95
+ * @returns Promise that resolves when schema is created
96
+ *
97
+ * @example
98
+ * ```typescript
99
+ * await schemaManager.createSchema('new-tenant');
100
+ * ```
101
+ */
102
+ createSchema(tenantId: string): Promise<void>;
103
+ /**
104
+ * Drop a tenant schema from the database
105
+ *
106
+ * @param tenantId - The tenant identifier
107
+ * @param options - Drop options (cascade, force)
108
+ * @returns Promise that resolves when schema is dropped
109
+ *
110
+ * @example
111
+ * ```typescript
112
+ * // Drop with CASCADE (removes all objects)
113
+ * await schemaManager.dropSchema('old-tenant', { cascade: true });
114
+ *
115
+ * // Drop with RESTRICT (fails if objects exist)
116
+ * await schemaManager.dropSchema('old-tenant', { cascade: false });
117
+ * ```
118
+ */
119
+ dropSchema(tenantId: string, options?: DropSchemaOptions): Promise<void>;
120
+ /**
121
+ * Check if a tenant schema exists in the database
122
+ *
123
+ * @param tenantId - The tenant identifier
124
+ * @returns True if schema exists, false otherwise
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * if (await schemaManager.schemaExists('tenant-123')) {
129
+ * console.log('Tenant schema exists');
130
+ * }
131
+ * ```
132
+ */
133
+ schemaExists(tenantId: string): Promise<boolean>;
134
+ /**
135
+ * List all schemas matching a pattern
136
+ *
137
+ * @param pattern - SQL LIKE pattern to filter schemas (optional)
138
+ * @returns Array of schema names
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * // List all tenant schemas
143
+ * const schemas = await schemaManager.listSchemas('tenant_%');
144
+ * ```
145
+ */
146
+ listSchemas(pattern?: string): Promise<string[]>;
147
+ /**
148
+ * Ensure the migrations table exists with the correct format
149
+ *
150
+ * Creates the migrations tracking table if it doesn't exist,
151
+ * using the appropriate column types based on the format.
152
+ *
153
+ * @param pool - Database pool to use
154
+ * @param schemaName - The schema to create the table in
155
+ * @param format - The detected/configured table format
156
+ *
157
+ * @example
158
+ * ```typescript
159
+ * const pool = await schemaManager.createPool('tenant_123');
160
+ * await schemaManager.ensureMigrationsTable(pool, 'tenant_123', format);
161
+ * ```
162
+ */
163
+ ensureMigrationsTable(pool: Pool, schemaName: string, format: DetectedFormat): Promise<void>;
164
+ /**
165
+ * Check if the migrations table exists in a schema
166
+ *
167
+ * @param pool - Database pool to use
168
+ * @param schemaName - The schema to check
169
+ * @returns True if migrations table exists
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const pool = await schemaManager.createPool('tenant_123');
174
+ * if (await schemaManager.migrationsTableExists(pool, 'tenant_123')) {
175
+ * console.log('Migrations table exists');
176
+ * }
177
+ * ```
178
+ */
179
+ migrationsTableExists(pool: Pool, schemaName: string): Promise<boolean>;
180
+ /**
181
+ * Get the configured migrations table name
182
+ *
183
+ * @returns The migrations table name
184
+ */
185
+ getMigrationsTableName(): string;
186
+ }
38
187
  /**
39
- * Detect the format of an existing migrations table
188
+ * Factory function to create a SchemaManager instance
189
+ *
190
+ * @param config - The tenant configuration
191
+ * @param migrationsTable - Optional custom migrations table name
192
+ * @returns A new SchemaManager instance
40
193
  *
41
- * @param pool - Database connection pool
42
- * @param schemaName - Schema to check
43
- * @param tableName - Migrations table name
44
- * @returns Detected format or null if table doesn't exist
194
+ * @example
195
+ * ```typescript
196
+ * const schemaManager = createSchemaManager(config);
197
+ * await schemaManager.createSchema('tenant-123');
198
+ * ```
45
199
  */
46
- declare function detectTableFormat(pool: Pool, schemaName: string, tableName: string): Promise<DetectedFormat | null>;
200
+ declare function createSchemaManager<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(config: Config<TTenantSchema, TSharedSchema>, migrationsTable?: string): SchemaManager<TTenantSchema, TSharedSchema>;
201
+
47
202
  /**
48
- * Get the format configuration for a specific format type
203
+ * Interfaces for Migrator module refactoring
204
+ *
205
+ * These interfaces define the contracts for the extracted modules.
206
+ * They should be implemented when refactoring the god component.
207
+ *
208
+ * @see REFACTOR_PROPOSAL.md for full refactoring plan
49
209
  */
50
- declare function getFormatConfig(format: TableFormat, tableName?: string): DetectedFormat;
51
210
 
52
211
  /**
53
- * Migration file metadata
54
- */
55
- interface MigrationFile {
56
- /** Migration file name */
57
- name: string;
58
- /** Full file path */
59
- path: string;
60
- /** SQL content */
61
- sql: string;
62
- /** Timestamp extracted from filename */
63
- timestamp: number;
64
- /** SHA-256 hash of file content (for drizzle-kit compatibility) */
65
- hash: string;
66
- }
67
- /**
68
- * Migration status for a tenant
69
- */
70
- interface TenantMigrationStatus {
71
- tenantId: string;
72
- schemaName: string;
73
- appliedCount: number;
74
- pendingCount: number;
75
- pendingMigrations: string[];
76
- status: 'ok' | 'behind' | 'error';
77
- error?: string;
78
- /** Detected table format (null for new tenants without migrations table) */
79
- format: TableFormat | null;
212
+ * Options for executing a single migration
213
+ */
214
+ interface ExecuteMigrationOptions {
215
+ /** Whether to skip actual SQL execution (mark as applied only) */
216
+ markOnly?: boolean;
217
+ /** Progress callback */
218
+ onProgress?: (status: 'applying' | 'recording') => void;
80
219
  }
81
220
  /**
82
- * Migration result for a single tenant
83
- */
84
- interface TenantMigrationResult {
85
- tenantId: string;
86
- schemaName: string;
87
- success: boolean;
88
- appliedMigrations: string[];
89
- error?: string;
90
- durationMs: number;
91
- /** Table format used for this migration */
92
- format?: TableFormat;
221
+ * Responsible for executing migrations on a single tenant
222
+ */
223
+ interface IMigrationExecutor {
224
+ /**
225
+ * Execute a single migration on a tenant
226
+ */
227
+ executeMigration(pool: Pool, schemaName: string, migration: MigrationFile, format: DetectedFormat, options?: ExecuteMigrationOptions): Promise<void>;
228
+ /**
229
+ * Execute multiple migrations on a tenant
230
+ */
231
+ executeMigrations(pool: Pool, schemaName: string, migrations: MigrationFile[], format: DetectedFormat, options?: ExecuteMigrationOptions): Promise<string[]>;
232
+ /**
233
+ * Record a migration as applied without executing SQL
234
+ */
235
+ recordMigration(pool: Pool, schemaName: string, migration: MigrationFile, format: DetectedFormat): Promise<void>;
236
+ /**
237
+ * Get list of applied migrations for a tenant
238
+ */
239
+ getAppliedMigrations(pool: Pool, schemaName: string, format: DetectedFormat): Promise<Array<{
240
+ identifier: string;
241
+ name?: string;
242
+ hash?: string;
243
+ appliedAt: Date;
244
+ }>>;
245
+ /**
246
+ * Get pending migrations (not yet applied)
247
+ */
248
+ getPendingMigrations(pool: Pool, schemaName: string, allMigrations: MigrationFile[], format: DetectedFormat): Promise<MigrationFile[]>;
93
249
  }
94
250
  /**
95
- * Aggregate migration results
251
+ * Batch execution options
96
252
  */
97
- interface MigrationResults {
98
- total: number;
99
- succeeded: number;
100
- failed: number;
101
- skipped: number;
102
- details: TenantMigrationResult[];
253
+ interface BatchExecuteOptions extends MigrateOptions {
254
+ /** Migrations to apply (if not provided, loads from disk) */
255
+ migrations?: MigrationFile[];
103
256
  }
104
257
  /**
105
- * Progress callback for migrations
258
+ * Responsible for batch migration operations across multiple tenants
106
259
  */
107
- type MigrationProgressCallback = (tenantId: string, status: 'starting' | 'migrating' | 'completed' | 'failed' | 'skipped', migrationName?: string) => void;
260
+ interface IBatchExecutor {
261
+ /**
262
+ * Migrate all tenants in parallel
263
+ */
264
+ migrateAll(options?: BatchExecuteOptions): Promise<MigrationResults>;
265
+ /**
266
+ * Migrate specific tenants in parallel
267
+ */
268
+ migrateTenants(tenantIds: string[], options?: BatchExecuteOptions): Promise<MigrationResults>;
269
+ /**
270
+ * Mark all tenants as applied without executing SQL
271
+ */
272
+ markAllAsApplied(options?: MigrateOptions): Promise<MigrationResults>;
273
+ }
108
274
  /**
109
- * Error handler for migrations
275
+ * Responsible for synchronizing migration state between disk and database
110
276
  */
111
- type MigrationErrorHandler = (tenantId: string, error: Error) => 'continue' | 'abort';
277
+ interface ISyncManager {
278
+ /**
279
+ * Get sync status for all tenants
280
+ */
281
+ getSyncStatus(): Promise<SyncStatus>;
282
+ /**
283
+ * Get sync status for a specific tenant
284
+ */
285
+ getTenantSyncStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantSyncStatus>;
286
+ /**
287
+ * Mark missing migrations as applied for a tenant
288
+ */
289
+ markMissing(tenantId: string): Promise<TenantSyncResult>;
290
+ /**
291
+ * Mark missing migrations as applied for all tenants
292
+ */
293
+ markAllMissing(options?: SyncOptions): Promise<SyncResults>;
294
+ /**
295
+ * Remove orphan records from a tenant
296
+ */
297
+ cleanOrphans(tenantId: string): Promise<TenantSyncResult>;
298
+ /**
299
+ * Remove orphan records from all tenants
300
+ */
301
+ cleanAllOrphans(options?: SyncOptions): Promise<SyncResults>;
302
+ }
112
303
  /**
113
- * Migration hooks
304
+ * Responsible for seeding tenant databases
114
305
  */
115
- interface MigrationHooks {
116
- /** Called before migrating a tenant */
117
- beforeTenant?: (tenantId: string) => void | Promise<void>;
118
- /** Called after migrating a tenant */
119
- afterTenant?: (tenantId: string, result: TenantMigrationResult) => void | Promise<void>;
120
- /** Called before applying a migration */
121
- beforeMigration?: (tenantId: string, migrationName: string) => void | Promise<void>;
122
- /** Called after applying a migration */
123
- afterMigration?: (tenantId: string, migrationName: string, durationMs: number) => void | Promise<void>;
306
+ interface ISeeder<TSchema extends Record<string, unknown> = Record<string, unknown>> {
307
+ /**
308
+ * Seed a single tenant
309
+ */
310
+ seedTenant(tenantId: string, seedFn: SeedFunction<TSchema>): Promise<TenantSeedResult>;
311
+ /**
312
+ * Seed all tenants
313
+ */
314
+ seedAll(seedFn: SeedFunction<TSchema>, options?: SeedOptions): Promise<SeedResults>;
315
+ /**
316
+ * Seed specific tenants
317
+ */
318
+ seedTenants(tenantIds: string[], seedFn: SeedFunction<TSchema>, options?: SeedOptions): Promise<SeedResults>;
124
319
  }
320
+
125
321
  /**
126
- * Migrator configuration
322
+ * Configuration for the Seeder
127
323
  */
128
- interface MigratorConfig {
129
- /** Path to tenant migrations folder */
130
- migrationsFolder: string;
131
- /** Table name for tracking migrations */
132
- migrationsTable?: string;
324
+ interface SeederConfig {
133
325
  /** Function to discover tenant IDs */
134
326
  tenantDiscovery: () => Promise<string[]>;
135
- /** Migration hooks */
136
- hooks?: MigrationHooks;
327
+ }
328
+ /**
329
+ * Seeder dependencies
330
+ */
331
+ interface SeederDependencies<TTenantSchema extends Record<string, unknown> = Record<string, unknown>> {
332
+ /** Pool creation for specific schemas */
333
+ createPool: (schemaName: string) => Promise<pg.Pool>;
334
+ /** Schema name template function */
335
+ schemaNameTemplate: (tenantId: string) => string;
336
+ /** Tenant schema definition */
337
+ tenantSchema: TTenantSchema;
338
+ }
339
+
340
+ /**
341
+ * Responsible for seeding tenant databases with initial data.
342
+ *
343
+ * Extracted from Migrator to follow Single Responsibility Principle.
344
+ * Handles seeding individual tenants and batch seeding operations.
345
+ *
346
+ * @example
347
+ * ```typescript
348
+ * const seeder = new Seeder(config, dependencies);
349
+ *
350
+ * // Seed a single tenant
351
+ * const result = await seeder.seedTenant('tenant-123', async (db, tenantId) => {
352
+ * await db.insert(roles).values([
353
+ * { name: 'admin', permissions: ['*'] },
354
+ * { name: 'user', permissions: ['read'] },
355
+ * ]);
356
+ * });
357
+ *
358
+ * // Seed all tenants
359
+ * const results = await seeder.seedAll(seedFn, { concurrency: 10 });
360
+ * ```
361
+ */
362
+ declare class Seeder<TTenantSchema extends Record<string, unknown> = Record<string, unknown>> implements ISeeder<TTenantSchema> {
363
+ private readonly config;
364
+ private readonly deps;
365
+ constructor(config: SeederConfig, deps: SeederDependencies<TTenantSchema>);
137
366
  /**
138
- * Table format for tracking migrations
139
- * - "auto": Auto-detect existing format, use defaultFormat for new tables
140
- * - "name": Use filename (drizzle-multitenant native)
141
- * - "hash": Use SHA-256 hash
142
- * - "drizzle-kit": Exact drizzle-kit format (hash + bigint timestamp)
143
- * @default "auto"
367
+ * Seed a single tenant with initial data
368
+ *
369
+ * Creates a database connection for the tenant, executes the seed function,
370
+ * and properly cleans up the connection afterward.
371
+ *
372
+ * @param tenantId - The tenant identifier
373
+ * @param seedFn - Function that seeds the database
374
+ * @returns Result of the seeding operation
375
+ *
376
+ * @example
377
+ * ```typescript
378
+ * const result = await seeder.seedTenant('tenant-123', async (db, tenantId) => {
379
+ * await db.insert(users).values([
380
+ * { name: 'Admin', email: `admin@${tenantId}.com` },
381
+ * ]);
382
+ * });
383
+ *
384
+ * if (result.success) {
385
+ * console.log(`Seeded ${result.tenantId} in ${result.durationMs}ms`);
386
+ * }
387
+ * ```
144
388
  */
145
- tableFormat?: 'auto' | TableFormat;
389
+ seedTenant(tenantId: string, seedFn: SeedFunction<TTenantSchema>): Promise<TenantSeedResult>;
390
+ /**
391
+ * Seed all tenants with initial data in parallel
392
+ *
393
+ * Discovers all tenants and seeds them in batches with configurable concurrency.
394
+ * Supports progress callbacks and abort-on-error behavior.
395
+ *
396
+ * @param seedFn - Function that seeds each database
397
+ * @param options - Seeding options
398
+ * @returns Aggregate results of all seeding operations
399
+ *
400
+ * @example
401
+ * ```typescript
402
+ * const results = await seeder.seedAll(
403
+ * async (db, tenantId) => {
404
+ * await db.insert(settings).values({ key: 'initialized', value: 'true' });
405
+ * },
406
+ * {
407
+ * concurrency: 5,
408
+ * onProgress: (id, status) => console.log(`${id}: ${status}`),
409
+ * }
410
+ * );
411
+ *
412
+ * console.log(`Succeeded: ${results.succeeded}/${results.total}`);
413
+ * ```
414
+ */
415
+ seedAll(seedFn: SeedFunction<TTenantSchema>, options?: SeedOptions): Promise<SeedResults>;
416
+ /**
417
+ * Seed specific tenants with initial data
418
+ *
419
+ * Seeds only the specified tenants in batches with configurable concurrency.
420
+ *
421
+ * @param tenantIds - List of tenant IDs to seed
422
+ * @param seedFn - Function that seeds each database
423
+ * @param options - Seeding options
424
+ * @returns Aggregate results of seeding operations
425
+ *
426
+ * @example
427
+ * ```typescript
428
+ * const results = await seeder.seedTenants(
429
+ * ['tenant-1', 'tenant-2', 'tenant-3'],
430
+ * async (db) => {
431
+ * await db.insert(config).values({ setup: true });
432
+ * },
433
+ * { concurrency: 2 }
434
+ * );
435
+ * ```
436
+ */
437
+ seedTenants(tenantIds: string[], seedFn: SeedFunction<TTenantSchema>, options?: SeedOptions): Promise<SeedResults>;
438
+ /**
439
+ * Create a skipped result for aborted seeding
440
+ */
441
+ private createSkippedResult;
442
+ /**
443
+ * Create an error result for failed seeding
444
+ */
445
+ private createErrorResult;
146
446
  /**
147
- * When using "auto" format and no table exists, which format to create
148
- * @default "name"
447
+ * Aggregate individual results into a summary
149
448
  */
150
- defaultFormat?: TableFormat;
449
+ private aggregateResults;
151
450
  }
152
451
  /**
153
- * Migrate options
452
+ * Factory function to create a Seeder instance
453
+ *
454
+ * @param config - Seeder configuration
455
+ * @param dependencies - Required dependencies
456
+ * @returns A configured Seeder instance
457
+ *
458
+ * @example
459
+ * ```typescript
460
+ * const seeder = createSeeder(
461
+ * { tenantDiscovery: async () => ['t1', 't2'] },
462
+ * {
463
+ * createPool: schemaManager.createPool.bind(schemaManager),
464
+ * schemaNameTemplate: (id) => `tenant_${id}`,
465
+ * tenantSchema: schema,
466
+ * }
467
+ * );
468
+ * ```
154
469
  */
155
- interface MigrateOptions {
156
- /** Number of concurrent migrations */
157
- concurrency?: number;
158
- /** Progress callback */
159
- onProgress?: MigrationProgressCallback;
160
- /** Error handler */
161
- onError?: MigrationErrorHandler;
162
- /** Dry run mode */
163
- dryRun?: boolean;
470
+ declare function createSeeder<TTenantSchema extends Record<string, unknown> = Record<string, unknown>>(config: SeederConfig, dependencies: SeederDependencies<TTenantSchema>): Seeder<TTenantSchema>;
471
+
472
+ /**
473
+ * Internal types for the SyncManager module
474
+ *
475
+ * Public types are re-exported from the main types.ts file
476
+ * @module sync/types
477
+ */
478
+
479
+ /**
480
+ * Configuration for the SyncManager
481
+ */
482
+ interface SyncManagerConfig {
483
+ /** Function to discover tenant IDs */
484
+ tenantDiscovery: () => Promise<string[]>;
485
+ /** Path to migrations folder */
486
+ migrationsFolder: string;
487
+ /** Migrations table name */
488
+ migrationsTable: string;
164
489
  }
165
490
  /**
166
- * Tenant creation options
491
+ * SyncManager dependencies
167
492
  */
168
- interface CreateTenantOptions {
169
- /** Apply all migrations after creating schema */
170
- migrate?: boolean;
493
+ interface SyncManagerDependencies {
494
+ /** Create a pool for a specific schema */
495
+ createPool: (schemaName: string) => Promise<Pool>;
496
+ /** Schema name template function */
497
+ schemaNameTemplate: (tenantId: string) => string;
498
+ /** Check if migrations table exists */
499
+ migrationsTableExists: (pool: Pool, schemaName: string) => Promise<boolean>;
500
+ /** Ensure migrations table exists */
501
+ ensureMigrationsTable: (pool: Pool, schemaName: string, format: DetectedFormat) => Promise<void>;
502
+ /** Get or detect the format for a schema */
503
+ getOrDetectFormat: (pool: Pool, schemaName: string) => Promise<DetectedFormat>;
504
+ /** Load migrations from disk */
505
+ loadMigrations: () => Promise<MigrationFile[]>;
171
506
  }
507
+
172
508
  /**
173
- * Tenant drop options
509
+ * Responsible for synchronizing migration state between disk and database.
510
+ *
511
+ * Extracted from Migrator to follow Single Responsibility Principle.
512
+ * Handles detection of divergences and reconciliation operations.
513
+ *
514
+ * @example
515
+ * ```typescript
516
+ * const syncManager = new SyncManager(config, dependencies);
517
+ *
518
+ * // Check sync status
519
+ * const status = await syncManager.getSyncStatus();
520
+ * if (status.outOfSync > 0) {
521
+ * console.log(`${status.outOfSync} tenants out of sync`);
522
+ * }
523
+ *
524
+ * // Mark missing migrations as applied
525
+ * await syncManager.markAllMissing({ concurrency: 10 });
526
+ *
527
+ * // Clean orphan records
528
+ * await syncManager.cleanAllOrphans({ concurrency: 10 });
529
+ * ```
174
530
  */
175
- interface DropTenantOptions {
176
- /** Skip confirmation (force drop) */
177
- force?: boolean;
178
- /** Cascade drop */
179
- cascade?: boolean;
531
+ declare class SyncManager implements ISyncManager {
532
+ private readonly config;
533
+ private readonly deps;
534
+ constructor(config: SyncManagerConfig, deps: SyncManagerDependencies);
535
+ /**
536
+ * Get sync status for all tenants
537
+ *
538
+ * Detects divergences between migrations on disk and tracking in database.
539
+ * A tenant is "in sync" when all disk migrations are tracked and no orphan records exist.
540
+ *
541
+ * @returns Aggregate sync status for all tenants
542
+ *
543
+ * @example
544
+ * ```typescript
545
+ * const status = await syncManager.getSyncStatus();
546
+ * console.log(`Total: ${status.total}, In sync: ${status.inSync}, Out of sync: ${status.outOfSync}`);
547
+ *
548
+ * for (const tenant of status.details.filter(d => !d.inSync)) {
549
+ * console.log(`${tenant.tenantId}: missing=${tenant.missing.length}, orphans=${tenant.orphans.length}`);
550
+ * }
551
+ * ```
552
+ */
553
+ getSyncStatus(): Promise<SyncStatus>;
554
+ /**
555
+ * Get sync status for a specific tenant
556
+ *
557
+ * Compares migrations on disk with records in the database.
558
+ * Identifies missing migrations (on disk but not tracked) and
559
+ * orphan records (tracked but not on disk).
560
+ *
561
+ * @param tenantId - The tenant identifier
562
+ * @param migrations - Optional pre-loaded migrations (avoids reloading from disk)
563
+ * @returns Sync status for the tenant
564
+ *
565
+ * @example
566
+ * ```typescript
567
+ * const status = await syncManager.getTenantSyncStatus('tenant-123');
568
+ * if (status.missing.length > 0) {
569
+ * console.log(`Missing: ${status.missing.join(', ')}`);
570
+ * }
571
+ * if (status.orphans.length > 0) {
572
+ * console.log(`Orphans: ${status.orphans.join(', ')}`);
573
+ * }
574
+ * ```
575
+ */
576
+ getTenantSyncStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantSyncStatus>;
577
+ /**
578
+ * Mark missing migrations as applied for a tenant
579
+ *
580
+ * Records migrations that exist on disk but are not tracked in the database.
581
+ * Useful for syncing tracking state with already-applied migrations.
582
+ *
583
+ * @param tenantId - The tenant identifier
584
+ * @returns Result of the mark operation
585
+ *
586
+ * @example
587
+ * ```typescript
588
+ * const result = await syncManager.markMissing('tenant-123');
589
+ * if (result.success) {
590
+ * console.log(`Marked ${result.markedMigrations.length} migrations as applied`);
591
+ * }
592
+ * ```
593
+ */
594
+ markMissing(tenantId: string): Promise<TenantSyncResult>;
595
+ /**
596
+ * Mark missing migrations as applied for all tenants
597
+ *
598
+ * Processes all tenants in parallel with configurable concurrency.
599
+ * Supports progress callbacks and abort-on-error behavior.
600
+ *
601
+ * @param options - Sync options
602
+ * @returns Aggregate results of all mark operations
603
+ *
604
+ * @example
605
+ * ```typescript
606
+ * const results = await syncManager.markAllMissing({
607
+ * concurrency: 10,
608
+ * onProgress: (id, status) => console.log(`${id}: ${status}`),
609
+ * });
610
+ * console.log(`Succeeded: ${results.succeeded}/${results.total}`);
611
+ * ```
612
+ */
613
+ markAllMissing(options?: SyncOptions): Promise<SyncResults>;
614
+ /**
615
+ * Remove orphan migration records for a tenant
616
+ *
617
+ * Deletes records from the migrations table that don't have
618
+ * corresponding files on disk.
619
+ *
620
+ * @param tenantId - The tenant identifier
621
+ * @returns Result of the clean operation
622
+ *
623
+ * @example
624
+ * ```typescript
625
+ * const result = await syncManager.cleanOrphans('tenant-123');
626
+ * if (result.success) {
627
+ * console.log(`Removed ${result.removedOrphans.length} orphan records`);
628
+ * }
629
+ * ```
630
+ */
631
+ cleanOrphans(tenantId: string): Promise<TenantSyncResult>;
632
+ /**
633
+ * Remove orphan migration records for all tenants
634
+ *
635
+ * Processes all tenants in parallel with configurable concurrency.
636
+ * Supports progress callbacks and abort-on-error behavior.
637
+ *
638
+ * @param options - Sync options
639
+ * @returns Aggregate results of all clean operations
640
+ *
641
+ * @example
642
+ * ```typescript
643
+ * const results = await syncManager.cleanAllOrphans({
644
+ * concurrency: 10,
645
+ * onProgress: (id, status) => console.log(`${id}: ${status}`),
646
+ * });
647
+ * console.log(`Succeeded: ${results.succeeded}/${results.total}`);
648
+ * ```
649
+ */
650
+ cleanAllOrphans(options?: SyncOptions): Promise<SyncResults>;
651
+ /**
652
+ * Get applied migrations for a schema
653
+ */
654
+ private getAppliedMigrations;
655
+ /**
656
+ * Check if a migration has been applied
657
+ */
658
+ private isMigrationApplied;
659
+ /**
660
+ * Record a migration as applied (without executing SQL)
661
+ */
662
+ private recordMigration;
663
+ /**
664
+ * Create a skipped sync result for aborted operations
665
+ */
666
+ private createSkippedSyncResult;
667
+ /**
668
+ * Create an error sync result for failed operations
669
+ */
670
+ private createErrorSyncResult;
671
+ /**
672
+ * Aggregate individual sync results into a summary
673
+ */
674
+ private aggregateSyncResults;
180
675
  }
181
676
  /**
182
- * Applied migration record
677
+ * Factory function to create a SyncManager instance
678
+ *
679
+ * @param config - SyncManager configuration
680
+ * @param dependencies - Required dependencies
681
+ * @returns A configured SyncManager instance
682
+ *
683
+ * @example
684
+ * ```typescript
685
+ * const syncManager = createSyncManager(
686
+ * {
687
+ * tenantDiscovery: async () => ['t1', 't2'],
688
+ * migrationsFolder: './migrations',
689
+ * migrationsTable: '__drizzle_migrations',
690
+ * },
691
+ * {
692
+ * createPool: schemaManager.createPool.bind(schemaManager),
693
+ * schemaNameTemplate: (id) => `tenant_${id}`,
694
+ * migrationsTableExists: schemaManager.migrationsTableExists.bind(schemaManager),
695
+ * ensureMigrationsTable: schemaManager.ensureMigrationsTable.bind(schemaManager),
696
+ * getOrDetectFormat: migrator.getOrDetectFormat.bind(migrator),
697
+ * loadMigrations: migrator.loadMigrations.bind(migrator),
698
+ * }
699
+ * );
700
+ * ```
701
+ */
702
+ declare function createSyncManager(config: SyncManagerConfig, dependencies: SyncManagerDependencies): SyncManager;
703
+
704
+ /**
705
+ * Applied migration with parsed data
183
706
  */
184
707
  interface AppliedMigration {
185
- id: number;
186
- /** Migration identifier (name or hash depending on format) */
187
708
  identifier: string;
188
- /** Migration name (only available in name-based format) */
189
709
  name?: string;
190
- /** Migration hash (only available in hash-based format) */
191
710
  hash?: string;
192
711
  appliedAt: Date;
193
712
  }
194
713
  /**
195
- * Sync status for a single tenant
196
- */
197
- interface TenantSyncStatus {
198
- tenantId: string;
199
- schemaName: string;
200
- /** Migrations in disk but not tracked in database */
201
- missing: string[];
202
- /** Migrations tracked in database but not found in disk */
203
- orphans: string[];
204
- /** Whether the tenant is in sync */
205
- inSync: boolean;
206
- /** Table format used */
207
- format: TableFormat | null;
208
- /** Error if any */
209
- error?: string;
714
+ * Configuration for MigrationExecutor
715
+ */
716
+ interface MigrationExecutorConfig {
717
+ /** Migration hooks for lifecycle events */
718
+ hooks?: MigrationHooks | undefined;
210
719
  }
211
720
  /**
212
- * Aggregate sync status
721
+ * Dependencies for MigrationExecutor
213
722
  */
214
- interface SyncStatus {
215
- total: number;
216
- inSync: number;
217
- outOfSync: number;
218
- error: number;
219
- details: TenantSyncStatus[];
723
+ interface MigrationExecutorDependencies {
724
+ /** Create a database pool for a schema */
725
+ createPool: (schemaName: string) => Promise<Pool>;
726
+ /** Get schema name from tenant ID */
727
+ schemaNameTemplate: (tenantId: string) => string;
728
+ /** Check if migrations table exists */
729
+ migrationsTableExists: (pool: Pool, schemaName: string) => Promise<boolean>;
730
+ /** Ensure migrations table exists with correct format */
731
+ ensureMigrationsTable: (pool: Pool, schemaName: string, format: DetectedFormat) => Promise<void>;
732
+ /** Get or detect table format */
733
+ getOrDetectFormat: (pool: Pool, schemaName: string) => Promise<DetectedFormat>;
734
+ /** Load migrations from disk */
735
+ loadMigrations: () => Promise<MigrationFile[]>;
220
736
  }
221
737
  /**
222
- * Sync result for a single tenant
223
- */
224
- interface TenantSyncResult {
225
- tenantId: string;
226
- schemaName: string;
227
- success: boolean;
228
- /** Migrations that were marked as applied */
229
- markedMigrations: string[];
230
- /** Orphan records that were removed */
231
- removedOrphans: string[];
232
- error?: string;
233
- durationMs: number;
738
+ * Options for migrating a single tenant
739
+ */
740
+ interface MigrateTenantOptions {
741
+ /** Whether to skip actual SQL execution (dry run) */
742
+ dryRun?: boolean | undefined;
743
+ /** Progress callback */
744
+ onProgress?: MigrationProgressCallback | undefined;
234
745
  }
235
746
  /**
236
- * Aggregate sync results
747
+ * Configuration for BatchExecutor
237
748
  */
238
- interface SyncResults {
239
- total: number;
240
- succeeded: number;
241
- failed: number;
242
- details: TenantSyncResult[];
749
+ interface BatchExecutorConfig {
750
+ /** Function to discover tenant IDs */
751
+ tenantDiscovery: () => Promise<string[]>;
243
752
  }
244
753
  /**
245
- * Options for sync operations
754
+ * Options for batch migration operations
246
755
  */
247
- interface SyncOptions {
248
- /** Number of concurrent operations */
756
+ interface BatchMigrateOptions {
757
+ /** Number of concurrent migrations */
249
758
  concurrency?: number;
250
759
  /** Progress callback */
251
- onProgress?: (tenantId: string, status: 'starting' | 'syncing' | 'completed' | 'failed') => void;
760
+ onProgress?: MigrationProgressCallback;
252
761
  /** Error handler */
253
762
  onError?: MigrationErrorHandler;
763
+ /** Dry run mode */
764
+ dryRun?: boolean;
254
765
  }
255
766
 
256
767
  /**
257
- * Parallel migration engine for multi-tenant applications
768
+ * Responsible for executing migrations on individual tenants.
769
+ *
770
+ * Extracted from Migrator to follow Single Responsibility Principle.
771
+ * Handles single-tenant migration operations including:
772
+ * - Executing migrations with SQL
773
+ * - Marking migrations as applied (without SQL)
774
+ * - Checking migration status
775
+ * - Getting applied/pending migrations
776
+ *
777
+ * @example
778
+ * ```typescript
779
+ * const executor = new MigrationExecutor(config, dependencies);
780
+ *
781
+ * // Migrate a single tenant
782
+ * const result = await executor.migrateTenant('tenant-123');
783
+ *
784
+ * // Mark migrations as applied without executing SQL
785
+ * const markResult = await executor.markAsApplied('tenant-123');
786
+ *
787
+ * // Get tenant status
788
+ * const status = await executor.getTenantStatus('tenant-123');
789
+ * ```
258
790
  */
259
- declare class Migrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>> {
260
- private readonly tenantConfig;
261
- private readonly migratorConfig;
262
- private readonly migrationsTable;
263
- constructor(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig);
264
- /**
265
- * Migrate all tenants in parallel
266
- */
267
- migrateAll(options?: MigrateOptions): Promise<MigrationResults>;
791
+ declare class MigrationExecutor implements IMigrationExecutor {
792
+ private readonly config;
793
+ private readonly deps;
794
+ constructor(config: MigrationExecutorConfig, deps: MigrationExecutorDependencies);
268
795
  /**
269
796
  * Migrate a single tenant
797
+ *
798
+ * Applies all pending migrations to the tenant's schema.
799
+ * Creates the migrations table if it doesn't exist.
800
+ *
801
+ * @param tenantId - The tenant identifier
802
+ * @param migrations - Optional pre-loaded migrations (avoids reloading from disk)
803
+ * @param options - Migration options (dryRun, onProgress)
804
+ * @returns Migration result with applied migrations and duration
805
+ *
806
+ * @example
807
+ * ```typescript
808
+ * const result = await executor.migrateTenant('tenant-123', undefined, {
809
+ * dryRun: false,
810
+ * onProgress: (id, status, name) => console.log(`${id}: ${status} ${name}`),
811
+ * });
812
+ *
813
+ * if (result.success) {
814
+ * console.log(`Applied ${result.appliedMigrations.length} migrations`);
815
+ * }
816
+ * ```
270
817
  */
271
- migrateTenant(tenantId: string, migrations?: MigrationFile[], options?: {
272
- dryRun?: boolean;
273
- onProgress?: MigrateOptions['onProgress'];
274
- }): Promise<TenantMigrationResult>;
275
- /**
276
- * Migrate specific tenants
277
- */
278
- migrateTenants(tenantIds: string[], options?: MigrateOptions): Promise<MigrationResults>;
279
- /**
280
- * Get migration status for all tenants
281
- */
282
- getStatus(): Promise<TenantMigrationStatus[]>;
283
- /**
284
- * Get migration status for a specific tenant
285
- */
286
- getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus>;
287
- /**
288
- * Create a new tenant schema and optionally apply migrations
289
- */
290
- createTenant(tenantId: string, options?: CreateTenantOptions): Promise<void>;
291
- /**
292
- * Drop a tenant schema
293
- */
294
- dropTenant(tenantId: string, options?: DropTenantOptions): Promise<void>;
295
- /**
296
- * Check if a tenant schema exists
297
- */
298
- tenantExists(tenantId: string): Promise<boolean>;
818
+ migrateTenant(tenantId: string, migrations?: MigrationFile[], options?: MigrateTenantOptions): Promise<TenantMigrationResult>;
299
819
  /**
300
820
  * Mark migrations as applied without executing SQL
821
+ *
301
822
  * Useful for syncing tracking state with already-applied migrations
823
+ * or when migrations were applied manually.
824
+ *
825
+ * @param tenantId - The tenant identifier
826
+ * @param options - Options with progress callback
827
+ * @returns Result with list of marked migrations
828
+ *
829
+ * @example
830
+ * ```typescript
831
+ * const result = await executor.markAsApplied('tenant-123', {
832
+ * onProgress: (id, status, name) => console.log(`${id}: marking ${name}`),
833
+ * });
834
+ *
835
+ * console.log(`Marked ${result.appliedMigrations.length} migrations`);
836
+ * ```
302
837
  */
303
838
  markAsApplied(tenantId: string, options?: {
304
- onProgress?: MigrateOptions['onProgress'];
839
+ onProgress?: MigrateTenantOptions['onProgress'];
305
840
  }): Promise<TenantMigrationResult>;
306
841
  /**
307
- * Mark migrations as applied for all tenants without executing SQL
308
- * Useful for syncing tracking state with already-applied migrations
309
- */
310
- markAllAsApplied(options?: MigrateOptions): Promise<MigrationResults>;
311
- /**
312
- * Get sync status for all tenants
313
- * Detects divergences between migrations on disk and tracking in database
314
- */
315
- getSyncStatus(): Promise<SyncStatus>;
316
- /**
317
- * Get sync status for a specific tenant
318
- */
319
- getTenantSyncStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantSyncStatus>;
320
- /**
321
- * Mark missing migrations as applied for a tenant
322
- */
323
- markMissing(tenantId: string): Promise<TenantSyncResult>;
324
- /**
325
- * Mark missing migrations as applied for all tenants
842
+ * Get migration status for a specific tenant
843
+ *
844
+ * Returns information about applied and pending migrations.
845
+ *
846
+ * @param tenantId - The tenant identifier
847
+ * @param migrations - Optional pre-loaded migrations
848
+ * @returns Migration status with counts and pending list
849
+ *
850
+ * @example
851
+ * ```typescript
852
+ * const status = await executor.getTenantStatus('tenant-123');
853
+ * if (status.status === 'behind') {
854
+ * console.log(`Pending: ${status.pendingMigrations.join(', ')}`);
855
+ * }
856
+ * ```
326
857
  */
327
- markAllMissing(options?: SyncOptions): Promise<SyncResults>;
858
+ getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus>;
328
859
  /**
329
- * Remove orphan migration records for a tenant
860
+ * Execute a single migration on a schema
330
861
  */
331
- cleanOrphans(tenantId: string): Promise<TenantSyncResult>;
862
+ executeMigration(pool: Pool, schemaName: string, migration: MigrationFile, format: DetectedFormat, options?: {
863
+ markOnly?: boolean;
864
+ onProgress?: (status: 'applying' | 'recording') => void;
865
+ }): Promise<void>;
332
866
  /**
333
- * Remove orphan migration records for all tenants
867
+ * Execute multiple migrations on a schema
334
868
  */
335
- cleanAllOrphans(options?: SyncOptions): Promise<SyncResults>;
869
+ executeMigrations(pool: Pool, schemaName: string, migrations: MigrationFile[], format: DetectedFormat, options?: {
870
+ markOnly?: boolean;
871
+ onProgress?: (status: 'applying' | 'recording') => void;
872
+ }): Promise<string[]>;
336
873
  /**
337
- * Load migration files from the migrations folder
874
+ * Record a migration as applied without executing SQL
338
875
  */
339
- private loadMigrations;
876
+ recordMigration(pool: Pool, schemaName: string, migration: MigrationFile, format: DetectedFormat): Promise<void>;
340
877
  /**
341
- * Create a pool for a specific schema
878
+ * Get list of applied migrations for a tenant
342
879
  */
343
- private createPool;
880
+ getAppliedMigrations(pool: Pool, schemaName: string, format: DetectedFormat): Promise<AppliedMigration[]>;
344
881
  /**
345
- * Ensure migrations table exists with the correct format
882
+ * Get pending migrations (not yet applied)
346
883
  */
347
- private ensureMigrationsTable;
884
+ getPendingMigrations(pool: Pool, schemaName: string, allMigrations: MigrationFile[], format: DetectedFormat): Promise<MigrationFile[]>;
348
885
  /**
349
- * Check if migrations table exists
886
+ * Check if a migration has been applied
350
887
  */
351
- private migrationsTableExists;
888
+ private isMigrationApplied;
352
889
  /**
353
- * Get applied migrations for a schema
890
+ * Apply a migration to a schema (execute SQL + record)
354
891
  */
355
- private getAppliedMigrations;
892
+ private applyMigration;
893
+ }
894
+ /**
895
+ * Factory function to create a MigrationExecutor instance
896
+ *
897
+ * @param config - Executor configuration (hooks)
898
+ * @param dependencies - Required dependencies
899
+ * @returns A configured MigrationExecutor instance
900
+ *
901
+ * @example
902
+ * ```typescript
903
+ * const executor = createMigrationExecutor(
904
+ * { hooks: { beforeTenant: async (id) => console.log(`Starting ${id}`) } },
905
+ * {
906
+ * createPool: schemaManager.createPool.bind(schemaManager),
907
+ * schemaNameTemplate: (id) => `tenant_${id}`,
908
+ * migrationsTableExists: schemaManager.migrationsTableExists.bind(schemaManager),
909
+ * ensureMigrationsTable: schemaManager.ensureMigrationsTable.bind(schemaManager),
910
+ * getOrDetectFormat: async (pool, schema) => getFormatConfig('name', table),
911
+ * loadMigrations: async () => loadMigrationsFromDisk(),
912
+ * }
913
+ * );
914
+ * ```
915
+ */
916
+ declare function createMigrationExecutor(config: MigrationExecutorConfig, dependencies: MigrationExecutorDependencies): MigrationExecutor;
917
+
918
+ /**
919
+ * Responsible for batch migration operations across multiple tenants.
920
+ *
921
+ * Extracted from Migrator to follow Single Responsibility Principle.
922
+ * Handles parallel migration operations including:
923
+ * - Migrating all tenants with configurable concurrency
924
+ * - Migrating specific tenants
925
+ * - Marking all migrations as applied
926
+ * - Progress tracking and error handling
927
+ *
928
+ * @example
929
+ * ```typescript
930
+ * const batchExecutor = new BatchExecutor(config, migrationExecutor, loadMigrations);
931
+ *
932
+ * // Migrate all tenants with concurrency 10
933
+ * const results = await batchExecutor.migrateAll({ concurrency: 10 });
934
+ *
935
+ * // Migrate specific tenants
936
+ * const specificResults = await batchExecutor.migrateTenants(['t1', 't2'], {
937
+ * onProgress: (id, status) => console.log(`${id}: ${status}`),
938
+ * });
939
+ * ```
940
+ */
941
+ declare class BatchExecutor implements IBatchExecutor {
942
+ private readonly config;
943
+ private readonly executor;
944
+ private readonly loadMigrations;
945
+ constructor(config: BatchExecutorConfig, executor: MigrationExecutor, loadMigrations: () => Promise<MigrationFile[]>);
356
946
  /**
357
- * Check if a migration has been applied
947
+ * Migrate all tenants in parallel
948
+ *
949
+ * Processes tenants in batches with configurable concurrency.
950
+ * Supports progress callbacks, error handling, and abort behavior.
951
+ *
952
+ * @param options - Migration options (concurrency, dryRun, callbacks)
953
+ * @returns Aggregate results for all tenants
954
+ *
955
+ * @example
956
+ * ```typescript
957
+ * const results = await batchExecutor.migrateAll({
958
+ * concurrency: 10,
959
+ * dryRun: false,
960
+ * onProgress: (id, status) => console.log(`${id}: ${status}`),
961
+ * onError: (id, error) => {
962
+ * console.error(`${id} failed: ${error.message}`);
963
+ * return 'continue'; // or 'abort' to stop all
964
+ * },
965
+ * });
966
+ *
967
+ * console.log(`Succeeded: ${results.succeeded}/${results.total}`);
968
+ * ```
358
969
  */
359
- private isMigrationApplied;
970
+ migrateAll(options?: BatchMigrateOptions): Promise<MigrationResults>;
360
971
  /**
361
- * Get or detect the format for a schema
362
- * Returns the configured format or auto-detects from existing table
972
+ * Migrate specific tenants in parallel
973
+ *
974
+ * Same as migrateAll but for a subset of tenants.
975
+ *
976
+ * @param tenantIds - List of tenant IDs to migrate
977
+ * @param options - Migration options
978
+ * @returns Aggregate results for specified tenants
979
+ *
980
+ * @example
981
+ * ```typescript
982
+ * const results = await batchExecutor.migrateTenants(
983
+ * ['tenant-1', 'tenant-2', 'tenant-3'],
984
+ * { concurrency: 5 }
985
+ * );
986
+ * ```
363
987
  */
364
- private getOrDetectFormat;
988
+ migrateTenants(tenantIds: string[], options?: BatchMigrateOptions): Promise<MigrationResults>;
365
989
  /**
366
- * Apply a migration to a schema
990
+ * Mark all tenants as applied without executing SQL
991
+ *
992
+ * Useful for syncing tracking state with already-applied migrations.
993
+ * Processes tenants in parallel with configurable concurrency.
994
+ *
995
+ * @param options - Migration options
996
+ * @returns Aggregate results for all tenants
997
+ *
998
+ * @example
999
+ * ```typescript
1000
+ * const results = await batchExecutor.markAllAsApplied({
1001
+ * concurrency: 10,
1002
+ * onProgress: (id, status) => console.log(`${id}: ${status}`),
1003
+ * });
1004
+ * ```
367
1005
  */
368
- private applyMigration;
1006
+ markAllAsApplied(options?: BatchMigrateOptions): Promise<MigrationResults>;
369
1007
  /**
370
- * Record a migration as applied without executing SQL
371
- * Used by markAsApplied to sync tracking state
1008
+ * Get migration status for all tenants
1009
+ *
1010
+ * Queries each tenant's migration status sequentially.
1011
+ *
1012
+ * @returns List of migration status for all tenants
1013
+ *
1014
+ * @example
1015
+ * ```typescript
1016
+ * const statuses = await batchExecutor.getStatus();
1017
+ * const behind = statuses.filter(s => s.status === 'behind');
1018
+ * console.log(`${behind.length} tenants need migrations`);
1019
+ * ```
372
1020
  */
373
- private recordMigration;
1021
+ getStatus(): Promise<TenantMigrationStatus[]>;
374
1022
  /**
375
- * Create a skipped result
1023
+ * Create a skipped result for aborted operations
376
1024
  */
377
1025
  private createSkippedResult;
378
1026
  /**
379
- * Create an error result
1027
+ * Create an error result for failed operations
380
1028
  */
381
1029
  private createErrorResult;
382
1030
  /**
383
- * Aggregate migration results
1031
+ * Aggregate individual migration results into a summary
384
1032
  */
385
1033
  private aggregateResults;
1034
+ }
1035
+ /**
1036
+ * Factory function to create a BatchExecutor instance
1037
+ *
1038
+ * @param config - Batch configuration (tenantDiscovery)
1039
+ * @param executor - MigrationExecutor instance
1040
+ * @param loadMigrations - Function to load migrations from disk
1041
+ * @returns A configured BatchExecutor instance
1042
+ *
1043
+ * @example
1044
+ * ```typescript
1045
+ * const batchExecutor = createBatchExecutor(
1046
+ * { tenantDiscovery: async () => ['t1', 't2', 't3'] },
1047
+ * migrationExecutor,
1048
+ * async () => loadMigrationsFromDisk('./migrations')
1049
+ * );
1050
+ * ```
1051
+ */
1052
+ declare function createBatchExecutor(config: BatchExecutorConfig, executor: MigrationExecutor, loadMigrations: () => Promise<MigrationFile[]>): BatchExecutor;
1053
+
1054
+ /**
1055
+ * Cloner
1056
+ *
1057
+ * Clones a tenant to another, including structure and optionally data.
1058
+ *
1059
+ * @module clone/cloner
1060
+ */
1061
+
1062
+ /**
1063
+ * Clones tenants with support for introspection + DDL
1064
+ *
1065
+ * @example
1066
+ * ```typescript
1067
+ * const cloner = createCloner(config, dependencies);
1068
+ *
1069
+ * // Schema-only clone
1070
+ * await cloner.cloneTenant('source', 'target');
1071
+ *
1072
+ * // Clone with data
1073
+ * await cloner.cloneTenant('source', 'target', { includeData: true });
1074
+ *
1075
+ * // Clone with anonymization
1076
+ * await cloner.cloneTenant('source', 'target', {
1077
+ * includeData: true,
1078
+ * anonymize: {
1079
+ * enabled: true,
1080
+ * rules: { users: { email: null, phone: null } },
1081
+ * },
1082
+ * });
1083
+ * ```
1084
+ */
1085
+ declare class Cloner {
1086
+ private readonly deps;
1087
+ private readonly migrationsTable;
1088
+ constructor(config: ClonerConfig, deps: ClonerDependencies);
386
1089
  /**
387
- * Create a skipped sync result
388
- */
389
- private createSkippedSyncResult;
390
- /**
391
- * Create an error sync result
392
- */
393
- private createErrorSyncResult;
394
- /**
395
- * Aggregate sync results
1090
+ * Clone a tenant to another
1091
+ *
1092
+ * @param sourceTenantId - Source tenant ID
1093
+ * @param targetTenantId - Target tenant ID
1094
+ * @param options - Clone options
1095
+ * @returns Clone result
396
1096
  */
397
- private aggregateSyncResults;
1097
+ cloneTenant(sourceTenantId: string, targetTenantId: string, options?: CloneTenantOptions): Promise<CloneTenantResult>;
1098
+ private createErrorResult;
398
1099
  }
399
1100
  /**
400
- * Create a migrator instance
1101
+ * Factory function for Cloner
1102
+ *
1103
+ * @param config - Cloner configuration
1104
+ * @param dependencies - Cloner dependencies
1105
+ * @returns Cloner instance
401
1106
  */
402
- declare function createMigrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig): Migrator<TTenantSchema, TSharedSchema>;
1107
+ declare function createCloner(config: ClonerConfig, dependencies: ClonerDependencies): Cloner;
403
1108
 
404
- export { type AppliedMigration, type CreateTenantOptions, DEFAULT_FORMAT, DRIZZLE_KIT_FORMAT, type DetectedFormat, type DropTenantOptions, type MigrateOptions, type MigrationErrorHandler, type MigrationFile, type MigrationHooks, type MigrationProgressCallback, type MigrationResults, Migrator, type MigratorConfig, type TableFormat, type TenantMigrationResult, type TenantMigrationStatus, createMigrator, detectTableFormat, getFormatConfig };
1109
+ export { BatchExecutor, type BatchExecutorConfig, type BatchMigrateOptions, CloneTenantOptions, CloneTenantResult, Cloner, ClonerConfig, ClonerDependencies, type CreateSchemaOptions, DetectedFormat, type DropSchemaOptions, MigrateOptions, type MigrateTenantOptions, MigrationErrorHandler, MigrationExecutor, type MigrationExecutorConfig, type MigrationExecutorDependencies, MigrationFile, MigrationHooks, MigrationProgressCallback, MigrationResults, SchemaManager, SeedFunction, SeedOptions, SeedResults, Seeder, type SeederConfig, type SeederDependencies, SyncManager, type SyncManagerConfig, type SyncManagerDependencies, SyncOptions, SyncResults, SyncStatus, TenantMigrationResult, TenantMigrationStatus, TenantSeedResult, TenantSyncResult, TenantSyncStatus, createBatchExecutor, createCloner, createMigrationExecutor, createSchemaManager, createSeeder, createSyncManager };