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,356 +1,1203 @@
1
- import { C as Config } from '../types-BhK96FPC.js';
1
+ import { o as DetectedFormat, S as SeedFunction, k as TenantSeedResult, j as SeedOptions, l as SeedResults, p as SyncStatus, b as MigrationFile, q as TenantSyncStatus, r as TenantSyncResult, s as SyncOptions, t as SyncResults, d as MigrateOptions, e as MigrationResults, g as MigrationHooks, h as MigrationProgressCallback, i as MigrationErrorHandler, T as TenantMigrationResult, f as TenantMigrationStatus, u as ClonerConfig, v as ClonerDependencies, w as CloneTenantOptions, x as CloneTenantResult, y as SharedMigrateOptions, z as SharedMigrationResult, B as SharedMigrationStatus } from '../migrator-B7oPKe73.js';
2
+ export { _ as AnonymizeOptions, $ as AnonymizeRules, a0 as AnonymizeValue, A as AppliedMigration, Y as CloneProgressCallback, Z as CloneProgressStatus, P as ColumnDrift, J as ColumnInfo, R as ConstraintDrift, L as ConstraintInfo, C as CreateTenantOptions, G as DEFAULT_FORMAT, H as DRIZZLE_KIT_FORMAT, D as DropTenantOptions, Q as IndexDrift, K as IndexInfo, M as Migrator, a as MigratorConfig, X as SchemaDriftOptions, W as SchemaDriftStatus, a2 as SharedMigrationHooks, a1 as SharedMigratorConfig, m as SharedSeedFunction, n as SharedSeedResult, U as TableDrift, I as TableFormat, N as TableSchema, O as TenantSchema, V as TenantSchemaDrift, c as createMigrator, E as detectTableFormat, F as getFormatConfig } from '../migrator-B7oPKe73.js';
3
+ import * as pg from 'pg';
2
4
  import { Pool } from 'pg';
5
+ import { C as Config } from '../types-CGqsPe2Q.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>);
366
+ /**
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
+ * ```
388
+ */
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;
137
442
  /**
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"
443
+ * Create an error result for failed seeding
144
444
  */
