-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathmigrate.ts
More file actions
96 lines (84 loc) · 3.77 KB
/
migrate.ts
File metadata and controls
96 lines (84 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import { readdir, readFile } from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { PGlite } from '@electric-sql/pglite'
import { env } from '../lib/env.js'
import { getDb } from './index.js'
const migrateFile = fileURLToPath(import.meta.url)
const dbDir = dirname(migrateFile)
// migrationsDir is relative to this file: src/db/migrate.ts
// So migrations are at: src/db/migrations (same level as migrate.ts)
const migrationsDir = join(dbDir, 'migrations')
async function readMigrationFiles(migrationDir: string): Promise<string[]> {
try {
const files = await readdir(migrationDir)
return files.filter(file => file.endsWith('.sql')).sort()
} catch {
// Migrations directory doesn't exist yet
return []
}
}
/**
* Run database migrations
* Supports build-time migrations for PostgreSQL and runtime migrations for PGLite
* - PostgreSQL: Migrations run at build time via `pnpm db:migrate` → `pnpm build`
* Migrations are skipped at runtime (already applied at build time)
* - PGLite: Migrations run at runtime when instance is created
*/
export async function runMigrations(logger?: {
info: (msg: string) => void
error: (msg: string, err?: unknown) => void
}): Promise<void> {
const migrationFiles = await readMigrationFiles(migrationsDir)
if (migrationFiles.length === 0) {
logger?.info('No migrations found, skipping migration step')
return
}
try {
const shouldUsePGLite = env.PGLITE === true || env.NODE_ENV === 'test'
if (shouldUsePGLite) {
// PGLite: Run migrations at runtime when instance is created
logger?.info(`Found ${migrationFiles.length} migration file(s), running migrations...`)
const db = await getDb()
// Instead of using migratePGLite (which silently fails):
// await migratePGLite(db as unknown as PgliteDatabase, { migrationsFolder: migrationsDir })
// Execute SQL directly on PGLite instance
// Get the underlying PGLite instance from the Drizzle connection
// For test mode: instance comes from getTestDatabase() singleton
// For runtime mode: instance is stored in pgLiteInstance variable in index.ts
let pgliteInstance: PGlite
if (env.NODE_ENV === 'test') {
// In test mode, get instance from test utils singleton
const { getTestDatabase } = await import('../../test/utils/db.js')
const { instance } = await getTestDatabase()
pgliteInstance = instance
} else {
// In runtime mode, get instance from db connection
// The instance is available at db._.session.client for PGLite connections
// Accessing internal Drizzle structure - not part of public API
pgliteInstance = (db as unknown as { _: { session: { client: PGlite } } })._.session.client
}
// Read and execute each migration SQL file
// Remove statement-breakpoint markers and execute as a single SQL script
// PGLite's exec() can handle multiple statements separated by semicolons
for (const file of migrationFiles) {
const sqlPath = join(migrationsDir, file)
let sql = await readFile(sqlPath, 'utf-8')
// Remove all statement-breakpoint markers (they're just Drizzle metadata)
sql = sql.replace(/--> statement-breakpoint\s*/gi, '\n').trim()
// Execute the entire SQL file - PGLite handles multiple statements
await pgliteInstance.exec(sql)
}
logger?.info('Migrations completed successfully (PGLite)')
} else {
// PostgreSQL: Migrations already ran at build time, skip at runtime
logger?.info(
'PostgreSQL detected: migrations already applied at build time, skipping runtime migrations',
)
return
}
} catch (err) {
logger?.error('Migration failed', err)
throw err
}
}