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.
- package/dist/cli/index.js +4441 -742
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3758 -1335
- package/dist/index.js.map +1 -1
- package/dist/integrations/nestjs/index.js +479 -232
- package/dist/integrations/nestjs/index.js.map +1 -1
- package/dist/migrator/index.d.ts +986 -281
- package/dist/migrator/index.js +2799 -664
- package/dist/migrator/index.js.map +1 -1
- package/dist/migrator-BDgFzSh8.d.ts +824 -0
- package/package.json +1 -1
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { C as Config } from './types-BhK96FPC.js';
|
|
2
|
+
import { Pool } from 'pg';
|
|
3
|
+
import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
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
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Default format configuration for new tables
|
|
32
|
+
*/
|
|
33
|
+
declare const DEFAULT_FORMAT: DetectedFormat;
|
|
34
|
+
/**
|
|
35
|
+
* drizzle-kit format configuration
|
|
36
|
+
*/
|
|
37
|
+
declare const DRIZZLE_KIT_FORMAT: DetectedFormat;
|
|
38
|
+
/**
|
|
39
|
+
* Detect the format of an existing migrations table
|
|
40
|
+
*
|
|
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
|
|
45
|
+
*/
|
|
46
|
+
declare function detectTableFormat(pool: Pool, schemaName: string, tableName: string): Promise<DetectedFormat | null>;
|
|
47
|
+
/**
|
|
48
|
+
* Get the format configuration for a specific format type
|
|
49
|
+
*/
|
|
50
|
+
declare function getFormatConfig(format: TableFormat, tableName?: string): DetectedFormat;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Types for tenant cloning module
|
|
54
|
+
* @module clone/types
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Value to use for anonymization (v1: null or fixed value)
|
|
59
|
+
*/
|
|
60
|
+
type AnonymizeValue = null | string | number | boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Anonymization rules by table and column
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* const rules: AnonymizeRules = {
|
|
67
|
+
* users: {
|
|
68
|
+
* email: null,
|
|
69
|
+
* phone: null,
|
|
70
|
+
* ssn: '000-00-0000',
|
|
71
|
+
* },
|
|
72
|
+
* payments: {
|
|
73
|
+
* card_number: null,
|
|
74
|
+
* },
|
|
75
|
+
* };
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
interface AnonymizeRules {
|
|
79
|
+
[tableName: string]: {
|
|
80
|
+
[columnName: string]: AnonymizeValue;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Anonymization options
|
|
85
|
+
*/
|
|
86
|
+
interface AnonymizeOptions {
|
|
87
|
+
/** Enable anonymization */
|
|
88
|
+
enabled: boolean;
|
|
89
|
+
/** Rules per table/column */
|
|
90
|
+
rules?: AnonymizeRules;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Options for cloning a tenant
|
|
94
|
+
*/
|
|
95
|
+
interface CloneTenantOptions {
|
|
96
|
+
/** Include data (default: false, schema only) */
|
|
97
|
+
includeData?: boolean;
|
|
98
|
+
/** Anonymize sensitive data */
|
|
99
|
+
anonymize?: AnonymizeOptions;
|
|
100
|
+
/** Tables to exclude from cloning */
|
|
101
|
+
excludeTables?: string[];
|
|
102
|
+
/** Progress callback */
|
|
103
|
+
onProgress?: CloneProgressCallback;
|
|
104
|
+
/** Error handler */
|
|
105
|
+
onError?: (error: Error) => 'continue' | 'abort';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Progress status for cloning operation
|
|
109
|
+
*/
|
|
110
|
+
type CloneProgressStatus = 'starting' | 'introspecting' | 'creating_schema' | 'creating_tables' | 'creating_indexes' | 'creating_constraints' | 'copying_data' | 'completed' | 'failed';
|
|
111
|
+
/**
|
|
112
|
+
* Progress callback for cloning
|
|
113
|
+
*/
|
|
114
|
+
type CloneProgressCallback = (status: CloneProgressStatus, details?: {
|
|
115
|
+
table?: string;
|
|
116
|
+
progress?: number;
|
|
117
|
+
total?: number;
|
|
118
|
+
}) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Result of cloning a tenant
|
|
121
|
+
*/
|
|
122
|
+
interface CloneTenantResult {
|
|
123
|
+
/** Source tenant ID */
|
|
124
|
+
sourceTenant: string;
|
|
125
|
+
/** Target tenant ID */
|
|
126
|
+
targetTenant: string;
|
|
127
|
+
/** Target schema name */
|
|
128
|
+
targetSchema: string;
|
|
129
|
+
/** Whether the operation was successful */
|
|
130
|
+
success: boolean;
|
|
131
|
+
/** Error message if failed */
|
|
132
|
+
error?: string;
|
|
133
|
+
/** Tables cloned */
|
|
134
|
+
tables: string[];
|
|
135
|
+
/** Number of rows copied (if includeData) */
|
|
136
|
+
rowsCopied?: number;
|
|
137
|
+
/** Duration in milliseconds */
|
|
138
|
+
durationMs: number;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Configuration for Cloner
|
|
142
|
+
*/
|
|
143
|
+
interface ClonerConfig {
|
|
144
|
+
/** Migrations table name (excluded from data copy) */
|
|
145
|
+
migrationsTable?: string;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Dependencies for Cloner
|
|
149
|
+
*/
|
|
150
|
+
interface ClonerDependencies {
|
|
151
|
+
/** Create pool for specific schema */
|
|
152
|
+
createPool: (schemaName: string) => Promise<Pool>;
|
|
153
|
+
/** Create pool without schema (root) */
|
|
154
|
+
createRootPool: () => Promise<Pool>;
|
|
155
|
+
/** Schema name template function */
|
|
156
|
+
schemaNameTemplate: (tenantId: string) => string;
|
|
157
|
+
/** Check if schema exists */
|
|
158
|
+
schemaExists: (tenantId: string) => Promise<boolean>;
|
|
159
|
+
/** Create schema */
|
|
160
|
+
createSchema: (tenantId: string) => Promise<void>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Seed function signature
|
|
165
|
+
* Called with the tenant database instance and tenant ID
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* ```typescript
|
|
169
|
+
* const seed: SeedFunction = async (db, tenantId) => {
|
|
170
|
+
* await db.insert(roles).values([
|
|
171
|
+
* { name: 'admin', permissions: ['*'] },
|
|
172
|
+
* { name: 'user', permissions: ['read'] },
|
|
173
|
+
* ]);
|
|
174
|
+
* };
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
type SeedFunction<TSchema extends Record<string, unknown> = Record<string, unknown>> = (db: PostgresJsDatabase<TSchema>, tenantId: string) => Promise<void>;
|
|
178
|
+
/**
|
|
179
|
+
* Seed result for a single tenant
|
|
180
|
+
*/
|
|
181
|
+
interface TenantSeedResult {
|
|
182
|
+
tenantId: string;
|
|
183
|
+
schemaName: string;
|
|
184
|
+
success: boolean;
|
|
185
|
+
error?: string;
|
|
186
|
+
durationMs: number;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Aggregate seed results
|
|
190
|
+
*/
|
|
191
|
+
interface SeedResults {
|
|
192
|
+
total: number;
|
|
193
|
+
succeeded: number;
|
|
194
|
+
failed: number;
|
|
195
|
+
skipped: number;
|
|
196
|
+
details: TenantSeedResult[];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Options for seed operations
|
|
200
|
+
*/
|
|
201
|
+
interface SeedOptions {
|
|
202
|
+
/** Number of concurrent seed operations */
|
|
203
|
+
concurrency?: number;
|
|
204
|
+
/** Progress callback */
|
|
205
|
+
onProgress?: (tenantId: string, status: 'starting' | 'seeding' | 'completed' | 'failed' | 'skipped') => void;
|
|
206
|
+
/** Error handler */
|
|
207
|
+
onError?: (tenantId: string, error: Error) => 'continue' | 'abort';
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Migration file metadata
|
|
211
|
+
*/
|
|
212
|
+
interface MigrationFile {
|
|
213
|
+
/** Migration file name */
|
|
214
|
+
name: string;
|
|
215
|
+
/** Full file path */
|
|
216
|
+
path: string;
|
|
217
|
+
/** SQL content */
|
|
218
|
+
sql: string;
|
|
219
|
+
/** Timestamp extracted from filename */
|
|
220
|
+
timestamp: number;
|
|
221
|
+
/** SHA-256 hash of file content (for drizzle-kit compatibility) */
|
|
222
|
+
hash: string;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Migration status for a tenant
|
|
226
|
+
*/
|
|
227
|
+
interface TenantMigrationStatus {
|
|
228
|
+
tenantId: string;
|
|
229
|
+
schemaName: string;
|
|
230
|
+
appliedCount: number;
|
|
231
|
+
pendingCount: number;
|
|
232
|
+
pendingMigrations: string[];
|
|
233
|
+
status: 'ok' | 'behind' | 'error';
|
|
234
|
+
error?: string;
|
|
235
|
+
/** Detected table format (null for new tenants without migrations table) */
|
|
236
|
+
format: TableFormat | null;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Migration result for a single tenant
|
|
240
|
+
*/
|
|
241
|
+
interface TenantMigrationResult {
|
|
242
|
+
tenantId: string;
|
|
243
|
+
schemaName: string;
|
|
244
|
+
success: boolean;
|
|
245
|
+
appliedMigrations: string[];
|
|
246
|
+
error?: string;
|
|
247
|
+
durationMs: number;
|
|
248
|
+
/** Table format used for this migration */
|
|
249
|
+
format?: TableFormat;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Aggregate migration results
|
|
253
|
+
*/
|
|
254
|
+
interface MigrationResults {
|
|
255
|
+
total: number;
|
|
256
|
+
succeeded: number;
|
|
257
|
+
failed: number;
|
|
258
|
+
skipped: number;
|
|
259
|
+
details: TenantMigrationResult[];
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Progress callback for migrations
|
|
263
|
+
*/
|
|
264
|
+
type MigrationProgressCallback = (tenantId: string, status: 'starting' | 'migrating' | 'completed' | 'failed' | 'skipped', migrationName?: string) => void;
|
|
265
|
+
/**
|
|
266
|
+
* Error handler for migrations
|
|
267
|
+
*/
|
|
268
|
+
type MigrationErrorHandler = (tenantId: string, error: Error) => 'continue' | 'abort';
|
|
269
|
+
/**
|
|
270
|
+
* Migration hooks
|
|
271
|
+
*/
|
|
272
|
+
interface MigrationHooks {
|
|
273
|
+
/** Called before migrating a tenant */
|
|
274
|
+
beforeTenant?: (tenantId: string) => void | Promise<void>;
|
|
275
|
+
/** Called after migrating a tenant */
|
|
276
|
+
afterTenant?: (tenantId: string, result: TenantMigrationResult) => void | Promise<void>;
|
|
277
|
+
/** Called before applying a migration */
|
|
278
|
+
beforeMigration?: (tenantId: string, migrationName: string) => void | Promise<void>;
|
|
279
|
+
/** Called after applying a migration */
|
|
280
|
+
afterMigration?: (tenantId: string, migrationName: string, durationMs: number) => void | Promise<void>;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Migrator configuration
|
|
284
|
+
*/
|
|
285
|
+
interface MigratorConfig {
|
|
286
|
+
/** Path to tenant migrations folder */
|
|
287
|
+
migrationsFolder: string;
|
|
288
|
+
/** Table name for tracking migrations */
|
|
289
|
+
migrationsTable?: string;
|
|
290
|
+
/** Function to discover tenant IDs */
|
|
291
|
+
tenantDiscovery: () => Promise<string[]>;
|
|
292
|
+
/** Migration hooks */
|
|
293
|
+
hooks?: MigrationHooks;
|
|
294
|
+
/**
|
|
295
|
+
* Table format for tracking migrations
|
|
296
|
+
* - "auto": Auto-detect existing format, use defaultFormat for new tables
|
|
297
|
+
* - "name": Use filename (drizzle-multitenant native)
|
|
298
|
+
* - "hash": Use SHA-256 hash
|
|
299
|
+
* - "drizzle-kit": Exact drizzle-kit format (hash + bigint timestamp)
|
|
300
|
+
* @default "auto"
|
|
301
|
+
*/
|
|
302
|
+
tableFormat?: 'auto' | TableFormat;
|
|
303
|
+
/**
|
|
304
|
+
* When using "auto" format and no table exists, which format to create
|
|
305
|
+
* @default "name"
|
|
306
|
+
*/
|
|
307
|
+
defaultFormat?: TableFormat;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Migrate options
|
|
311
|
+
*/
|
|
312
|
+
interface MigrateOptions {
|
|
313
|
+
/** Number of concurrent migrations */
|
|
314
|
+
concurrency?: number;
|
|
315
|
+
/** Progress callback */
|
|
316
|
+
onProgress?: MigrationProgressCallback;
|
|
317
|
+
/** Error handler */
|
|
318
|
+
onError?: MigrationErrorHandler;
|
|
319
|
+
/** Dry run mode */
|
|
320
|
+
dryRun?: boolean;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Tenant creation options
|
|
324
|
+
*/
|
|
325
|
+
interface CreateTenantOptions {
|
|
326
|
+
/** Apply all migrations after creating schema */
|
|
327
|
+
migrate?: boolean;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Tenant drop options
|
|
331
|
+
*/
|
|
332
|
+
interface DropTenantOptions {
|
|
333
|
+
/** Skip confirmation (force drop) */
|
|
334
|
+
force?: boolean;
|
|
335
|
+
/** Cascade drop */
|
|
336
|
+
cascade?: boolean;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Applied migration record
|
|
340
|
+
*/
|
|
341
|
+
interface AppliedMigration {
|
|
342
|
+
id: number;
|
|
343
|
+
/** Migration identifier (name or hash depending on format) */
|
|
344
|
+
identifier: string;
|
|
345
|
+
/** Migration name (only available in name-based format) */
|
|
346
|
+
name?: string;
|
|
347
|
+
/** Migration hash (only available in hash-based format) */
|
|
348
|
+
hash?: string;
|
|
349
|
+
appliedAt: Date;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Sync status for a single tenant
|
|
353
|
+
*/
|
|
354
|
+
interface TenantSyncStatus {
|
|
355
|
+
tenantId: string;
|
|
356
|
+
schemaName: string;
|
|
357
|
+
/** Migrations in disk but not tracked in database */
|
|
358
|
+
missing: string[];
|
|
359
|
+
/** Migrations tracked in database but not found in disk */
|
|
360
|
+
orphans: string[];
|
|
361
|
+
/** Whether the tenant is in sync */
|
|
362
|
+
inSync: boolean;
|
|
363
|
+
/** Table format used */
|
|
364
|
+
format: TableFormat | null;
|
|
365
|
+
/** Error if any */
|
|
366
|
+
error?: string;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Aggregate sync status
|
|
370
|
+
*/
|
|
371
|
+
interface SyncStatus {
|
|
372
|
+
total: number;
|
|
373
|
+
inSync: number;
|
|
374
|
+
outOfSync: number;
|
|
375
|
+
error: number;
|
|
376
|
+
details: TenantSyncStatus[];
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Sync result for a single tenant
|
|
380
|
+
*/
|
|
381
|
+
interface TenantSyncResult {
|
|
382
|
+
tenantId: string;
|
|
383
|
+
schemaName: string;
|
|
384
|
+
success: boolean;
|
|
385
|
+
/** Migrations that were marked as applied */
|
|
386
|
+
markedMigrations: string[];
|
|
387
|
+
/** Orphan records that were removed */
|
|
388
|
+
removedOrphans: string[];
|
|
389
|
+
error?: string;
|
|
390
|
+
durationMs: number;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Aggregate sync results
|
|
394
|
+
*/
|
|
395
|
+
interface SyncResults {
|
|
396
|
+
total: number;
|
|
397
|
+
succeeded: number;
|
|
398
|
+
failed: number;
|
|
399
|
+
details: TenantSyncResult[];
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Options for sync operations
|
|
403
|
+
*/
|
|
404
|
+
interface SyncOptions {
|
|
405
|
+
/** Number of concurrent operations */
|
|
406
|
+
concurrency?: number;
|
|
407
|
+
/** Progress callback */
|
|
408
|
+
onProgress?: (tenantId: string, status: 'starting' | 'syncing' | 'completed' | 'failed') => void;
|
|
409
|
+
/** Error handler */
|
|
410
|
+
onError?: MigrationErrorHandler;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Column information from database introspection
|
|
414
|
+
*/
|
|
415
|
+
interface ColumnInfo {
|
|
416
|
+
/** Column name */
|
|
417
|
+
name: string;
|
|
418
|
+
/** PostgreSQL data type */
|
|
419
|
+
dataType: string;
|
|
420
|
+
/** Full data type (e.g., varchar(255)) */
|
|
421
|
+
udtName: string;
|
|
422
|
+
/** Whether column is nullable */
|
|
423
|
+
isNullable: boolean;
|
|
424
|
+
/** Default value expression */
|
|
425
|
+
columnDefault: string | null;
|
|
426
|
+
/** Character maximum length for varchar/char */
|
|
427
|
+
characterMaximumLength: number | null;
|
|
428
|
+
/** Numeric precision for numeric types */
|
|
429
|
+
numericPrecision: number | null;
|
|
430
|
+
/** Numeric scale for numeric types */
|
|
431
|
+
numericScale: number | null;
|
|
432
|
+
/** Ordinal position in table */
|
|
433
|
+
ordinalPosition: number;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Index information from database introspection
|
|
437
|
+
*/
|
|
438
|
+
interface IndexInfo {
|
|
439
|
+
/** Index name */
|
|
440
|
+
name: string;
|
|
441
|
+
/** Column names in the index */
|
|
442
|
+
columns: string[];
|
|
443
|
+
/** Whether index is unique */
|
|
444
|
+
isUnique: boolean;
|
|
445
|
+
/** Whether index is primary key */
|
|
446
|
+
isPrimary: boolean;
|
|
447
|
+
/** Index definition SQL */
|
|
448
|
+
definition: string;
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Constraint information from database introspection
|
|
452
|
+
*/
|
|
453
|
+
interface ConstraintInfo {
|
|
454
|
+
/** Constraint name */
|
|
455
|
+
name: string;
|
|
456
|
+
/** Constraint type (PRIMARY KEY, FOREIGN KEY, UNIQUE, CHECK) */
|
|
457
|
+
type: 'PRIMARY KEY' | 'FOREIGN KEY' | 'UNIQUE' | 'CHECK';
|
|
458
|
+
/** Columns involved in constraint */
|
|
459
|
+
columns: string[];
|
|
460
|
+
/** Foreign table (for foreign keys) */
|
|
461
|
+
foreignTable?: string;
|
|
462
|
+
/** Foreign columns (for foreign keys) */
|
|
463
|
+
foreignColumns?: string[];
|
|
464
|
+
/** Check expression (for check constraints) */
|
|
465
|
+
checkExpression?: string;
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Table schema information
|
|
469
|
+
*/
|
|
470
|
+
interface TableSchema {
|
|
471
|
+
/** Table name */
|
|
472
|
+
name: string;
|
|
473
|
+
/** Columns in the table */
|
|
474
|
+
columns: ColumnInfo[];
|
|
475
|
+
/** Indexes on the table */
|
|
476
|
+
indexes: IndexInfo[];
|
|
477
|
+
/** Constraints on the table */
|
|
478
|
+
constraints: ConstraintInfo[];
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Full schema for a tenant
|
|
482
|
+
*/
|
|
483
|
+
interface TenantSchema {
|
|
484
|
+
/** Tenant ID */
|
|
485
|
+
tenantId: string;
|
|
486
|
+
/** Schema name */
|
|
487
|
+
schemaName: string;
|
|
488
|
+
/** Tables in the schema */
|
|
489
|
+
tables: TableSchema[];
|
|
490
|
+
/** Introspection timestamp */
|
|
491
|
+
introspectedAt: Date;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Column drift details
|
|
495
|
+
*/
|
|
496
|
+
interface ColumnDrift {
|
|
497
|
+
/** Column name */
|
|
498
|
+
column: string;
|
|
499
|
+
/** Type of drift */
|
|
500
|
+
type: 'missing' | 'extra' | 'type_mismatch' | 'nullable_mismatch' | 'default_mismatch';
|
|
501
|
+
/** Expected value (from reference) */
|
|
502
|
+
expected?: string | boolean | null;
|
|
503
|
+
/** Actual value (from tenant) */
|
|
504
|
+
actual?: string | boolean | null;
|
|
505
|
+
/** Human-readable description */
|
|
506
|
+
description: string;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Index drift details
|
|
510
|
+
*/
|
|
511
|
+
interface IndexDrift {
|
|
512
|
+
/** Index name */
|
|
513
|
+
index: string;
|
|
514
|
+
/** Type of drift */
|
|
515
|
+
type: 'missing' | 'extra' | 'definition_mismatch';
|
|
516
|
+
/** Expected definition */
|
|
517
|
+
expected?: string;
|
|
518
|
+
/** Actual definition */
|
|
519
|
+
actual?: string;
|
|
520
|
+
/** Human-readable description */
|
|
521
|
+
description: string;
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Constraint drift details
|
|
525
|
+
*/
|
|
526
|
+
interface ConstraintDrift {
|
|
527
|
+
/** Constraint name */
|
|
528
|
+
constraint: string;
|
|
529
|
+
/** Type of drift */
|
|
530
|
+
type: 'missing' | 'extra' | 'definition_mismatch';
|
|
531
|
+
/** Expected details */
|
|
532
|
+
expected?: string;
|
|
533
|
+
/** Actual details */
|
|
534
|
+
actual?: string;
|
|
535
|
+
/** Human-readable description */
|
|
536
|
+
description: string;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Table drift details
|
|
540
|
+
*/
|
|
541
|
+
interface TableDrift {
|
|
542
|
+
/** Table name */
|
|
543
|
+
table: string;
|
|
544
|
+
/** Whether the entire table is missing or extra */
|
|
545
|
+
status: 'ok' | 'missing' | 'extra' | 'drifted';
|
|
546
|
+
/** Column drifts */
|
|
547
|
+
columns: ColumnDrift[];
|
|
548
|
+
/** Index drifts */
|
|
549
|
+
indexes: IndexDrift[];
|
|
550
|
+
/** Constraints drifts */
|
|
551
|
+
constraints: ConstraintDrift[];
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Schema drift for a single tenant
|
|
555
|
+
*/
|
|
556
|
+
interface TenantSchemaDrift {
|
|
557
|
+
/** Tenant ID */
|
|
558
|
+
tenantId: string;
|
|
559
|
+
/** Schema name */
|
|
560
|
+
schemaName: string;
|
|
561
|
+
/** Whether schema has drift */
|
|
562
|
+
hasDrift: boolean;
|
|
563
|
+
/** Table-level drifts */
|
|
564
|
+
tables: TableDrift[];
|
|
565
|
+
/** Total number of issues */
|
|
566
|
+
issueCount: number;
|
|
567
|
+
/** Error if introspection failed */
|
|
568
|
+
error?: string;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Aggregate schema drift status
|
|
572
|
+
*/
|
|
573
|
+
interface SchemaDriftStatus {
|
|
574
|
+
/** Reference tenant used for comparison */
|
|
575
|
+
referenceTenant: string;
|
|
576
|
+
/** Total tenants checked */
|
|
577
|
+
total: number;
|
|
578
|
+
/** Tenants without drift */
|
|
579
|
+
noDrift: number;
|
|
580
|
+
/** Tenants with drift */
|
|
581
|
+
withDrift: number;
|
|
582
|
+
/** Tenants with errors */
|
|
583
|
+
error: number;
|
|
584
|
+
/** Detailed results per tenant */
|
|
585
|
+
details: TenantSchemaDrift[];
|
|
586
|
+
/** Timestamp of the check */
|
|
587
|
+
timestamp: string;
|
|
588
|
+
/** Duration of the check in ms */
|
|
589
|
+
durationMs: number;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Options for schema drift detection
|
|
593
|
+
*/
|
|
594
|
+
interface SchemaDriftOptions {
|
|
595
|
+
/** Tenant ID to use as reference (default: first tenant) */
|
|
596
|
+
referenceTenant?: string;
|
|
597
|
+
/** Specific tenant IDs to check (default: all tenants) */
|
|
598
|
+
tenantIds?: string[];
|
|
599
|
+
/** Number of concurrent checks */
|
|
600
|
+
concurrency?: number;
|
|
601
|
+
/** Whether to include index comparison */
|
|
602
|
+
includeIndexes?: boolean;
|
|
603
|
+
/** Whether to include constraint comparison */
|
|
604
|
+
includeConstraints?: boolean;
|
|
605
|
+
/** Tables to exclude from comparison */
|
|
606
|
+
excludeTables?: string[];
|
|
607
|
+
/** Progress callback */
|
|
608
|
+
onProgress?: (tenantId: string, status: 'starting' | 'introspecting' | 'comparing' | 'completed' | 'failed') => void;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Parallel migration engine for multi-tenant applications
|
|
613
|
+
*/
|
|
614
|
+
declare class Migrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>> {
|
|
615
|
+
private readonly migratorConfig;
|
|
616
|
+
private readonly migrationsTable;
|
|
617
|
+
private readonly schemaManager;
|
|
618
|
+
private readonly driftDetector;
|
|
619
|
+
private readonly seeder;
|
|
620
|
+
private readonly syncManager;
|
|
621
|
+
private readonly migrationExecutor;
|
|
622
|
+
private readonly batchExecutor;
|
|
623
|
+
private readonly cloner;
|
|
624
|
+
constructor(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig);
|
|
625
|
+
/**
|
|
626
|
+
* Migrate all tenants in parallel
|
|
627
|
+
*
|
|
628
|
+
* Delegates to BatchExecutor for parallel migration operations.
|
|
629
|
+
*/
|
|
630
|
+
migrateAll(options?: MigrateOptions): Promise<MigrationResults>;
|
|
631
|
+
/**
|
|
632
|
+
* Migrate a single tenant
|
|
633
|
+
*
|
|
634
|
+
* Delegates to MigrationExecutor for single tenant operations.
|
|
635
|
+
*/
|
|
636
|
+
migrateTenant(tenantId: string, migrations?: MigrationFile[], options?: {
|
|
637
|
+
dryRun?: boolean;
|
|
638
|
+
onProgress?: MigrateOptions['onProgress'];
|
|
639
|
+
}): Promise<TenantMigrationResult>;
|
|
640
|
+
/**
|
|
641
|
+
* Migrate specific tenants
|
|
642
|
+
*
|
|
643
|
+
* Delegates to BatchExecutor for parallel migration operations.
|
|
644
|
+
*/
|
|
645
|
+
migrateTenants(tenantIds: string[], options?: MigrateOptions): Promise<MigrationResults>;
|
|
646
|
+
/**
|
|
647
|
+
* Get migration status for all tenants
|
|
648
|
+
*
|
|
649
|
+
* Delegates to BatchExecutor for status operations.
|
|
650
|
+
*/
|
|
651
|
+
getStatus(): Promise<TenantMigrationStatus[]>;
|
|
652
|
+
/**
|
|
653
|
+
* Get migration status for a specific tenant
|
|
654
|
+
*
|
|
655
|
+
* Delegates to MigrationExecutor for single tenant operations.
|
|
656
|
+
*/
|
|
657
|
+
getTenantStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantMigrationStatus>;
|
|
658
|
+
/**
|
|
659
|
+
* Create a new tenant schema and optionally apply migrations
|
|
660
|
+
*/
|
|
661
|
+
createTenant(tenantId: string, options?: CreateTenantOptions): Promise<void>;
|
|
662
|
+
/**
|
|
663
|
+
* Drop a tenant schema
|
|
664
|
+
*/
|
|
665
|
+
dropTenant(tenantId: string, options?: DropTenantOptions): Promise<void>;
|
|
666
|
+
/**
|
|
667
|
+
* Check if a tenant schema exists
|
|
668
|
+
*/
|
|
669
|
+
tenantExists(tenantId: string): Promise<boolean>;
|
|
670
|
+
/**
|
|
671
|
+
* Clone a tenant to a new tenant
|
|
672
|
+
*
|
|
673
|
+
* By default, clones only schema structure. Use includeData to copy data.
|
|
674
|
+
*
|
|
675
|
+
* @example
|
|
676
|
+
* ```typescript
|
|
677
|
+
* // Schema-only clone
|
|
678
|
+
* await migrator.cloneTenant('production', 'dev');
|
|
679
|
+
*
|
|
680
|
+
* // Clone with data
|
|
681
|
+
* await migrator.cloneTenant('production', 'dev', { includeData: true });
|
|
682
|
+
*
|
|
683
|
+
* // Clone with anonymization
|
|
684
|
+
* await migrator.cloneTenant('production', 'dev', {
|
|
685
|
+
* includeData: true,
|
|
686
|
+
* anonymize: {
|
|
687
|
+
* enabled: true,
|
|
688
|
+
* rules: {
|
|
689
|
+
* users: { email: null, phone: null },
|
|
690
|
+
* },
|
|
691
|
+
* },
|
|
692
|
+
* });
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
cloneTenant(sourceTenantId: string, targetTenantId: string, options?: CloneTenantOptions): Promise<CloneTenantResult>;
|
|
696
|
+
/**
|
|
697
|
+
* Mark migrations as applied without executing SQL
|
|
698
|
+
* Useful for syncing tracking state with already-applied migrations
|
|
699
|
+
*
|
|
700
|
+
* Delegates to MigrationExecutor for single tenant operations.
|
|
701
|
+
*/
|
|
702
|
+
markAsApplied(tenantId: string, options?: {
|
|
703
|
+
onProgress?: MigrateOptions['onProgress'];
|
|
704
|
+
}): Promise<TenantMigrationResult>;
|
|
705
|
+
/**
|
|
706
|
+
* Mark migrations as applied for all tenants without executing SQL
|
|
707
|
+
* Useful for syncing tracking state with already-applied migrations
|
|
708
|
+
*
|
|
709
|
+
* Delegates to BatchExecutor for parallel operations.
|
|
710
|
+
*/
|
|
711
|
+
markAllAsApplied(options?: MigrateOptions): Promise<MigrationResults>;
|
|
712
|
+
/**
|
|
713
|
+
* Get sync status for all tenants
|
|
714
|
+
* Detects divergences between migrations on disk and tracking in database
|
|
715
|
+
*/
|
|
716
|
+
getSyncStatus(): Promise<SyncStatus>;
|
|
717
|
+
/**
|
|
718
|
+
* Get sync status for a specific tenant
|
|
719
|
+
*/
|
|
720
|
+
getTenantSyncStatus(tenantId: string, migrations?: MigrationFile[]): Promise<TenantSyncStatus>;
|
|
721
|
+
/**
|
|
722
|
+
* Mark missing migrations as applied for a tenant
|
|
723
|
+
*/
|
|
724
|
+
markMissing(tenantId: string): Promise<TenantSyncResult>;
|
|
725
|
+
/**
|
|
726
|
+
* Mark missing migrations as applied for all tenants
|
|
727
|
+
*/
|
|
728
|
+
markAllMissing(options?: SyncOptions): Promise<SyncResults>;
|
|
729
|
+
/**
|
|
730
|
+
* Remove orphan migration records for a tenant
|
|
731
|
+
*/
|
|
732
|
+
cleanOrphans(tenantId: string): Promise<TenantSyncResult>;
|
|
733
|
+
/**
|
|
734
|
+
* Remove orphan migration records for all tenants
|
|
735
|
+
*/
|
|
736
|
+
cleanAllOrphans(options?: SyncOptions): Promise<SyncResults>;
|
|
737
|
+
/**
|
|
738
|
+
* Seed a single tenant with initial data
|
|
739
|
+
*
|
|
740
|
+
* @example
|
|
741
|
+
* ```typescript
|
|
742
|
+
* const seed: SeedFunction = async (db, tenantId) => {
|
|
743
|
+
* await db.insert(roles).values([
|
|
744
|
+
* { name: 'admin', permissions: ['*'] },
|
|
745
|
+
* { name: 'user', permissions: ['read'] },
|
|
746
|
+
* ]);
|
|
747
|
+
* };
|
|
748
|
+
*
|
|
749
|
+
* await migrator.seedTenant('tenant-123', seed);
|
|
750
|
+
* ```
|
|
751
|
+
*/
|
|
752
|
+
seedTenant(tenantId: string, seedFn: SeedFunction<TTenantSchema>): Promise<TenantSeedResult>;
|
|
753
|
+
/**
|
|
754
|
+
* Seed all tenants with initial data in parallel
|
|
755
|
+
*
|
|
756
|
+
* @example
|
|
757
|
+
* ```typescript
|
|
758
|
+
* const seed: SeedFunction = async (db, tenantId) => {
|
|
759
|
+
* await db.insert(roles).values([
|
|
760
|
+
* { name: 'admin', permissions: ['*'] },
|
|
761
|
+
* ]);
|
|
762
|
+
* };
|
|
763
|
+
*
|
|
764
|
+
* await migrator.seedAll(seed, { concurrency: 10 });
|
|
765
|
+
* ```
|
|
766
|
+
*/
|
|
767
|
+
seedAll(seedFn: SeedFunction<TTenantSchema>, options?: SeedOptions): Promise<SeedResults>;
|
|
768
|
+
/**
|
|
769
|
+
* Seed specific tenants with initial data
|
|
770
|
+
*/
|
|
771
|
+
seedTenants(tenantIds: string[], seedFn: SeedFunction<TTenantSchema>, options?: SeedOptions): Promise<SeedResults>;
|
|
772
|
+
/**
|
|
773
|
+
* Load migration files from the migrations folder
|
|
774
|
+
*/
|
|
775
|
+
private loadMigrations;
|
|
776
|
+
/**
|
|
777
|
+
* Get or detect the format for a schema
|
|
778
|
+
* Returns the configured format or auto-detects from existing table
|
|
779
|
+
*
|
|
780
|
+
* Note: This method is shared with SyncManager and MigrationExecutor via dependency injection.
|
|
781
|
+
*/
|
|
782
|
+
private getOrDetectFormat;
|
|
783
|
+
/**
|
|
784
|
+
* Detect schema drift across all tenants
|
|
785
|
+
* Compares each tenant's schema against a reference tenant (first tenant by default)
|
|
786
|
+
*
|
|
787
|
+
* @example
|
|
788
|
+
* ```typescript
|
|
789
|
+
* const drift = await migrator.getSchemaDrift();
|
|
790
|
+
* if (drift.withDrift > 0) {
|
|
791
|
+
* console.log('Schema drift detected!');
|
|
792
|
+
* for (const tenant of drift.details) {
|
|
793
|
+
* if (tenant.hasDrift) {
|
|
794
|
+
* console.log(`Tenant ${tenant.tenantId} has drift:`);
|
|
795
|
+
* for (const table of tenant.tables) {
|
|
796
|
+
* for (const col of table.columns) {
|
|
797
|
+
* console.log(` - ${table.table}.${col.column}: ${col.description}`);
|
|
798
|
+
* }
|
|
799
|
+
* }
|
|
800
|
+
* }
|
|
801
|
+
* }
|
|
802
|
+
* }
|
|
803
|
+
* ```
|
|
804
|
+
*/
|
|
805
|
+
getSchemaDrift(options?: SchemaDriftOptions): Promise<SchemaDriftStatus>;
|
|
806
|
+
/**
|
|
807
|
+
* Get schema drift for a specific tenant compared to a reference
|
|
808
|
+
*/
|
|
809
|
+
getTenantSchemaDrift(tenantId: string, referenceTenantId: string, options?: Pick<SchemaDriftOptions, 'includeIndexes' | 'includeConstraints' | 'excludeTables'>): Promise<TenantSchemaDrift>;
|
|
810
|
+
/**
|
|
811
|
+
* Introspect the schema of a tenant
|
|
812
|
+
*/
|
|
813
|
+
introspectTenantSchema(tenantId: string, options?: {
|
|
814
|
+
includeIndexes?: boolean;
|
|
815
|
+
includeConstraints?: boolean;
|
|
816
|
+
excludeTables?: string[];
|
|
817
|
+
}): Promise<TenantSchema | null>;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* Create a migrator instance
|
|
821
|
+
*/
|
|
822
|
+
declare function createMigrator<TTenantSchema extends Record<string, unknown>, TSharedSchema extends Record<string, unknown>>(tenantConfig: Config<TTenantSchema, TSharedSchema>, migratorConfig: MigratorConfig): Migrator<TTenantSchema, TSharedSchema>;
|
|
823
|
+
|
|
824
|
+
export { type AppliedMigration as A, type TableFormat as B, type CreateTenantOptions as C, type DropTenantOptions as D, type ColumnInfo as E, type ConstraintInfo as F, type TableSchema as G, type TenantSchema as H, type IndexInfo as I, type ColumnDrift as J, type IndexDrift as K, type ConstraintDrift as L, Migrator as M, type TableDrift as N, type TenantSchemaDrift as O, type SchemaDriftStatus as P, type SchemaDriftOptions as Q, type CloneProgressCallback as R, type SeedFunction as S, type TenantMigrationResult as T, type CloneProgressStatus as U, type AnonymizeOptions as V, type AnonymizeRules as W, type AnonymizeValue as X, type MigratorConfig as a, type MigrationFile as b, createMigrator as c, type MigrateOptions as d, type MigrationResults as e, type TenantMigrationStatus as f, type MigrationHooks as g, type MigrationProgressCallback as h, type MigrationErrorHandler as i, type SeedOptions as j, type TenantSeedResult as k, type SeedResults as l, type DetectedFormat as m, type SyncStatus as n, type TenantSyncStatus as o, type TenantSyncResult as p, type SyncOptions as q, type SyncResults as r, type ClonerConfig as s, type ClonerDependencies as t, type CloneTenantOptions as u, type CloneTenantResult as v, detectTableFormat as w, getFormatConfig as x, DEFAULT_FORMAT as y, DRIZZLE_KIT_FORMAT as z };
|