145
- tableFormat?: 'auto' | TableFormat;
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);
791
+ declare class MigrationExecutor implements IMigrationExecutor {
792
+ private readonly config;
793
+ private readonly deps;
794
+ constructor(config: MigrationExecutorConfig, deps: MigrationExecutorDependencies);
264
795
  /**
265
- * Migrate all tenants in parallel
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
+ * ```
266
817
  */
267
- migrateAll(options?: MigrateOptions): Promise<MigrationResults>;
818
+ migrateTenant(tenantId: string, migrations?: MigrationFile[], options?: MigrateTenantOptions): Promise<TenantMigrationResult>;
268
819
  /**
269
- * Migrate a single tenant
820
+ * Mark migrations as applied without executing SQL
821
+ *
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
+ * ```
270
837
  */
271
- migrateTenant(tenantId: string, migrations?: MigrationFile[], options?: {
272
- dryRun?: boolean;
273
- onProgress?: MigrateOptions['onProgress'];
838
+ markAsApplied(tenantId: string, options?: {
839
+ onProgress?: MigrateTenantOptions['onProgress'];
274
840
  }): Promise<TenantMigrationResult>;
275
841
  /**
276
- * Migrate specific 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
+ * ```
277
857
  */
278
- migrateTenants(tenantIds: string[], options?: MigrateOptions): Promise<MigrationResults>;
858
+ getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus>;
279
859
  /**
280
- * Get migration status for all tenants
860
+ * Execute a single migration on a schema
281
861
  */
282
- getStatus(): Promise<TenantMigrationStatus[]>;
862
+ executeMigration(pool: Pool, schemaName: string, migration: MigrationFile, format: DetectedFormat, options?: {
863
+ markOnly?: boolean;
864
+ onProgress?: (status: 'applying' | 'recording') => void;
865
+ }): Promise<void>;
283
866
  /**
284
- * Get migration status for a specific tenant
867
+ * Execute multiple migrations on a schema
285
868
  */
286
- getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus>;
869
+ executeMigrations(pool: Pool, schemaName: string, migrations: MigrationFile[], format: DetectedFormat, options?: {
870
+ markOnly?: boolean;
871
+ onProgress?: (status: 'applying' | 'recording') => void;
872
+ }): Promise<string[]>;
287
873
  /**
288
- * Create a new tenant schema and optionally apply migrations
874
+ * Record a migration as applied without executing SQL
289
875
  */
290
- createTenant(tenantId: string, options?: CreateTenantOptions): Promise<void>;
876
+ recordMigration(pool: Pool, schemaName: string, migration: MigrationFile, format: DetectedFormat): Promise<void>;
291
877
  /**
292
- * Drop a tenant schema
878
+ * Get list of applied migrations for a tenant
293
879
  */
294
- dropTenant(tenantId: string, options?: DropTenantOptions): Promise<void>;
880
+ getAppliedMigrations(pool: Pool, schemaName: string, format: DetectedFormat): Promise<AppliedMigration[]>;
295
881
  /**
296
- * Check if a tenant schema exists
882
+ * Get pending migrations (not yet applied)
297
883
  */
298
- tenantExists(tenantId: string): Promise<boolean>;
884
+ getPendingMigrations(pool: Pool, schemaName: string, allMigrations: MigrationFile[], format: DetectedFormat): Promise<MigrationFile[]>;
299
885
  /**
300
- * Mark migrations as applied without executing SQL
301
- * Useful for syncing tracking state with already-applied migrations
886
+ * Check if a migration has been applied
302
887
  */
303
- markAsApplied(tenantId: string, options?: {
304
- onProgress?: MigrateOptions['onProgress'];
305
- }): Promise<TenantMigrationResult>;
888
+ private isMigrationApplied;
306
889
  /**
307
- * Mark migrations as applied for all tenants without executing SQL
308
- * Useful for syncing tracking state with already-applied migrations
890
+ * Apply a migration to a schema (execute SQL + record)
309
891
  */
310
- markAllAsApplied(options?: MigrateOptions): Promise<MigrationResults>;
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[]>);
311
946
  /**
312
- * Get sync status for all tenants
313
- * Detects divergences between migrations on disk and tracking in database
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
+ * ```
314
969
  */
315
- getSyncStatus(): Promise<SyncStatus>;
970
+ migrateAll(options?: BatchMigrateOptions): Promise<MigrationResults>;
316
971
  /**
317
- * Get sync status for a specific tenant
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
+ * ```
318
987
  */
319
- getTenantSyncStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantSyncStatus>;
988
+ migrateTenants(tenantIds: string[], options?: BatchMigrateOptions): Promise<MigrationResults>;
320
989
  /**
321
- * Mark missing migrations as applied for a tenant
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
+ * ```
322
1005
  */
323
- markMissing(tenantId: string): Promise<TenantSyncResult>;
1006
+ markAllAsApplied(options?: BatchMigrateOptions): Promise<MigrationResults>;
324
1007
  /**
325
- * Mark missing migrations as applied for all tenants
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
+ * ```
326
1020
  */
327
- markAllMissing(options?: SyncOptions): Promise<SyncResults>;
1021
+ getStatus(): Promise<TenantMigrationStatus[]>;
328
1022
  /**
329
- * Remove orphan migration records for a tenant
1023
+ * Create a skipped result for aborted operations
330
1024
  */
331
- cleanOrphans(tenantId: string): Promise<TenantSyncResult>;
1025
+ private createSkippedResult;
332
1026
  /**
333
- * Remove orphan migration records for all tenants
1027
+ * Create an error result for failed operations
334
1028
  */
335
- cleanAllOrphans(options?: SyncOptions): Promise<SyncResults>;
1029
+ private createErrorResult;
336
1030
  /**
337
- * Load migration files from the migrations folder
1031
+ * Aggregate individual migration results into a summary
338
1032
  */
339
- private loadMigrations;
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);
340
1089
  /**
341
- * Create a pool for a specific schema
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
342
1096
  */
343
- private createPool;
1097
+ cloneTenant(sourceTenantId: string, targetTenantId: string, options?: CloneTenantOptions): Promise<CloneTenantResult>;
1098
+ private createErrorResult;
1099
+ }
1100
+ /**
1101
+ * Factory function for Cloner
1102
+ *
1103
+ * @param config - Cloner configuration
1104
+ * @param dependencies - Cloner dependencies
1105
+ * @returns Cloner instance
1106
+ */
1107
+ declare function createCloner(config: ClonerConfig, dependencies: ClonerDependencies): Cloner;
1108
+
1109
+ /**
1110
+ * Configuration for SharedMigrationExecutor
1111
+ */
1112
+ interface SharedMigrationExecutorConfig {
1113
+ /** Schema name for shared tables (default: 'public') */
1114
+ schemaName?: string;
1115
+ /** Table name for tracking migrations */
1116
+ migrationsTable: string;
1117
+ /** Hooks for migration events */
1118
+ hooks?: {
1119
+ beforeMigration?: () => void | Promise<void>;
1120
+ afterMigration?: (migrationName: string, durationMs: number) => void | Promise<void>;
1121
+ };
1122
+ }
1123
+ /**
1124
+ * Dependencies for SharedMigrationExecutor
1125
+ */
1126
+ interface SharedMigrationExecutorDependencies {
1127
+ /** Create a database pool for the shared schema */
1128
+ createPool: () => Promise<Pool>;
1129
+ /** Check if migrations table exists */
1130
+ migrationsTableExists: (pool: Pool, schemaName: string) => Promise<boolean>;
1131
+ /** Ensure migrations table exists with correct format */
1132
+ ensureMigrationsTable: (pool: Pool, schemaName: string, format: DetectedFormat) => Promise<void>;
1133
+ /** Get or detect migration table format */
1134
+ getOrDetectFormat: (pool: Pool, schemaName: string) => Promise<DetectedFormat>;
1135
+ /** Load migrations from disk */
1136
+ loadMigrations: () => Promise<MigrationFile[]>;
1137
+ }
1138
+
1139
+ /**
1140
+ * Executor for shared schema migrations
1141
+ *
1142
+ * Handles migrations for the shared/public schema, separate from tenant schemas.
1143
+ * Used for tables that are shared across all tenants (e.g., plans, roles, permissions).
1144
+ *
1145
+ * @example
1146
+ * ```typescript
1147
+ * const executor = new SharedMigrationExecutor(config, dependencies);
1148
+ *
1149
+ * // Get status
1150
+ * const status = await executor.getStatus();
1151
+ * console.log(`Pending: ${status.pendingCount}`);
1152
+ *
1153
+ * // Apply migrations
1154
+ * const result = await executor.migrate();
1155
+ * console.log(`Applied: ${result.appliedMigrations.length}`);
1156
+ * ```
1157
+ */
1158
+ declare class SharedMigrationExecutor {
1159
+ private readonly config;
1160
+ private readonly deps;
1161
+ private readonly schemaName;
1162
+ constructor(config: SharedMigrationExecutorConfig, deps: SharedMigrationExecutorDependencies);
344
1163
  /**
345
- * Ensure migrations table exists with the correct format
1164
+ * Apply pending migrations to the shared schema
1165
+ *
1166
+ * @param options - Migration options (dryRun, onProgress)
1167
+ * @returns Migration result with applied migrations
1168
+ *
1169
+ * @example
1170
+ * ```typescript
1171
+ * const result = await executor.migrate({
1172
+ * dryRun: false,
1173
+ * onProgress: (status, name) => console.log(`${status}: ${name}`),
1174
+ * });
1175
+ *
1176
+ * if (result.success) {
1177
+ * console.log(`Applied ${result.appliedMigrations.length} migrations`);
1178
+ * }
1179
+ * ```
346
1180
  */
347
- private ensureMigrationsTable;
1181
+ migrate(options?: SharedMigrateOptions): Promise<SharedMigrationResult>;
348
1182
  /**
349
- * Check if migrations table exists
1183
+ * Mark migrations as applied without executing SQL
1184
+ *
1185
+ * Useful for syncing tracking state with already-applied migrations.
1186
+ *
1187
+ * @param options - Options with progress callback
1188
+ * @returns Result with list of marked migrations
350
1189
  */
351
- private migrationsTableExists;
1190
+ markAsApplied(options?: {
1191
+ onProgress?: SharedMigrateOptions['onProgress'];
1192
+ }): Promise<SharedMigrationResult>;
352
1193
  /**
353
- * Get applied migrations for a schema
1194
+ * Get migration status for the shared schema
1195
+ *
1196
+ * @returns Status with applied/pending counts
1197
+ */
1198
+ getStatus(): Promise<SharedMigrationStatus>;
1199
+ /**
1200
+ * Get list of applied migrations
354
1201
  */
355
1202
  private getAppliedMigrations;
356
1203
  /**
@@ -358,47 +1205,17 @@ declare class Migrator<TTenantSchema extends Record<string, unknown>, TSharedSch
358
1205
  */
359
1206
  private isMigrationApplied;
360
1207
  /**
361
- * Get or detect the format for a schema
362
- * Returns the configured format or auto-detects from existing table
363
- */
364
- private getOrDetectFormat;
365
- /**
366
- * Apply a migration to a schema
1208
+ * Apply a migration (execute SQL + record)
367
1209
  */
368
1210
  private applyMigration;
369
1211
  /**
370
1212
  * Record a migration as applied without executing SQL
371
- * Used by markAsApplied to sync tracking state
372
1213
  */
373
1214
  private recordMigration;
374
- /**
375
- * Create a skipped result
376
- */
377
- private createSkippedResult;
378
- /**
379
- * Create an error result
380
- */
381
- private createErrorResult;
382
- /**
383
- * Aggregate migration results
384
- */
385
- private aggregateResults;
386
- /**
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
396
- */
397
- private aggregateSyncResults;
398
1215
  }
399
1216
  /**
400
- * Create a migrator instance
1217
+ * Factory function to create a SharedMigrationExecutor
401
1218
  */
402
- declare function createMigrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig): Migrator<TTenantSchema, TSharedSchema>;
1219
+ declare function createSharedMigrationExecutor(config: SharedMigrationExecutorConfig, dependencies: SharedMigrationExecutorDependencies): SharedMigrationExecutor;
403
1220
 
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 };
1221
+ 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, SharedMigrateOptions, SharedMigrationExecutor, type SharedMigrationExecutorConfig, type SharedMigrationExecutorDependencies, SharedMigrationResult, SharedMigrationStatus, SyncManager, type SyncManagerConfig, type SyncManagerDependencies, SyncOptions, SyncResults, SyncStatus, TenantMigrationResult, TenantMigrationStatus, TenantSeedResult, TenantSyncResult, TenantSyncStatus, createBatchExecutor, createCloner, createMigrationExecutor, createSchemaManager, createSeeder, createSharedMigrationExecutor, createSyncManager };