drizzle-multitenant 1.0.5 → 1.0.7
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/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
# Proposal: Melhorias Identificadas
|
|
1
|
+
# Proposal: Melhorias Identificadas
|
|
2
2
|
|
|
3
3
|
> **Status**: Proposta
|
|
4
|
-
> **Origem**: Integração com PrimeSys-v2
|
|
5
4
|
> **Data**: 2024-12-23
|
|
6
5
|
|
|
7
6
|
## Contexto
|
|
8
7
|
|
|
9
|
-
Durante a integração do `drizzle-multitenant` no projeto
|
|
8
|
+
Durante a integração do `drizzle-multitenant` no projeto, foram identificadas melhorias que beneficiariam o pacote e outros usuários.
|
|
10
9
|
|
|
11
10
|
---
|
|
12
11
|
|
|
@@ -381,5 +380,4 @@ const result = await db
|
|
|
381
380
|
|
|
382
381
|
## Referências
|
|
383
382
|
|
|
384
|
-
- [PrimeSys-v2 Migration Proposal](../../PrimeSys-v2/proposals/backlog/drizzle-multitenant-migration.md)
|
|
385
383
|
- [drizzle-multitenant Roadmap](../roadmap.md)
|
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
# Proposal: Drizzle-Kit Native Compatibility
|
|
2
|
-
|
|
3
|
-
> **Status**: Proposal
|
|
4
|
-
> **Priority**: P0 (Critical for Adoption)
|
|
5
|
-
> **Effort**: 6-8 hours
|
|
6
|
-
> **Date**: 2024-12-24
|
|
7
|
-
|
|
8
|
-
## Problem Statement
|
|
9
|
-
|
|
10
|
-
`drizzle-multitenant` currently cannot work with databases that have existing migrations applied via:
|
|
11
|
-
1. **drizzle-kit migrate** (standard Drizzle ORM workflow)
|
|
12
|
-
2. **Custom migration scripts** with different table structures
|
|
13
|
-
|
|
14
|
-
When running `npx drizzle-multitenant status`, users see errors like:
|
|
15
|
-
|
|
16
|
-
```
|
|
17
|
-
column "name" does not exist
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
This happens because `drizzle-multitenant` expects a specific table structure that differs from what `drizzle-kit` and other tools create.
|
|
21
|
-
|
|
22
|
-
## Migration Table Formats
|
|
23
|
-
|
|
24
|
-
### 1. drizzle-multitenant (current)
|
|
25
|
-
|
|
26
|
-
```sql
|
|
27
|
-
CREATE TABLE "__drizzle_migrations" (
|
|
28
|
-
id SERIAL PRIMARY KEY,
|
|
29
|
-
name VARCHAR(255) NOT NULL UNIQUE,
|
|
30
|
-
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
31
|
-
);
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
**Tracking method**: Filename without extension (e.g., `0001_initial_schema`)
|
|
35
|
-
|
|
36
|
-
### 2. drizzle-kit (standard Drizzle ORM)
|
|
37
|
-
|
|
38
|
-
```sql
|
|
39
|
-
CREATE TABLE "__drizzle_migrations" (
|
|
40
|
-
id SERIAL PRIMARY KEY,
|
|
41
|
-
hash TEXT NOT NULL,
|
|
42
|
-
created_at BIGINT NOT NULL
|
|
43
|
-
);
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**Tracking method**: SHA-256 hash of migration file content
|
|
47
|
-
|
|
48
|
-
### 3. Custom Scripts (e.g., PrimeSys-v2 legacy)
|
|
49
|
-
|
|
50
|
-
```sql
|
|
51
|
-
CREATE TABLE "__drizzle_tenant_migrations" (
|
|
52
|
-
id SERIAL PRIMARY KEY,
|
|
53
|
-
hash TEXT NOT NULL,
|
|
54
|
-
created_at BIGINT NOT NULL
|
|
55
|
-
);
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
**Tracking method**: SHA-256 hash (same as drizzle-kit, different table name)
|
|
59
|
-
|
|
60
|
-
## Proposed Solution
|
|
61
|
-
|
|
62
|
-
### 1. Auto-Detection of Table Format
|
|
63
|
-
|
|
64
|
-
The migrator should automatically detect the existing table structure and adapt:
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
// src/migrator/table-format.ts
|
|
68
|
-
export type TableFormat = 'name' | 'hash' | 'drizzle-kit';
|
|
69
|
-
|
|
70
|
-
export interface DetectedFormat {
|
|
71
|
-
format: TableFormat;
|
|
72
|
-
tableName: string;
|
|
73
|
-
columns: {
|
|
74
|
-
identifier: 'name' | 'hash';
|
|
75
|
-
timestamp: 'applied_at' | 'created_at';
|
|
76
|
-
timestampType: 'timestamp' | 'bigint';
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export async function detectTableFormat(
|
|
81
|
-
pool: Pool,
|
|
82
|
-
schemaName: string,
|
|
83
|
-
tableName: string
|
|
84
|
-
): Promise<DetectedFormat | null> {
|
|
85
|
-
// Check if table exists
|
|
86
|
-
const tableExists = await pool.query(`
|
|
87
|
-
SELECT 1 FROM information_schema.tables
|
|
88
|
-
WHERE table_schema = $1 AND table_name = $2
|
|
89
|
-
`, [schemaName, tableName]);
|
|
90
|
-
|
|
91
|
-
if (tableExists.rowCount === 0) return null;
|
|
92
|
-
|
|
93
|
-
// Check columns
|
|
94
|
-
const columns = await pool.query(`
|
|
95
|
-
SELECT column_name, data_type
|
|
96
|
-
FROM information_schema.columns
|
|
97
|
-
WHERE table_schema = $1 AND table_name = $2
|
|
98
|
-
`, [schemaName, tableName]);
|
|
99
|
-
|
|
100
|
-
const columnMap = new Map(columns.rows.map(r => [r.column_name, r.data_type]));
|
|
101
|
-
|
|
102
|
-
if (columnMap.has('name')) {
|
|
103
|
-
return {
|
|
104
|
-
format: 'name',
|
|
105
|
-
tableName,
|
|
106
|
-
columns: {
|
|
107
|
-
identifier: 'name',
|
|
108
|
-
timestamp: 'applied_at',
|
|
109
|
-
timestampType: 'timestamp',
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (columnMap.has('hash')) {
|
|
115
|
-
const timestampType = columnMap.get('created_at');
|
|
116
|
-
return {
|
|
117
|
-
format: timestampType === 'bigint' ? 'drizzle-kit' : 'hash',
|
|
118
|
-
tableName,
|
|
119
|
-
columns: {
|
|
120
|
-
identifier: 'hash',
|
|
121
|
-
timestamp: 'created_at',
|
|
122
|
-
timestampType: timestampType === 'bigint' ? 'bigint' : 'timestamp',
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return null;
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### 2. Format-Aware Migration Tracking
|
|
132
|
-
|
|
133
|
-
Update the `Migrator` class to work with any detected format:
|
|
134
|
-
|
|
135
|
-
```typescript
|
|
136
|
-
// src/migrator/migrator.ts
|
|
137
|
-
|
|
138
|
-
interface MigrationIdentifier {
|
|
139
|
-
name: string; // Filename without extension
|
|
140
|
-
hash: string; // SHA-256 of content
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
private async getAppliedMigrations(
|
|
144
|
-
pool: Pool,
|
|
145
|
-
schemaName: string,
|
|
146
|
-
format: DetectedFormat
|
|
147
|
-
): Promise<AppliedMigration[]> {
|
|
148
|
-
const identifierColumn = format.columns.identifier;
|
|
149
|
-
const timestampColumn = format.columns.timestamp;
|
|
150
|
-
|
|
151
|
-
const result = await pool.query(
|
|
152
|
-
`SELECT id, ${identifierColumn} as identifier, ${timestampColumn} as applied_at
|
|
153
|
-
FROM "${schemaName}"."${format.tableName}"
|
|
154
|
-
ORDER BY id`
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
return result.rows.map((row) => ({
|
|
158
|
-
id: row.id,
|
|
159
|
-
identifier: row.identifier,
|
|
160
|
-
appliedAt: format.columns.timestampType === 'bigint'
|
|
161
|
-
? new Date(parseInt(row.applied_at))
|
|
162
|
-
: row.applied_at,
|
|
163
|
-
}));
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
private isMigrationApplied(
|
|
167
|
-
migration: MigrationFile,
|
|
168
|
-
appliedIdentifiers: Set<string>,
|
|
169
|
-
format: DetectedFormat
|
|
170
|
-
): boolean {
|
|
171
|
-
if (format.columns.identifier === 'name') {
|
|
172
|
-
return appliedIdentifiers.has(migration.name);
|
|
173
|
-
}
|
|
174
|
-
// Hash-based: check both hash AND name for backwards compatibility
|
|
175
|
-
return appliedIdentifiers.has(migration.hash) ||
|
|
176
|
-
appliedIdentifiers.has(migration.name);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private async recordMigration(
|
|
180
|
-
pool: Pool,
|
|
181
|
-
schemaName: string,
|
|
182
|
-
migration: MigrationFile,
|
|
183
|
-
format: DetectedFormat
|
|
184
|
-
): Promise<void> {
|
|
185
|
-
const identifierColumn = format.columns.identifier;
|
|
186
|
-
const timestampColumn = format.columns.timestamp;
|
|
187
|
-
const identifier = format.columns.identifier === 'name'
|
|
188
|
-
? migration.name
|
|
189
|
-
: migration.hash;
|
|
190
|
-
const timestamp = format.columns.timestampType === 'bigint'
|
|
191
|
-
? Date.now()
|
|
192
|
-
: new Date();
|
|
193
|
-
|
|
194
|
-
await pool.query(
|
|
195
|
-
`INSERT INTO "${schemaName}"."${format.tableName}"
|
|
196
|
-
(${identifierColumn}, ${timestampColumn}) VALUES ($1, $2)`,
|
|
197
|
-
[identifier, timestamp]
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
### 3. Configuration Options
|
|
203
|
-
|
|
204
|
-
Allow explicit format configuration with sensible defaults:
|
|
205
|
-
|
|
206
|
-
```typescript
|
|
207
|
-
// src/migrator/types.ts
|
|
208
|
-
export interface MigratorConfig {
|
|
209
|
-
migrationsFolder: string;
|
|
210
|
-
tenantDiscovery: () => Promise<string[]>;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Migration table name
|
|
214
|
-
* @default "__drizzle_migrations"
|
|
215
|
-
*/
|
|
216
|
-
migrationsTable?: string;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Table format for tracking migrations
|
|
220
|
-
* - "auto": Auto-detect existing format, use "name" for new tables
|
|
221
|
-
* - "name": Use filename (drizzle-multitenant native)
|
|
222
|
-
* - "hash": Use SHA-256 hash (drizzle-kit compatible)
|
|
223
|
-
* - "drizzle-kit": Exact drizzle-kit format (hash + bigint timestamp)
|
|
224
|
-
* @default "auto"
|
|
225
|
-
*/
|
|
226
|
-
tableFormat?: 'auto' | 'name' | 'hash' | 'drizzle-kit';
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* When using "auto" format and no table exists, which format to create
|
|
230
|
-
* @default "name"
|
|
231
|
-
*/
|
|
232
|
-
defaultFormat?: 'name' | 'hash' | 'drizzle-kit';
|
|
233
|
-
|
|
234
|
-
hooks?: MigratorHooks;
|
|
235
|
-
}
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
### 4. Migration File Enhancement
|
|
239
|
-
|
|
240
|
-
Add hash computation to migration files:
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
// src/migrator/migrator.ts
|
|
244
|
-
import { createHash } from 'node:crypto';
|
|
245
|
-
|
|
246
|
-
private async loadMigrations(): Promise<MigrationFile[]> {
|
|
247
|
-
const files = await readdir(this.migratorConfig.migrationsFolder);
|
|
248
|
-
const migrations: MigrationFile[] = [];
|
|
249
|
-
|
|
250
|
-
for (const file of files) {
|
|
251
|
-
if (!file.endsWith('.sql')) continue;
|
|
252
|
-
|
|
253
|
-
const filePath = join(this.migratorConfig.migrationsFolder, file);
|
|
254
|
-
const content = await readFile(filePath, 'utf-8');
|
|
255
|
-
|
|
256
|
-
const match = file.match(/^(\d+)_/);
|
|
257
|
-
const timestamp = match?.[1] ? parseInt(match[1], 10) : 0;
|
|
258
|
-
|
|
259
|
-
migrations.push({
|
|
260
|
-
name: basename(file, '.sql'),
|
|
261
|
-
path: filePath,
|
|
262
|
-
sql: content,
|
|
263
|
-
timestamp,
|
|
264
|
-
hash: createHash('sha256').update(content).digest('hex'), // NEW
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return migrations.sort((a, b) => a.timestamp - b.timestamp);
|
|
269
|
-
}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### 5. CLI Updates
|
|
273
|
-
|
|
274
|
-
Update status command to show format information:
|
|
275
|
-
|
|
276
|
-
```bash
|
|
277
|
-
$ npx drizzle-multitenant status -c tenant.config.ts
|
|
278
|
-
|
|
279
|
-
Migration Status:
|
|
280
|
-
┌──────────────────────────────────────┬──────────────────────┬────────┬─────────┬─────────┬──────────────┐
|
|
281
|
-
│ Tenant │ Schema │ Format │ Applied │ Pending │ Status │
|
|
282
|
-
├──────────────────────────────────────┼──────────────────────┼────────┼─────────┼─────────┼──────────────┤
|
|
283
|
-
│ abc-123 │ empresa_abc_123 │ hash │ 45 │ 3 │ ✓ Behind │
|
|
284
|
-
│ def-456 │ empresa_def_456 │ name │ 48 │ 0 │ ✓ Up to date │
|
|
285
|
-
│ ghi-789 │ empresa_ghi_789 │ (new) │ 0 │ 48 │ ○ New tenant │
|
|
286
|
-
└──────────────────────────────────────┴──────────────────────┴────────┴─────────┴─────────┴──────────────┘
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
### 6. New CLI Command: `convert-format`
|
|
290
|
-
|
|
291
|
-
For users who want to standardize on a single format:
|
|
292
|
-
|
|
293
|
-
```bash
|
|
294
|
-
# Preview conversion
|
|
295
|
-
npx drizzle-multitenant convert-format --to=name --dry-run
|
|
296
|
-
|
|
297
|
-
# Convert all tenants from hash to name format
|
|
298
|
-
npx drizzle-multitenant convert-format --to=name
|
|
299
|
-
|
|
300
|
-
# Convert specific tenant
|
|
301
|
-
npx drizzle-multitenant convert-format --to=name --tenant=abc-123
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
Implementation:
|
|
305
|
-
|
|
306
|
-
```typescript
|
|
307
|
-
// src/cli/commands/convert-format.ts
|
|
308
|
-
async function convertFormat(
|
|
309
|
-
pool: Pool,
|
|
310
|
-
schemaName: string,
|
|
311
|
-
tableName: string,
|
|
312
|
-
migrations: MigrationFile[],
|
|
313
|
-
targetFormat: 'name' | 'hash'
|
|
314
|
-
): Promise<void> {
|
|
315
|
-
const migrationMap = new Map(
|
|
316
|
-
migrations.map(m => [m.hash, m.name])
|
|
317
|
-
);
|
|
318
|
-
|
|
319
|
-
// Read current records
|
|
320
|
-
const current = await pool.query(
|
|
321
|
-
`SELECT id, hash FROM "${schemaName}"."${tableName}" ORDER BY id`
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
await pool.query('BEGIN');
|
|
325
|
-
|
|
326
|
-
try {
|
|
327
|
-
// Add name column if converting to name format
|
|
328
|
-
if (targetFormat === 'name') {
|
|
329
|
-
await pool.query(`
|
|
330
|
-
ALTER TABLE "${schemaName}"."${tableName}"
|
|
331
|
-
ADD COLUMN IF NOT EXISTS name VARCHAR(255)
|
|
332
|
-
`);
|
|
333
|
-
|
|
334
|
-
// Populate name from hash using migration files
|
|
335
|
-
for (const row of current.rows) {
|
|
336
|
-
const name = migrationMap.get(row.hash);
|
|
337
|
-
if (name) {
|
|
338
|
-
await pool.query(
|
|
339
|
-
`UPDATE "${schemaName}"."${tableName}"
|
|
340
|
-
SET name = $1 WHERE id = $2`,
|
|
341
|
-
[name, row.id]
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Make name NOT NULL and add unique constraint
|
|
347
|
-
await pool.query(`
|
|
348
|
-
ALTER TABLE "${schemaName}"."${tableName}"
|
|
349
|
-
ALTER COLUMN name SET NOT NULL,
|
|
350
|
-
ADD CONSTRAINT ${tableName}_name_unique UNIQUE (name)
|
|
351
|
-
`);
|
|
352
|
-
|
|
353
|
-
// Optionally drop hash column
|
|
354
|
-
// await pool.query(`ALTER TABLE ... DROP COLUMN hash`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
await pool.query('COMMIT');
|
|
358
|
-
} catch (error) {
|
|
359
|
-
await pool.query('ROLLBACK');
|
|
360
|
-
throw error;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
## Implementation Plan
|
|
366
|
-
|
|
367
|
-
### Phase 1: Detection & Read Compatibility (2 hours)
|
|
368
|
-
|
|
369
|
-
1. Implement `detectTableFormat()` function
|
|
370
|
-
2. Update `getAppliedMigrations()` to handle all formats
|
|
371
|
-
3. Update `isMigrationApplied()` for hash-based comparison
|
|
372
|
-
4. Update `getTenantStatus()` to show format in status
|
|
373
|
-
|
|
374
|
-
### Phase 2: Write Compatibility (2 hours)
|
|
375
|
-
|
|
376
|
-
1. Add hash computation to `loadMigrations()`
|
|
377
|
-
2. Update `recordMigration()` to use detected format
|
|
378
|
-
3. Update `ensureMigrationsTable()` to create correct format
|
|
379
|
-
4. Add `tableFormat` config option
|
|
380
|
-
|
|
381
|
-
### Phase 3: CLI Updates (2 hours)
|
|
382
|
-
|
|
383
|
-
1. Update `status` command output
|
|
384
|
-
2. Add `convert-format` command
|
|
385
|
-
3. Update `--dry-run` to show format info
|
|
386
|
-
4. Add format info to `migrate` command output
|
|
387
|
-
|
|
388
|
-
### Phase 4: Testing & Documentation (2 hours)
|
|
389
|
-
|
|
390
|
-
1. Unit tests for format detection
|
|
391
|
-
2. Integration tests with all formats
|
|
392
|
-
3. Update README with format documentation
|
|
393
|
-
4. Add migration guide for existing users
|
|
394
|
-
|
|
395
|
-
## Backwards Compatibility
|
|
396
|
-
|
|
397
|
-
- **Existing drizzle-multitenant users**: No changes needed, default behavior preserved
|
|
398
|
-
- **drizzle-kit users**: Works automatically with `tableFormat: "auto"` (default)
|
|
399
|
-
- **Custom script users**: Set `tableFormat: "hash"` or let auto-detection handle it
|
|
400
|
-
|
|
401
|
-
## Configuration Examples
|
|
402
|
-
|
|
403
|
-
### New Project (drizzle-multitenant native)
|
|
404
|
-
|
|
405
|
-
```typescript
|
|
406
|
-
export default {
|
|
407
|
-
...config,
|
|
408
|
-
migrations: {
|
|
409
|
-
tenantFolder: "./drizzle/tenant",
|
|
410
|
-
tenantDiscovery: discoverTenants,
|
|
411
|
-
// Uses default: tableFormat: "auto", defaultFormat: "name"
|
|
412
|
-
},
|
|
413
|
-
};
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
### Migrating from drizzle-kit
|
|
417
|
-
|
|
418
|
-
```typescript
|
|
419
|
-
export default {
|
|
420
|
-
...config,
|
|
421
|
-
migrations: {
|
|
422
|
-
tenantFolder: "./drizzle/tenant",
|
|
423
|
-
tenantDiscovery: discoverTenants,
|
|
424
|
-
migrationsTable: "__drizzle_migrations",
|
|
425
|
-
tableFormat: "auto", // Auto-detects drizzle-kit format
|
|
426
|
-
},
|
|
427
|
-
};
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
### Explicit drizzle-kit Compatibility
|
|
431
|
-
|
|
432
|
-
```typescript
|
|
433
|
-
export default {
|
|
434
|
-
...config,
|
|
435
|
-
migrations: {
|
|
436
|
-
tenantFolder: "./drizzle/tenant",
|
|
437
|
-
tenantDiscovery: discoverTenants,
|
|
438
|
-
migrationsTable: "__drizzle_migrations",
|
|
439
|
-
tableFormat: "drizzle-kit", // Forces drizzle-kit format
|
|
440
|
-
},
|
|
441
|
-
};
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
### Legacy Custom Script
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
export default {
|
|
448
|
-
...config,
|
|
449
|
-
migrations: {
|
|
450
|
-
tenantFolder: "./drizzle/tenant",
|
|
451
|
-
tenantDiscovery: discoverTenants,
|
|
452
|
-
migrationsTable: "__drizzle_tenant_migrations", // Custom table name
|
|
453
|
-
tableFormat: "hash", // Uses hash-based tracking
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
```
|
|
457
|
-
|
|
458
|
-
## Success Criteria
|
|
459
|
-
|
|
460
|
-
1. `npx drizzle-multitenant status` works with existing drizzle-kit databases
|
|
461
|
-
2. `npx drizzle-multitenant migrate` applies new migrations to drizzle-kit databases
|
|
462
|
-
3. No data loss or duplicate migrations during format transition
|
|
463
|
-
4. Clear error messages when format detection fails
|
|
464
|
-
5. Documentation covers all migration scenarios
|
|
465
|
-
|
|
466
|
-
## Related
|
|
467
|
-
|
|
468
|
-
- [Improvements from PrimeSys](./improvements-from-primesys.md) - Section 3
|
|
469
|
-
- [drizzle-multitenant Roadmap](../roadmap.md)
|
|
470
|
-
- [Drizzle ORM Migrations Docs](https://orm.drizzle.team/docs/migrations)
|
|
471
|
-
- [Drizzle Kit Migrate](https://orm.drizzle.team/docs/drizzle-kit-migrate)
|
|
472
|
-
|
|
473
|
-
## Appendix: Table Structure Reference
|
|
474
|
-
|
|
475
|
-
### drizzle-kit Internal Structure
|
|
476
|
-
|
|
477
|
-
Based on Drizzle ORM source code, `drizzle-kit migrate` creates:
|
|
478
|
-
|
|
479
|
-
```sql
|
|
480
|
-
CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
|
|
481
|
-
id SERIAL PRIMARY KEY,
|
|
482
|
-
hash TEXT NOT NULL,
|
|
483
|
-
created_at BIGINT NOT NULL -- Unix timestamp in milliseconds
|
|
484
|
-
);
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
The hash is computed as SHA-256 of the migration file content, allowing drizzle-kit to detect if a migration file was modified after being applied.
|
|
488
|
-
|
|
489
|
-
### Why Hash-Based Tracking?
|
|
490
|
-
|
|
491
|
-
- **Content verification**: Detects if migration files were modified
|
|
492
|
-
- **Idempotency**: Same content = same hash, prevents accidental re-runs
|
|
493
|
-
- **drizzle-kit compatibility**: Matches drizzle-kit's internal behavior
|
|
494
|
-
|
|
495
|
-
### Why Name-Based Tracking?
|
|
496
|
-
|
|
497
|
-
- **Human readable**: Easy to see which migrations are applied
|
|
498
|
-
- **Debug friendly**: `SELECT * FROM __drizzle_migrations` shows migration names
|
|
499
|
-
- **Simpler**: No hash computation needed
|