@digilogiclabs/platform-core 1.0.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/README.md +545 -0
- package/dist/ConsoleEmail-XeUBAnwc.d.mts +2786 -0
- package/dist/ConsoleEmail-XeUBAnwc.d.ts +2786 -0
- package/dist/index-C_2W7Byw.d.mts +379 -0
- package/dist/index-C_2W7Byw.d.ts +379 -0
- package/dist/index.d.mts +2045 -0
- package/dist/index.d.ts +2045 -0
- package/dist/index.js +6690 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6550 -0
- package/dist/index.mjs.map +1 -0
- package/dist/migrate.js +1101 -0
- package/dist/migrate.js.map +1 -0
- package/dist/migrations/index.d.mts +1 -0
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.js +508 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/migrations/index.mjs +468 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/testing.d.mts +97 -0
- package/dist/testing.d.ts +97 -0
- package/dist/testing.js +1789 -0
- package/dist/testing.js.map +1 -0
- package/dist/testing.mjs +1743 -0
- package/dist/testing.mjs.map +1 -0
- package/package.json +152 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/migrations/index.ts","../../src/migrations/Migrator.ts","../../src/migrations/helpers.ts"],"sourcesContent":["/**\r\n * Database Migrations\r\n *\r\n * Enterprise-grade migration system for managing database schema changes.\r\n *\r\n * @example\r\n * ```typescript\r\n * import { Migrator } from '@digilogiclabs/platform-core';\r\n * import { PostgresDatabase } from '@digilogiclabs/platform-core';\r\n *\r\n * // Create database connection\r\n * const db = await PostgresDatabase.fromEnv();\r\n *\r\n * // Create migrator\r\n * const migrator = new Migrator(db, {\r\n * tableName: '_migrations',\r\n * migrationsDir: './migrations'\r\n * });\r\n *\r\n * // Add migrations programmatically\r\n * migrator.addMigrations([\r\n * {\r\n * version: '20240101000000',\r\n * name: 'create_users_table',\r\n * up: `CREATE TABLE users (id UUID PRIMARY KEY, email VARCHAR(255) UNIQUE)`,\r\n * down: `DROP TABLE users`\r\n * }\r\n * ]);\r\n *\r\n * // Or load from directory\r\n * await migrator.loadMigrations('./migrations');\r\n *\r\n * // Check status\r\n * const status = await migrator.status();\r\n * console.log('Applied:', status.applied.length);\r\n * console.log('Pending:', status.pending.length);\r\n *\r\n * // Run pending migrations\r\n * const results = await migrator.up();\r\n * for (const result of results) {\r\n * if (result.success) {\r\n * console.log(`✓ ${result.migration.name} (${result.executionTime}ms)`);\r\n * } else {\r\n * console.error(`✗ ${result.migration.name}: ${result.error?.message}`);\r\n * }\r\n * }\r\n *\r\n * // Rollback last migration\r\n * await migrator.down();\r\n *\r\n * // Rollback last 3 migrations\r\n * await migrator.down({ steps: 3 });\r\n *\r\n * // Dry run (preview without applying)\r\n * await migrator.up({ dryRun: true });\r\n * ```\r\n */\r\n\r\nexport { Migrator } from './Migrator';\r\nexport type {\r\n Migration,\r\n MigrationRecord,\r\n MigrationResult,\r\n MigrationStatus,\r\n MigratorConfig,\r\n IMigrator,\r\n IMigrationDatabase,\r\n} from '../interfaces/IMigration';\r\n\r\n// Re-export common migration patterns\r\nexport { createMigration, defineMigration, sqlMigration } from './helpers';\r\n","/**\r\n * Database Migrator\r\n *\r\n * Enterprise-grade database migration system supporting:\r\n * - PostgreSQL (primary)\r\n * - Memory database (for testing)\r\n * - Transaction-safe migrations\r\n * - Concurrent execution locking\r\n * - Checksum validation\r\n * - Dry-run mode\r\n */\r\n\r\nimport type {\r\n IMigrator,\r\n Migration,\r\n MigrationRecord,\r\n MigrationResult,\r\n MigrationStatus,\r\n MigratorConfig,\r\n IMigrationDatabase,\r\n} from '../interfaces/IMigration';\r\n\r\nconst DEFAULT_CONFIG: Required<Omit<MigratorConfig, 'migrationsDir'>> & { migrationsDir?: string } = {\r\n tableName: '_migrations',\r\n schema: 'public',\r\n lockTimeout: 60,\r\n validateChecksums: true,\r\n migrationsDir: undefined,\r\n};\r\n\r\n/**\r\n * Generate a checksum for migration content\r\n */\r\nfunction generateChecksum(content: string): string {\r\n // Simple hash for checksum - in production you might use crypto\r\n let hash = 0;\r\n for (let i = 0; i < content.length; i++) {\r\n const char = content.charCodeAt(i);\r\n hash = (hash << 5) - hash + char;\r\n hash = hash & hash; // Convert to 32-bit integer\r\n }\r\n return Math.abs(hash).toString(16).padStart(8, '0');\r\n}\r\n\r\n/**\r\n * Sort migrations by version\r\n */\r\nfunction sortMigrations(migrations: Migration[]): Migration[] {\r\n return [...migrations].sort((a, b) => a.version.localeCompare(b.version));\r\n}\r\n\r\nexport class Migrator implements IMigrator {\r\n private db: IMigrationDatabase;\r\n private config: Required<Omit<MigratorConfig, 'migrationsDir'>> & { migrationsDir?: string };\r\n private migrations: Migration[] = [];\r\n private initialized = false;\r\n private locked = false;\r\n\r\n constructor(db: IMigrationDatabase, config: MigratorConfig = {}) {\r\n this.db = db;\r\n this.config = { ...DEFAULT_CONFIG, ...config };\r\n }\r\n\r\n /**\r\n * Get the fully qualified migration table name\r\n */\r\n private get tableName(): string {\r\n return `\"${this.config.schema}\".\"${this.config.tableName}\"`;\r\n }\r\n\r\n /**\r\n * Get the lock table name\r\n */\r\n private get lockTableName(): string {\r\n return `\"${this.config.schema}\".\"${this.config.tableName}_lock\"`;\r\n }\r\n\r\n /**\r\n * Initialize migration tracking tables\r\n */\r\n private async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n // Create schema if not exists\r\n await this.db.raw(`CREATE SCHEMA IF NOT EXISTS \"${this.config.schema}\"`);\r\n\r\n // Create migrations tracking table\r\n await this.db.raw(`\r\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\r\n version VARCHAR(255) PRIMARY KEY,\r\n name VARCHAR(255) NOT NULL,\r\n applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\r\n execution_time INTEGER NOT NULL,\r\n checksum VARCHAR(64)\r\n )\r\n `);\r\n\r\n // Create lock table for concurrent safety\r\n await this.db.raw(`\r\n CREATE TABLE IF NOT EXISTS ${this.lockTableName} (\r\n id INTEGER PRIMARY KEY DEFAULT 1,\r\n locked_at TIMESTAMP WITH TIME ZONE,\r\n locked_by VARCHAR(255),\r\n CONSTRAINT single_row CHECK (id = 1)\r\n )\r\n `);\r\n\r\n // Insert initial lock row if not exists\r\n await this.db.raw(`\r\n INSERT INTO ${this.lockTableName} (id, locked_at, locked_by)\r\n VALUES (1, NULL, NULL)\r\n ON CONFLICT (id) DO NOTHING\r\n `);\r\n\r\n this.initialized = true;\r\n }\r\n\r\n /**\r\n * Acquire migration lock\r\n */\r\n async lock(): Promise<boolean> {\r\n await this.initialize();\r\n\r\n const lockId = `${process.pid}-${Date.now()}`;\r\n const lockTimeout = new Date(Date.now() - this.config.lockTimeout * 1000);\r\n\r\n // Try to acquire lock (only if not locked or lock expired)\r\n const result = await this.db.raw<{ locked_at: Date | null }>(\r\n `\r\n UPDATE ${this.lockTableName}\r\n SET locked_at = CURRENT_TIMESTAMP, locked_by = $1\r\n WHERE id = 1 AND (locked_at IS NULL OR locked_at < $2)\r\n RETURNING locked_at\r\n `,\r\n [lockId, lockTimeout]\r\n );\r\n\r\n if (result.data.length > 0) {\r\n this.locked = true;\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Release migration lock\r\n */\r\n async unlock(): Promise<void> {\r\n if (!this.locked) return;\r\n\r\n await this.db.raw(\r\n `\r\n UPDATE ${this.lockTableName}\r\n SET locked_at = NULL, locked_by = NULL\r\n WHERE id = 1\r\n `\r\n );\r\n\r\n this.locked = false;\r\n }\r\n\r\n /**\r\n * Add migrations to the migrator\r\n */\r\n addMigrations(migrations: Migration[]): void {\r\n for (const migration of migrations) {\r\n // Generate checksum if not provided\r\n if (!migration.checksum) {\r\n const content =\r\n typeof migration.up === 'string' ? migration.up : migration.up.toString();\r\n migration.checksum = generateChecksum(content);\r\n }\r\n\r\n // Avoid duplicates\r\n if (!this.migrations.find((m) => m.version === migration.version)) {\r\n this.migrations.push(migration);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Load migrations from directory\r\n * Expects files named: {version}_{name}.ts or {version}_{name}.sql\r\n */\r\n async loadMigrations(dir: string): Promise<void> {\r\n // Dynamic import for Node.js file system\r\n const { readdir, readFile } = await import('fs/promises');\r\n const { join } = await import('path');\r\n\r\n const files = await readdir(dir);\r\n const migrationFiles = files.filter(\r\n (f) => f.match(/^\\d+_.*\\.(ts|js|sql)$/)\r\n );\r\n\r\n for (const file of migrationFiles) {\r\n const filePath = join(dir, file);\r\n const [version, ...nameParts] = file.replace(/\\.(ts|js|sql)$/, '').split('_');\r\n const name = nameParts.join('_');\r\n\r\n if (file.endsWith('.sql')) {\r\n // SQL file - parse UP and DOWN sections\r\n const content = await readFile(filePath, 'utf-8');\r\n const [up, down] = this.parseSqlMigration(content);\r\n\r\n this.addMigrations([\r\n {\r\n version: version!,\r\n name,\r\n up,\r\n down,\r\n },\r\n ]);\r\n } else {\r\n // TypeScript/JavaScript file - import the module\r\n const module = await import(filePath);\r\n this.addMigrations([\r\n {\r\n version: version!,\r\n name,\r\n up: module.up,\r\n down: module.down,\r\n transactional: module.transactional,\r\n },\r\n ]);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Parse SQL migration file with -- UP and -- DOWN sections\r\n */\r\n private parseSqlMigration(content: string): [string, string] {\r\n const upMatch = content.match(/--\\s*UP\\s*\\n([\\s\\S]*?)(?=--\\s*DOWN|$)/i);\r\n const downMatch = content.match(/--\\s*DOWN\\s*\\n([\\s\\S]*?)$/i);\r\n\r\n const up = upMatch?.[1]?.trim() ?? content.trim();\r\n const down = downMatch?.[1]?.trim() ?? '';\r\n\r\n return [up, down];\r\n }\r\n\r\n /**\r\n * Get current migration status\r\n */\r\n async status(): Promise<MigrationStatus> {\r\n await this.initialize();\r\n\r\n // Get applied migrations\r\n const result = await this.db.raw<{\r\n version: string;\r\n name: string;\r\n applied_at: Date;\r\n execution_time: number;\r\n checksum: string | null;\r\n }>(\r\n `SELECT version, name, applied_at, execution_time, checksum\r\n FROM ${this.tableName}\r\n ORDER BY version ASC`\r\n );\r\n\r\n const applied: MigrationRecord[] = result.data.map((row) => ({\r\n version: row.version,\r\n name: row.name,\r\n appliedAt: new Date(row.applied_at),\r\n executionTime: row.execution_time,\r\n checksum: row.checksum ?? undefined,\r\n }));\r\n\r\n const appliedVersions = new Set(applied.map((m) => m.version));\r\n const sortedMigrations = sortMigrations(this.migrations);\r\n\r\n const pending = sortedMigrations.filter(\r\n (m) => !appliedVersions.has(m.version)\r\n );\r\n\r\n return {\r\n applied,\r\n pending,\r\n currentVersion: applied.length > 0 ? applied[applied.length - 1]!.version : null,\r\n latestVersion:\r\n sortedMigrations.length > 0\r\n ? sortedMigrations[sortedMigrations.length - 1]!.version\r\n : null,\r\n };\r\n }\r\n\r\n /**\r\n * Run all pending migrations (or up to a specific version)\r\n */\r\n async up(options: { dryRun?: boolean; to?: string } = {}): Promise<MigrationResult[]> {\r\n const { dryRun = false, to } = options;\r\n const results: MigrationResult[] = [];\r\n\r\n if (!dryRun) {\r\n const acquired = await this.lock();\r\n if (!acquired) {\r\n throw new Error('Could not acquire migration lock. Another migration may be in progress.');\r\n }\r\n }\r\n\r\n try {\r\n const status = await this.status();\r\n let pending = status.pending;\r\n\r\n // Filter to specific version if requested\r\n if (to) {\r\n const targetIndex = pending.findIndex((m) => m.version === to);\r\n if (targetIndex === -1) {\r\n throw new Error(`Target version ${to} not found in pending migrations`);\r\n }\r\n pending = pending.slice(0, targetIndex + 1);\r\n }\r\n\r\n for (const migration of pending) {\r\n const result = await this.runMigration(migration, 'up', dryRun);\r\n results.push(result);\r\n\r\n if (!result.success) {\r\n break; // Stop on first failure\r\n }\r\n }\r\n } finally {\r\n if (!dryRun) {\r\n await this.unlock();\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Rollback migrations\r\n */\r\n async down(options: { dryRun?: boolean; steps?: number } = {}): Promise<MigrationResult[]> {\r\n const { dryRun = false, steps = 1 } = options;\r\n const results: MigrationResult[] = [];\r\n\r\n if (!dryRun) {\r\n const acquired = await this.lock();\r\n if (!acquired) {\r\n throw new Error('Could not acquire migration lock. Another migration may be in progress.');\r\n }\r\n }\r\n\r\n try {\r\n const status = await this.status();\r\n\r\n // Get the last N applied migrations (in reverse order)\r\n const toRollback = status.applied.slice(-steps).reverse();\r\n\r\n for (const record of toRollback) {\r\n const migration = this.migrations.find((m) => m.version === record.version);\r\n if (!migration) {\r\n throw new Error(\r\n `Migration ${record.version} was applied but is not registered. Cannot rollback.`\r\n );\r\n }\r\n\r\n const result = await this.runMigration(migration, 'down', dryRun);\r\n results.push(result);\r\n\r\n if (!result.success) {\r\n break;\r\n }\r\n }\r\n } finally {\r\n if (!dryRun) {\r\n await this.unlock();\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Rollback all migrations and re-run them\r\n */\r\n async reset(options: { dryRun?: boolean } = {}): Promise<MigrationResult[]> {\r\n const status = await this.status();\r\n const downResults = await this.down({\r\n dryRun: options.dryRun,\r\n steps: status.applied.length,\r\n });\r\n\r\n // Only proceed with up if all downs succeeded\r\n if (downResults.every((r) => r.success)) {\r\n const upResults = await this.up({ dryRun: options.dryRun });\r\n return [...downResults, ...upResults];\r\n }\r\n\r\n return downResults;\r\n }\r\n\r\n /**\r\n * Migrate to a specific version\r\n */\r\n async goto(version: string, options: { dryRun?: boolean } = {}): Promise<MigrationResult[]> {\r\n const status = await this.status();\r\n const currentVersion = status.currentVersion;\r\n const results: MigrationResult[] = [];\r\n\r\n // If already at target, nothing to do\r\n if (currentVersion === version) {\r\n return results;\r\n }\r\n\r\n // Determine direction\r\n const isUp = !currentVersion || version > currentVersion;\r\n\r\n if (isUp) {\r\n return this.up({ ...options, to: version });\r\n } else {\r\n // Find how many steps down we need to go\r\n const currentIndex = status.applied.findIndex((m) => m.version === currentVersion);\r\n const targetIndex = status.applied.findIndex((m) => m.version === version);\r\n\r\n if (targetIndex === -1) {\r\n throw new Error(`Target version ${version} not found in applied migrations`);\r\n }\r\n\r\n const steps = currentIndex - targetIndex;\r\n return this.down({ ...options, steps });\r\n }\r\n }\r\n\r\n /**\r\n * Run a single migration\r\n */\r\n private async runMigration(\r\n migration: Migration,\r\n direction: 'up' | 'down',\r\n dryRun: boolean\r\n ): Promise<MigrationResult> {\r\n const startTime = Date.now();\r\n const action = direction === 'up' ? migration.up : migration.down;\r\n\r\n try {\r\n if (dryRun) {\r\n // Just simulate - don't actually run\r\n return {\r\n migration,\r\n success: true,\r\n executionTime: 0,\r\n dryRun: true,\r\n };\r\n }\r\n\r\n const transactional = migration.transactional !== false;\r\n const runAction = async (db: IMigrationDatabase) => {\r\n if (typeof action === 'string') {\r\n const result = await db.raw(action);\r\n if (result.error) {\r\n throw result.error;\r\n }\r\n } else {\r\n await action(db);\r\n }\r\n };\r\n\r\n if (transactional) {\r\n await this.db.transaction(async (tx) => {\r\n await runAction(tx);\r\n });\r\n } else {\r\n await runAction(this.db);\r\n }\r\n\r\n const executionTime = Date.now() - startTime;\r\n\r\n // Update tracking table\r\n if (direction === 'up') {\r\n await this.db.raw(\r\n `INSERT INTO ${this.tableName} (version, name, execution_time, checksum)\r\n VALUES ($1, $2, $3, $4)`,\r\n [migration.version, migration.name, executionTime, migration.checksum]\r\n );\r\n } else {\r\n await this.db.raw(\r\n `DELETE FROM ${this.tableName} WHERE version = $1`,\r\n [migration.version]\r\n );\r\n }\r\n\r\n return {\r\n migration,\r\n success: true,\r\n executionTime,\r\n dryRun: false,\r\n };\r\n } catch (error) {\r\n return {\r\n migration,\r\n success: false,\r\n executionTime: Date.now() - startTime,\r\n error: error instanceof Error ? error : new Error(String(error)),\r\n dryRun: false,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Create a new migration file\r\n */\r\n async create(name: string): Promise<string> {\r\n const { writeFile, mkdir } = await import('fs/promises');\r\n const { join } = await import('path');\r\n\r\n if (!this.config.migrationsDir) {\r\n throw new Error('migrationsDir must be configured to create migrations');\r\n }\r\n\r\n // Ensure directory exists\r\n await mkdir(this.config.migrationsDir, { recursive: true });\r\n\r\n // Generate version timestamp\r\n const now = new Date();\r\n const version = [\r\n now.getFullYear(),\r\n String(now.getMonth() + 1).padStart(2, '0'),\r\n String(now.getDate()).padStart(2, '0'),\r\n String(now.getHours()).padStart(2, '0'),\r\n String(now.getMinutes()).padStart(2, '0'),\r\n String(now.getSeconds()).padStart(2, '0'),\r\n ].join('');\r\n\r\n const safeName = name.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();\r\n const fileName = `${version}_${safeName}.sql`;\r\n const filePath = join(this.config.migrationsDir, fileName);\r\n\r\n const template = `-- Migration: ${name}\r\n-- Version: ${version}\r\n-- Created: ${now.toISOString()}\r\n\r\n-- UP\r\n-- Add your forward migration SQL here\r\n\r\n\r\n-- DOWN\r\n-- Add your rollback migration SQL here\r\n\r\n`;\r\n\r\n await writeFile(filePath, template, 'utf-8');\r\n\r\n return filePath;\r\n }\r\n\r\n /**\r\n * Validate migration integrity\r\n */\r\n async validate(): Promise<{ valid: boolean; errors: string[] }> {\r\n const errors: string[] = [];\r\n const status = await this.status();\r\n\r\n if (!this.config.validateChecksums) {\r\n return { valid: true, errors };\r\n }\r\n\r\n for (const applied of status.applied) {\r\n const migration = this.migrations.find((m) => m.version === applied.version);\r\n\r\n if (!migration) {\r\n errors.push(\r\n `Migration ${applied.version} (${applied.name}) was applied but is not registered`\r\n );\r\n continue;\r\n }\r\n\r\n if (applied.checksum && migration.checksum !== applied.checksum) {\r\n errors.push(\r\n `Migration ${applied.version} (${applied.name}) has been modified since it was applied`\r\n );\r\n }\r\n }\r\n\r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n };\r\n }\r\n}\r\n","/**\r\n * Migration Helper Functions\r\n *\r\n * Utilities for creating migrations in a type-safe and convenient way.\r\n */\r\n\r\nimport type { Migration, IMigrationDatabase } from '../interfaces/IMigration';\r\n\r\n/**\r\n * Create a SQL-based migration\r\n *\r\n * @example\r\n * ```typescript\r\n * export default sqlMigration({\r\n * version: '20240101000000',\r\n * name: 'create_users',\r\n * up: `\r\n * CREATE TABLE users (\r\n * id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\r\n * email VARCHAR(255) UNIQUE NOT NULL,\r\n * created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\r\n * )\r\n * `,\r\n * down: `DROP TABLE users`\r\n * });\r\n * ```\r\n */\r\nexport function sqlMigration(migration: {\r\n version: string;\r\n name: string;\r\n up: string;\r\n down: string;\r\n transactional?: boolean;\r\n}): Migration {\r\n return {\r\n version: migration.version,\r\n name: migration.name,\r\n up: migration.up,\r\n down: migration.down,\r\n transactional: migration.transactional,\r\n };\r\n}\r\n\r\n/**\r\n * Create a function-based migration for complex logic\r\n *\r\n * @example\r\n * ```typescript\r\n * export default createMigration({\r\n * version: '20240102000000',\r\n * name: 'seed_initial_data',\r\n * up: async (db) => {\r\n * await db.raw(`INSERT INTO users (email) VALUES ('admin@example.com')`);\r\n * await db.raw(`INSERT INTO settings (key, value) VALUES ('version', '1.0.0')`);\r\n * },\r\n * down: async (db) => {\r\n * await db.raw(`DELETE FROM settings WHERE key = 'version'`);\r\n * await db.raw(`DELETE FROM users WHERE email = 'admin@example.com'`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function createMigration(migration: {\r\n version: string;\r\n name: string;\r\n up: (db: IMigrationDatabase) => Promise<void>;\r\n down: (db: IMigrationDatabase) => Promise<void>;\r\n transactional?: boolean;\r\n}): Migration {\r\n return {\r\n version: migration.version,\r\n name: migration.name,\r\n up: migration.up,\r\n down: migration.down,\r\n transactional: migration.transactional,\r\n };\r\n}\r\n\r\n/**\r\n * Type-safe migration definition with inference\r\n * Useful for defining migrations in separate files\r\n *\r\n * @example\r\n * ```typescript\r\n * // migrations/20240101000000_create_users.ts\r\n * export const migration = defineMigration({\r\n * version: '20240101000000',\r\n * name: 'create_users',\r\n * up: 'CREATE TABLE users (...)',\r\n * down: 'DROP TABLE users'\r\n * });\r\n *\r\n * // Or with functions\r\n * export const migration = defineMigration({\r\n * version: '20240102000000',\r\n * name: 'complex_migration',\r\n * up: async (db) => { ... },\r\n * down: async (db) => { ... }\r\n * });\r\n * ```\r\n */\r\nexport function defineMigration<\r\n T extends {\r\n version: string;\r\n name: string;\r\n up: string | ((db: IMigrationDatabase) => Promise<void>);\r\n down: string | ((db: IMigrationDatabase) => Promise<void>);\r\n transactional?: boolean;\r\n }\r\n>(migration: T): Migration {\r\n return migration;\r\n}\r\n\r\n/**\r\n * Generate a version timestamp for new migrations\r\n *\r\n * @example\r\n * ```typescript\r\n * const version = generateVersion(); // '20240315143052'\r\n * ```\r\n */\r\nexport function generateVersion(): string {\r\n const now = new Date();\r\n return [\r\n now.getFullYear(),\r\n String(now.getMonth() + 1).padStart(2, '0'),\r\n String(now.getDate()).padStart(2, '0'),\r\n String(now.getHours()).padStart(2, '0'),\r\n String(now.getMinutes()).padStart(2, '0'),\r\n String(now.getSeconds()).padStart(2, '0'),\r\n ].join('');\r\n}\r\n\r\n/**\r\n * Common SQL snippets for migrations\r\n */\r\nexport const SQL = {\r\n /**\r\n * Create updated_at trigger function (PostgreSQL)\r\n */\r\n createUpdatedAtFunction: `\r\n CREATE OR REPLACE FUNCTION update_updated_at_column()\r\n RETURNS TRIGGER AS $$\r\n BEGIN\r\n NEW.updated_at = CURRENT_TIMESTAMP;\r\n RETURN NEW;\r\n END;\r\n $$ language 'plpgsql';\r\n `,\r\n\r\n /**\r\n * Create an updated_at trigger for a table\r\n */\r\n createUpdatedAtTrigger: (tableName: string) => `\r\n CREATE TRIGGER update_${tableName}_updated_at\r\n BEFORE UPDATE ON \"${tableName}\"\r\n FOR EACH ROW\r\n EXECUTE FUNCTION update_updated_at_column();\r\n `,\r\n\r\n /**\r\n * Drop an updated_at trigger\r\n */\r\n dropUpdatedAtTrigger: (tableName: string) => `\r\n DROP TRIGGER IF EXISTS update_${tableName}_updated_at ON \"${tableName}\";\r\n `,\r\n\r\n /**\r\n * Standard timestamp columns\r\n */\r\n timestampColumns: `\r\n created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,\r\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL\r\n `,\r\n\r\n /**\r\n * UUID primary key with default\r\n */\r\n uuidPrimaryKey: `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`,\r\n\r\n /**\r\n * Enable UUID extension\r\n */\r\n enableUuidExtension: `CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"`,\r\n\r\n /**\r\n * Enable pgcrypto for gen_random_uuid()\r\n */\r\n enablePgCrypto: `CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"`,\r\n};\r\n"],"mappings":"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","names":["module"]}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
// src/migrations/Migrator.ts
|
|
2
|
+
var DEFAULT_CONFIG = {
|
|
3
|
+
tableName: "_migrations",
|
|
4
|
+
schema: "public",
|
|
5
|
+
lockTimeout: 60,
|
|
6
|
+
validateChecksums: true,
|
|
7
|
+
migrationsDir: void 0
|
|
8
|
+
};
|
|
9
|
+
function generateChecksum(content) {
|
|
10
|
+
let hash = 0;
|
|
11
|
+
for (let i = 0; i < content.length; i++) {
|
|
12
|
+
const char = content.charCodeAt(i);
|
|
13
|
+
hash = (hash << 5) - hash + char;
|
|
14
|
+
hash = hash & hash;
|
|
15
|
+
}
|
|
16
|
+
return Math.abs(hash).toString(16).padStart(8, "0");
|
|
17
|
+
}
|
|
18
|
+
function sortMigrations(migrations) {
|
|
19
|
+
return [...migrations].sort((a, b) => a.version.localeCompare(b.version));
|
|
20
|
+
}
|
|
21
|
+
var Migrator = class {
|
|
22
|
+
db;
|
|
23
|
+
config;
|
|
24
|
+
migrations = [];
|
|
25
|
+
initialized = false;
|
|
26
|
+
locked = false;
|
|
27
|
+
constructor(db, config = {}) {
|
|
28
|
+
this.db = db;
|
|
29
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Get the fully qualified migration table name
|
|
33
|
+
*/
|
|
34
|
+
get tableName() {
|
|
35
|
+
return `"${this.config.schema}"."${this.config.tableName}"`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the lock table name
|
|
39
|
+
*/
|
|
40
|
+
get lockTableName() {
|
|
41
|
+
return `"${this.config.schema}"."${this.config.tableName}_lock"`;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Initialize migration tracking tables
|
|
45
|
+
*/
|
|
46
|
+
async initialize() {
|
|
47
|
+
if (this.initialized) return;
|
|
48
|
+
await this.db.raw(`CREATE SCHEMA IF NOT EXISTS "${this.config.schema}"`);
|
|
49
|
+
await this.db.raw(`
|
|
50
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
51
|
+
version VARCHAR(255) PRIMARY KEY,
|
|
52
|
+
name VARCHAR(255) NOT NULL,
|
|
53
|
+
applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
54
|
+
execution_time INTEGER NOT NULL,
|
|
55
|
+
checksum VARCHAR(64)
|
|
56
|
+
)
|
|
57
|
+
`);
|
|
58
|
+
await this.db.raw(`
|
|
59
|
+
CREATE TABLE IF NOT EXISTS ${this.lockTableName} (
|
|
60
|
+
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
61
|
+
locked_at TIMESTAMP WITH TIME ZONE,
|
|
62
|
+
locked_by VARCHAR(255),
|
|
63
|
+
CONSTRAINT single_row CHECK (id = 1)
|
|
64
|
+
)
|
|
65
|
+
`);
|
|
66
|
+
await this.db.raw(`
|
|
67
|
+
INSERT INTO ${this.lockTableName} (id, locked_at, locked_by)
|
|
68
|
+
VALUES (1, NULL, NULL)
|
|
69
|
+
ON CONFLICT (id) DO NOTHING
|
|
70
|
+
`);
|
|
71
|
+
this.initialized = true;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Acquire migration lock
|
|
75
|
+
*/
|
|
76
|
+
async lock() {
|
|
77
|
+
await this.initialize();
|
|
78
|
+
const lockId = `${process.pid}-${Date.now()}`;
|
|
79
|
+
const lockTimeout = new Date(Date.now() - this.config.lockTimeout * 1e3);
|
|
80
|
+
const result = await this.db.raw(
|
|
81
|
+
`
|
|
82
|
+
UPDATE ${this.lockTableName}
|
|
83
|
+
SET locked_at = CURRENT_TIMESTAMP, locked_by = $1
|
|
84
|
+
WHERE id = 1 AND (locked_at IS NULL OR locked_at < $2)
|
|
85
|
+
RETURNING locked_at
|
|
86
|
+
`,
|
|
87
|
+
[lockId, lockTimeout]
|
|
88
|
+
);
|
|
89
|
+
if (result.data.length > 0) {
|
|
90
|
+
this.locked = true;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Release migration lock
|
|
97
|
+
*/
|
|
98
|
+
async unlock() {
|
|
99
|
+
if (!this.locked) return;
|
|
100
|
+
await this.db.raw(
|
|
101
|
+
`
|
|
102
|
+
UPDATE ${this.lockTableName}
|
|
103
|
+
SET locked_at = NULL, locked_by = NULL
|
|
104
|
+
WHERE id = 1
|
|
105
|
+
`
|
|
106
|
+
);
|
|
107
|
+
this.locked = false;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Add migrations to the migrator
|
|
111
|
+
*/
|
|
112
|
+
addMigrations(migrations) {
|
|
113
|
+
for (const migration of migrations) {
|
|
114
|
+
if (!migration.checksum) {
|
|
115
|
+
const content = typeof migration.up === "string" ? migration.up : migration.up.toString();
|
|
116
|
+
migration.checksum = generateChecksum(content);
|
|
117
|
+
}
|
|
118
|
+
if (!this.migrations.find((m) => m.version === migration.version)) {
|
|
119
|
+
this.migrations.push(migration);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Load migrations from directory
|
|
125
|
+
* Expects files named: {version}_{name}.ts or {version}_{name}.sql
|
|
126
|
+
*/
|
|
127
|
+
async loadMigrations(dir) {
|
|
128
|
+
const { readdir, readFile } = await import("fs/promises");
|
|
129
|
+
const { join } = await import("path");
|
|
130
|
+
const files = await readdir(dir);
|
|
131
|
+
const migrationFiles = files.filter(
|
|
132
|
+
(f) => f.match(/^\d+_.*\.(ts|js|sql)$/)
|
|
133
|
+
);
|
|
134
|
+
for (const file of migrationFiles) {
|
|
135
|
+
const filePath = join(dir, file);
|
|
136
|
+
const [version, ...nameParts] = file.replace(/\.(ts|js|sql)$/, "").split("_");
|
|
137
|
+
const name = nameParts.join("_");
|
|
138
|
+
if (file.endsWith(".sql")) {
|
|
139
|
+
const content = await readFile(filePath, "utf-8");
|
|
140
|
+
const [up, down] = this.parseSqlMigration(content);
|
|
141
|
+
this.addMigrations([
|
|
142
|
+
{
|
|
143
|
+
version,
|
|
144
|
+
name,
|
|
145
|
+
up,
|
|
146
|
+
down
|
|
147
|
+
}
|
|
148
|
+
]);
|
|
149
|
+
} else {
|
|
150
|
+
const module = await import(filePath);
|
|
151
|
+
this.addMigrations([
|
|
152
|
+
{
|
|
153
|
+
version,
|
|
154
|
+
name,
|
|
155
|
+
up: module.up,
|
|
156
|
+
down: module.down,
|
|
157
|
+
transactional: module.transactional
|
|
158
|
+
}
|
|
159
|
+
]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Parse SQL migration file with -- UP and -- DOWN sections
|
|
165
|
+
*/
|
|
166
|
+
parseSqlMigration(content) {
|
|
167
|
+
const upMatch = content.match(/--\s*UP\s*\n([\s\S]*?)(?=--\s*DOWN|$)/i);
|
|
168
|
+
const downMatch = content.match(/--\s*DOWN\s*\n([\s\S]*?)$/i);
|
|
169
|
+
const up = upMatch?.[1]?.trim() ?? content.trim();
|
|
170
|
+
const down = downMatch?.[1]?.trim() ?? "";
|
|
171
|
+
return [up, down];
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get current migration status
|
|
175
|
+
*/
|
|
176
|
+
async status() {
|
|
177
|
+
await this.initialize();
|
|
178
|
+
const result = await this.db.raw(
|
|
179
|
+
`SELECT version, name, applied_at, execution_time, checksum
|
|
180
|
+
FROM ${this.tableName}
|
|
181
|
+
ORDER BY version ASC`
|
|
182
|
+
);
|
|
183
|
+
const applied = result.data.map((row) => ({
|
|
184
|
+
version: row.version,
|
|
185
|
+
name: row.name,
|
|
186
|
+
appliedAt: new Date(row.applied_at),
|
|
187
|
+
executionTime: row.execution_time,
|
|
188
|
+
checksum: row.checksum ?? void 0
|
|
189
|
+
}));
|
|
190
|
+
const appliedVersions = new Set(applied.map((m) => m.version));
|
|
191
|
+
const sortedMigrations = sortMigrations(this.migrations);
|
|
192
|
+
const pending = sortedMigrations.filter(
|
|
193
|
+
(m) => !appliedVersions.has(m.version)
|
|
194
|
+
);
|
|
195
|
+
return {
|
|
196
|
+
applied,
|
|
197
|
+
pending,
|
|
198
|
+
currentVersion: applied.length > 0 ? applied[applied.length - 1].version : null,
|
|
199
|
+
latestVersion: sortedMigrations.length > 0 ? sortedMigrations[sortedMigrations.length - 1].version : null
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Run all pending migrations (or up to a specific version)
|
|
204
|
+
*/
|
|
205
|
+
async up(options = {}) {
|
|
206
|
+
const { dryRun = false, to } = options;
|
|
207
|
+
const results = [];
|
|
208
|
+
if (!dryRun) {
|
|
209
|
+
const acquired = await this.lock();
|
|
210
|
+
if (!acquired) {
|
|
211
|
+
throw new Error("Could not acquire migration lock. Another migration may be in progress.");
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const status = await this.status();
|
|
216
|
+
let pending = status.pending;
|
|
217
|
+
if (to) {
|
|
218
|
+
const targetIndex = pending.findIndex((m) => m.version === to);
|
|
219
|
+
if (targetIndex === -1) {
|
|
220
|
+
throw new Error(`Target version ${to} not found in pending migrations`);
|
|
221
|
+
}
|
|
222
|
+
pending = pending.slice(0, targetIndex + 1);
|
|
223
|
+
}
|
|
224
|
+
for (const migration of pending) {
|
|
225
|
+
const result = await this.runMigration(migration, "up", dryRun);
|
|
226
|
+
results.push(result);
|
|
227
|
+
if (!result.success) {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} finally {
|
|
232
|
+
if (!dryRun) {
|
|
233
|
+
await this.unlock();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return results;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Rollback migrations
|
|
240
|
+
*/
|
|
241
|
+
async down(options = {}) {
|
|
242
|
+
const { dryRun = false, steps = 1 } = options;
|
|
243
|
+
const results = [];
|
|
244
|
+
if (!dryRun) {
|
|
245
|
+
const acquired = await this.lock();
|
|
246
|
+
if (!acquired) {
|
|
247
|
+
throw new Error("Could not acquire migration lock. Another migration may be in progress.");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
const status = await this.status();
|
|
252
|
+
const toRollback = status.applied.slice(-steps).reverse();
|
|
253
|
+
for (const record of toRollback) {
|
|
254
|
+
const migration = this.migrations.find((m) => m.version === record.version);
|
|
255
|
+
if (!migration) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
`Migration ${record.version} was applied but is not registered. Cannot rollback.`
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
const result = await this.runMigration(migration, "down", dryRun);
|
|
261
|
+
results.push(result);
|
|
262
|
+
if (!result.success) {
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} finally {
|
|
267
|
+
if (!dryRun) {
|
|
268
|
+
await this.unlock();
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return results;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Rollback all migrations and re-run them
|
|
275
|
+
*/
|
|
276
|
+
async reset(options = {}) {
|
|
277
|
+
const status = await this.status();
|
|
278
|
+
const downResults = await this.down({
|
|
279
|
+
dryRun: options.dryRun,
|
|
280
|
+
steps: status.applied.length
|
|
281
|
+
});
|
|
282
|
+
if (downResults.every((r) => r.success)) {
|
|
283
|
+
const upResults = await this.up({ dryRun: options.dryRun });
|
|
284
|
+
return [...downResults, ...upResults];
|
|
285
|
+
}
|
|
286
|
+
return downResults;
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Migrate to a specific version
|
|
290
|
+
*/
|
|
291
|
+
async goto(version, options = {}) {
|
|
292
|
+
const status = await this.status();
|
|
293
|
+
const currentVersion = status.currentVersion;
|
|
294
|
+
const results = [];
|
|
295
|
+
if (currentVersion === version) {
|
|
296
|
+
return results;
|
|
297
|
+
}
|
|
298
|
+
const isUp = !currentVersion || version > currentVersion;
|
|
299
|
+
if (isUp) {
|
|
300
|
+
return this.up({ ...options, to: version });
|
|
301
|
+
} else {
|
|
302
|
+
const currentIndex = status.applied.findIndex((m) => m.version === currentVersion);
|
|
303
|
+
const targetIndex = status.applied.findIndex((m) => m.version === version);
|
|
304
|
+
if (targetIndex === -1) {
|
|
305
|
+
throw new Error(`Target version ${version} not found in applied migrations`);
|
|
306
|
+
}
|
|
307
|
+
const steps = currentIndex - targetIndex;
|
|
308
|
+
return this.down({ ...options, steps });
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Run a single migration
|
|
313
|
+
*/
|
|
314
|
+
async runMigration(migration, direction, dryRun) {
|
|
315
|
+
const startTime = Date.now();
|
|
316
|
+
const action = direction === "up" ? migration.up : migration.down;
|
|
317
|
+
try {
|
|
318
|
+
if (dryRun) {
|
|
319
|
+
return {
|
|
320
|
+
migration,
|
|
321
|
+
success: true,
|
|
322
|
+
executionTime: 0,
|
|
323
|
+
dryRun: true
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
const transactional = migration.transactional !== false;
|
|
327
|
+
const runAction = async (db) => {
|
|
328
|
+
if (typeof action === "string") {
|
|
329
|
+
const result = await db.raw(action);
|
|
330
|
+
if (result.error) {
|
|
331
|
+
throw result.error;
|
|
332
|
+
}
|
|
333
|
+
} else {
|
|
334
|
+
await action(db);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
if (transactional) {
|
|
338
|
+
await this.db.transaction(async (tx) => {
|
|
339
|
+
await runAction(tx);
|
|
340
|
+
});
|
|
341
|
+
} else {
|
|
342
|
+
await runAction(this.db);
|
|
343
|
+
}
|
|
344
|
+
const executionTime = Date.now() - startTime;
|
|
345
|
+
if (direction === "up") {
|
|
346
|
+
await this.db.raw(
|
|
347
|
+
`INSERT INTO ${this.tableName} (version, name, execution_time, checksum)
|
|
348
|
+
VALUES ($1, $2, $3, $4)`,
|
|
349
|
+
[migration.version, migration.name, executionTime, migration.checksum]
|
|
350
|
+
);
|
|
351
|
+
} else {
|
|
352
|
+
await this.db.raw(
|
|
353
|
+
`DELETE FROM ${this.tableName} WHERE version = $1`,
|
|
354
|
+
[migration.version]
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return {
|
|
358
|
+
migration,
|
|
359
|
+
success: true,
|
|
360
|
+
executionTime,
|
|
361
|
+
dryRun: false
|
|
362
|
+
};
|
|
363
|
+
} catch (error) {
|
|
364
|
+
return {
|
|
365
|
+
migration,
|
|
366
|
+
success: false,
|
|
367
|
+
executionTime: Date.now() - startTime,
|
|
368
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
369
|
+
dryRun: false
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Create a new migration file
|
|
375
|
+
*/
|
|
376
|
+
async create(name) {
|
|
377
|
+
const { writeFile, mkdir } = await import("fs/promises");
|
|
378
|
+
const { join } = await import("path");
|
|
379
|
+
if (!this.config.migrationsDir) {
|
|
380
|
+
throw new Error("migrationsDir must be configured to create migrations");
|
|
381
|
+
}
|
|
382
|
+
await mkdir(this.config.migrationsDir, { recursive: true });
|
|
383
|
+
const now = /* @__PURE__ */ new Date();
|
|
384
|
+
const version = [
|
|
385
|
+
now.getFullYear(),
|
|
386
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
387
|
+
String(now.getDate()).padStart(2, "0"),
|
|
388
|
+
String(now.getHours()).padStart(2, "0"),
|
|
389
|
+
String(now.getMinutes()).padStart(2, "0"),
|
|
390
|
+
String(now.getSeconds()).padStart(2, "0")
|
|
391
|
+
].join("");
|
|
392
|
+
const safeName = name.replace(/[^a-zA-Z0-9_]/g, "_").toLowerCase();
|
|
393
|
+
const fileName = `${version}_${safeName}.sql`;
|
|
394
|
+
const filePath = join(this.config.migrationsDir, fileName);
|
|
395
|
+
const template = `-- Migration: ${name}
|
|
396
|
+
-- Version: ${version}
|
|
397
|
+
-- Created: ${now.toISOString()}
|
|
398
|
+
|
|
399
|
+
-- UP
|
|
400
|
+
-- Add your forward migration SQL here
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
-- DOWN
|
|
404
|
+
-- Add your rollback migration SQL here
|
|
405
|
+
|
|
406
|
+
`;
|
|
407
|
+
await writeFile(filePath, template, "utf-8");
|
|
408
|
+
return filePath;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Validate migration integrity
|
|
412
|
+
*/
|
|
413
|
+
async validate() {
|
|
414
|
+
const errors = [];
|
|
415
|
+
const status = await this.status();
|
|
416
|
+
if (!this.config.validateChecksums) {
|
|
417
|
+
return { valid: true, errors };
|
|
418
|
+
}
|
|
419
|
+
for (const applied of status.applied) {
|
|
420
|
+
const migration = this.migrations.find((m) => m.version === applied.version);
|
|
421
|
+
if (!migration) {
|
|
422
|
+
errors.push(
|
|
423
|
+
`Migration ${applied.version} (${applied.name}) was applied but is not registered`
|
|
424
|
+
);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (applied.checksum && migration.checksum !== applied.checksum) {
|
|
428
|
+
errors.push(
|
|
429
|
+
`Migration ${applied.version} (${applied.name}) has been modified since it was applied`
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
valid: errors.length === 0,
|
|
435
|
+
errors
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/migrations/helpers.ts
|
|
441
|
+
function sqlMigration(migration) {
|
|
442
|
+
return {
|
|
443
|
+
version: migration.version,
|
|
444
|
+
name: migration.name,
|
|
445
|
+
up: migration.up,
|
|
446
|
+
down: migration.down,
|
|
447
|
+
transactional: migration.transactional
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function createMigration(migration) {
|
|
451
|
+
return {
|
|
452
|
+
version: migration.version,
|
|
453
|
+
name: migration.name,
|
|
454
|
+
up: migration.up,
|
|
455
|
+
down: migration.down,
|
|
456
|
+
transactional: migration.transactional
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
function defineMigration(migration) {
|
|
460
|
+
return migration;
|
|
461
|
+
}
|
|
462
|
+
export {
|
|
463
|
+
Migrator,
|
|
464
|
+
createMigration,
|
|
465
|
+
defineMigration,
|
|
466
|
+
sqlMigration
|
|
467
|
+
};
|
|
468
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/migrations/Migrator.ts","../../src/migrations/helpers.ts"],"sourcesContent":["/**\r\n * Database Migrator\r\n *\r\n * Enterprise-grade database migration system supporting:\r\n * - PostgreSQL (primary)\r\n * - Memory database (for testing)\r\n * - Transaction-safe migrations\r\n * - Concurrent execution locking\r\n * - Checksum validation\r\n * - Dry-run mode\r\n */\r\n\r\nimport type {\r\n IMigrator,\r\n Migration,\r\n MigrationRecord,\r\n MigrationResult,\r\n MigrationStatus,\r\n MigratorConfig,\r\n IMigrationDatabase,\r\n} from '../interfaces/IMigration';\r\n\r\nconst DEFAULT_CONFIG: Required<Omit<MigratorConfig, 'migrationsDir'>> & { migrationsDir?: string } = {\r\n tableName: '_migrations',\r\n schema: 'public',\r\n lockTimeout: 60,\r\n validateChecksums: true,\r\n migrationsDir: undefined,\r\n};\r\n\r\n/**\r\n * Generate a checksum for migration content\r\n */\r\nfunction generateChecksum(content: string): string {\r\n // Simple hash for checksum - in production you might use crypto\r\n let hash = 0;\r\n for (let i = 0; i < content.length; i++) {\r\n const char = content.charCodeAt(i);\r\n hash = (hash << 5) - hash + char;\r\n hash = hash & hash; // Convert to 32-bit integer\r\n }\r\n return Math.abs(hash).toString(16).padStart(8, '0');\r\n}\r\n\r\n/**\r\n * Sort migrations by version\r\n */\r\nfunction sortMigrations(migrations: Migration[]): Migration[] {\r\n return [...migrations].sort((a, b) => a.version.localeCompare(b.version));\r\n}\r\n\r\nexport class Migrator implements IMigrator {\r\n private db: IMigrationDatabase;\r\n private config: Required<Omit<MigratorConfig, 'migrationsDir'>> & { migrationsDir?: string };\r\n private migrations: Migration[] = [];\r\n private initialized = false;\r\n private locked = false;\r\n\r\n constructor(db: IMigrationDatabase, config: MigratorConfig = {}) {\r\n this.db = db;\r\n this.config = { ...DEFAULT_CONFIG, ...config };\r\n }\r\n\r\n /**\r\n * Get the fully qualified migration table name\r\n */\r\n private get tableName(): string {\r\n return `\"${this.config.schema}\".\"${this.config.tableName}\"`;\r\n }\r\n\r\n /**\r\n * Get the lock table name\r\n */\r\n private get lockTableName(): string {\r\n return `\"${this.config.schema}\".\"${this.config.tableName}_lock\"`;\r\n }\r\n\r\n /**\r\n * Initialize migration tracking tables\r\n */\r\n private async initialize(): Promise<void> {\r\n if (this.initialized) return;\r\n\r\n // Create schema if not exists\r\n await this.db.raw(`CREATE SCHEMA IF NOT EXISTS \"${this.config.schema}\"`);\r\n\r\n // Create migrations tracking table\r\n await this.db.raw(`\r\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\r\n version VARCHAR(255) PRIMARY KEY,\r\n name VARCHAR(255) NOT NULL,\r\n applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,\r\n execution_time INTEGER NOT NULL,\r\n checksum VARCHAR(64)\r\n )\r\n `);\r\n\r\n // Create lock table for concurrent safety\r\n await this.db.raw(`\r\n CREATE TABLE IF NOT EXISTS ${this.lockTableName} (\r\n id INTEGER PRIMARY KEY DEFAULT 1,\r\n locked_at TIMESTAMP WITH TIME ZONE,\r\n locked_by VARCHAR(255),\r\n CONSTRAINT single_row CHECK (id = 1)\r\n )\r\n `);\r\n\r\n // Insert initial lock row if not exists\r\n await this.db.raw(`\r\n INSERT INTO ${this.lockTableName} (id, locked_at, locked_by)\r\n VALUES (1, NULL, NULL)\r\n ON CONFLICT (id) DO NOTHING\r\n `);\r\n\r\n this.initialized = true;\r\n }\r\n\r\n /**\r\n * Acquire migration lock\r\n */\r\n async lock(): Promise<boolean> {\r\n await this.initialize();\r\n\r\n const lockId = `${process.pid}-${Date.now()}`;\r\n const lockTimeout = new Date(Date.now() - this.config.lockTimeout * 1000);\r\n\r\n // Try to acquire lock (only if not locked or lock expired)\r\n const result = await this.db.raw<{ locked_at: Date | null }>(\r\n `\r\n UPDATE ${this.lockTableName}\r\n SET locked_at = CURRENT_TIMESTAMP, locked_by = $1\r\n WHERE id = 1 AND (locked_at IS NULL OR locked_at < $2)\r\n RETURNING locked_at\r\n `,\r\n [lockId, lockTimeout]\r\n );\r\n\r\n if (result.data.length > 0) {\r\n this.locked = true;\r\n return true;\r\n }\r\n\r\n return false;\r\n }\r\n\r\n /**\r\n * Release migration lock\r\n */\r\n async unlock(): Promise<void> {\r\n if (!this.locked) return;\r\n\r\n await this.db.raw(\r\n `\r\n UPDATE ${this.lockTableName}\r\n SET locked_at = NULL, locked_by = NULL\r\n WHERE id = 1\r\n `\r\n );\r\n\r\n this.locked = false;\r\n }\r\n\r\n /**\r\n * Add migrations to the migrator\r\n */\r\n addMigrations(migrations: Migration[]): void {\r\n for (const migration of migrations) {\r\n // Generate checksum if not provided\r\n if (!migration.checksum) {\r\n const content =\r\n typeof migration.up === 'string' ? migration.up : migration.up.toString();\r\n migration.checksum = generateChecksum(content);\r\n }\r\n\r\n // Avoid duplicates\r\n if (!this.migrations.find((m) => m.version === migration.version)) {\r\n this.migrations.push(migration);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Load migrations from directory\r\n * Expects files named: {version}_{name}.ts or {version}_{name}.sql\r\n */\r\n async loadMigrations(dir: string): Promise<void> {\r\n // Dynamic import for Node.js file system\r\n const { readdir, readFile } = await import('fs/promises');\r\n const { join } = await import('path');\r\n\r\n const files = await readdir(dir);\r\n const migrationFiles = files.filter(\r\n (f) => f.match(/^\\d+_.*\\.(ts|js|sql)$/)\r\n );\r\n\r\n for (const file of migrationFiles) {\r\n const filePath = join(dir, file);\r\n const [version, ...nameParts] = file.replace(/\\.(ts|js|sql)$/, '').split('_');\r\n const name = nameParts.join('_');\r\n\r\n if (file.endsWith('.sql')) {\r\n // SQL file - parse UP and DOWN sections\r\n const content = await readFile(filePath, 'utf-8');\r\n const [up, down] = this.parseSqlMigration(content);\r\n\r\n this.addMigrations([\r\n {\r\n version: version!,\r\n name,\r\n up,\r\n down,\r\n },\r\n ]);\r\n } else {\r\n // TypeScript/JavaScript file - import the module\r\n const module = await import(filePath);\r\n this.addMigrations([\r\n {\r\n version: version!,\r\n name,\r\n up: module.up,\r\n down: module.down,\r\n transactional: module.transactional,\r\n },\r\n ]);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Parse SQL migration file with -- UP and -- DOWN sections\r\n */\r\n private parseSqlMigration(content: string): [string, string] {\r\n const upMatch = content.match(/--\\s*UP\\s*\\n([\\s\\S]*?)(?=--\\s*DOWN|$)/i);\r\n const downMatch = content.match(/--\\s*DOWN\\s*\\n([\\s\\S]*?)$/i);\r\n\r\n const up = upMatch?.[1]?.trim() ?? content.trim();\r\n const down = downMatch?.[1]?.trim() ?? '';\r\n\r\n return [up, down];\r\n }\r\n\r\n /**\r\n * Get current migration status\r\n */\r\n async status(): Promise<MigrationStatus> {\r\n await this.initialize();\r\n\r\n // Get applied migrations\r\n const result = await this.db.raw<{\r\n version: string;\r\n name: string;\r\n applied_at: Date;\r\n execution_time: number;\r\n checksum: string | null;\r\n }>(\r\n `SELECT version, name, applied_at, execution_time, checksum\r\n FROM ${this.tableName}\r\n ORDER BY version ASC`\r\n );\r\n\r\n const applied: MigrationRecord[] = result.data.map((row) => ({\r\n version: row.version,\r\n name: row.name,\r\n appliedAt: new Date(row.applied_at),\r\n executionTime: row.execution_time,\r\n checksum: row.checksum ?? undefined,\r\n }));\r\n\r\n const appliedVersions = new Set(applied.map((m) => m.version));\r\n const sortedMigrations = sortMigrations(this.migrations);\r\n\r\n const pending = sortedMigrations.filter(\r\n (m) => !appliedVersions.has(m.version)\r\n );\r\n\r\n return {\r\n applied,\r\n pending,\r\n currentVersion: applied.length > 0 ? applied[applied.length - 1]!.version : null,\r\n latestVersion:\r\n sortedMigrations.length > 0\r\n ? sortedMigrations[sortedMigrations.length - 1]!.version\r\n : null,\r\n };\r\n }\r\n\r\n /**\r\n * Run all pending migrations (or up to a specific version)\r\n */\r\n async up(options: { dryRun?: boolean; to?: string } = {}): Promise<MigrationResult[]> {\r\n const { dryRun = false, to } = options;\r\n const results: MigrationResult[] = [];\r\n\r\n if (!dryRun) {\r\n const acquired = await this.lock();\r\n if (!acquired) {\r\n throw new Error('Could not acquire migration lock. Another migration may be in progress.');\r\n }\r\n }\r\n\r\n try {\r\n const status = await this.status();\r\n let pending = status.pending;\r\n\r\n // Filter to specific version if requested\r\n if (to) {\r\n const targetIndex = pending.findIndex((m) => m.version === to);\r\n if (targetIndex === -1) {\r\n throw new Error(`Target version ${to} not found in pending migrations`);\r\n }\r\n pending = pending.slice(0, targetIndex + 1);\r\n }\r\n\r\n for (const migration of pending) {\r\n const result = await this.runMigration(migration, 'up', dryRun);\r\n results.push(result);\r\n\r\n if (!result.success) {\r\n break; // Stop on first failure\r\n }\r\n }\r\n } finally {\r\n if (!dryRun) {\r\n await this.unlock();\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Rollback migrations\r\n */\r\n async down(options: { dryRun?: boolean; steps?: number } = {}): Promise<MigrationResult[]> {\r\n const { dryRun = false, steps = 1 } = options;\r\n const results: MigrationResult[] = [];\r\n\r\n if (!dryRun) {\r\n const acquired = await this.lock();\r\n if (!acquired) {\r\n throw new Error('Could not acquire migration lock. Another migration may be in progress.');\r\n }\r\n }\r\n\r\n try {\r\n const status = await this.status();\r\n\r\n // Get the last N applied migrations (in reverse order)\r\n const toRollback = status.applied.slice(-steps).reverse();\r\n\r\n for (const record of toRollback) {\r\n const migration = this.migrations.find((m) => m.version === record.version);\r\n if (!migration) {\r\n throw new Error(\r\n `Migration ${record.version} was applied but is not registered. Cannot rollback.`\r\n );\r\n }\r\n\r\n const result = await this.runMigration(migration, 'down', dryRun);\r\n results.push(result);\r\n\r\n if (!result.success) {\r\n break;\r\n }\r\n }\r\n } finally {\r\n if (!dryRun) {\r\n await this.unlock();\r\n }\r\n }\r\n\r\n return results;\r\n }\r\n\r\n /**\r\n * Rollback all migrations and re-run them\r\n */\r\n async reset(options: { dryRun?: boolean } = {}): Promise<MigrationResult[]> {\r\n const status = await this.status();\r\n const downResults = await this.down({\r\n dryRun: options.dryRun,\r\n steps: status.applied.length,\r\n });\r\n\r\n // Only proceed with up if all downs succeeded\r\n if (downResults.every((r) => r.success)) {\r\n const upResults = await this.up({ dryRun: options.dryRun });\r\n return [...downResults, ...upResults];\r\n }\r\n\r\n return downResults;\r\n }\r\n\r\n /**\r\n * Migrate to a specific version\r\n */\r\n async goto(version: string, options: { dryRun?: boolean } = {}): Promise<MigrationResult[]> {\r\n const status = await this.status();\r\n const currentVersion = status.currentVersion;\r\n const results: MigrationResult[] = [];\r\n\r\n // If already at target, nothing to do\r\n if (currentVersion === version) {\r\n return results;\r\n }\r\n\r\n // Determine direction\r\n const isUp = !currentVersion || version > currentVersion;\r\n\r\n if (isUp) {\r\n return this.up({ ...options, to: version });\r\n } else {\r\n // Find how many steps down we need to go\r\n const currentIndex = status.applied.findIndex((m) => m.version === currentVersion);\r\n const targetIndex = status.applied.findIndex((m) => m.version === version);\r\n\r\n if (targetIndex === -1) {\r\n throw new Error(`Target version ${version} not found in applied migrations`);\r\n }\r\n\r\n const steps = currentIndex - targetIndex;\r\n return this.down({ ...options, steps });\r\n }\r\n }\r\n\r\n /**\r\n * Run a single migration\r\n */\r\n private async runMigration(\r\n migration: Migration,\r\n direction: 'up' | 'down',\r\n dryRun: boolean\r\n ): Promise<MigrationResult> {\r\n const startTime = Date.now();\r\n const action = direction === 'up' ? migration.up : migration.down;\r\n\r\n try {\r\n if (dryRun) {\r\n // Just simulate - don't actually run\r\n return {\r\n migration,\r\n success: true,\r\n executionTime: 0,\r\n dryRun: true,\r\n };\r\n }\r\n\r\n const transactional = migration.transactional !== false;\r\n const runAction = async (db: IMigrationDatabase) => {\r\n if (typeof action === 'string') {\r\n const result = await db.raw(action);\r\n if (result.error) {\r\n throw result.error;\r\n }\r\n } else {\r\n await action(db);\r\n }\r\n };\r\n\r\n if (transactional) {\r\n await this.db.transaction(async (tx) => {\r\n await runAction(tx);\r\n });\r\n } else {\r\n await runAction(this.db);\r\n }\r\n\r\n const executionTime = Date.now() - startTime;\r\n\r\n // Update tracking table\r\n if (direction === 'up') {\r\n await this.db.raw(\r\n `INSERT INTO ${this.tableName} (version, name, execution_time, checksum)\r\n VALUES ($1, $2, $3, $4)`,\r\n [migration.version, migration.name, executionTime, migration.checksum]\r\n );\r\n } else {\r\n await this.db.raw(\r\n `DELETE FROM ${this.tableName} WHERE version = $1`,\r\n [migration.version]\r\n );\r\n }\r\n\r\n return {\r\n migration,\r\n success: true,\r\n executionTime,\r\n dryRun: false,\r\n };\r\n } catch (error) {\r\n return {\r\n migration,\r\n success: false,\r\n executionTime: Date.now() - startTime,\r\n error: error instanceof Error ? error : new Error(String(error)),\r\n dryRun: false,\r\n };\r\n }\r\n }\r\n\r\n /**\r\n * Create a new migration file\r\n */\r\n async create(name: string): Promise<string> {\r\n const { writeFile, mkdir } = await import('fs/promises');\r\n const { join } = await import('path');\r\n\r\n if (!this.config.migrationsDir) {\r\n throw new Error('migrationsDir must be configured to create migrations');\r\n }\r\n\r\n // Ensure directory exists\r\n await mkdir(this.config.migrationsDir, { recursive: true });\r\n\r\n // Generate version timestamp\r\n const now = new Date();\r\n const version = [\r\n now.getFullYear(),\r\n String(now.getMonth() + 1).padStart(2, '0'),\r\n String(now.getDate()).padStart(2, '0'),\r\n String(now.getHours()).padStart(2, '0'),\r\n String(now.getMinutes()).padStart(2, '0'),\r\n String(now.getSeconds()).padStart(2, '0'),\r\n ].join('');\r\n\r\n const safeName = name.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();\r\n const fileName = `${version}_${safeName}.sql`;\r\n const filePath = join(this.config.migrationsDir, fileName);\r\n\r\n const template = `-- Migration: ${name}\r\n-- Version: ${version}\r\n-- Created: ${now.toISOString()}\r\n\r\n-- UP\r\n-- Add your forward migration SQL here\r\n\r\n\r\n-- DOWN\r\n-- Add your rollback migration SQL here\r\n\r\n`;\r\n\r\n await writeFile(filePath, template, 'utf-8');\r\n\r\n return filePath;\r\n }\r\n\r\n /**\r\n * Validate migration integrity\r\n */\r\n async validate(): Promise<{ valid: boolean; errors: string[] }> {\r\n const errors: string[] = [];\r\n const status = await this.status();\r\n\r\n if (!this.config.validateChecksums) {\r\n return { valid: true, errors };\r\n }\r\n\r\n for (const applied of status.applied) {\r\n const migration = this.migrations.find((m) => m.version === applied.version);\r\n\r\n if (!migration) {\r\n errors.push(\r\n `Migration ${applied.version} (${applied.name}) was applied but is not registered`\r\n );\r\n continue;\r\n }\r\n\r\n if (applied.checksum && migration.checksum !== applied.checksum) {\r\n errors.push(\r\n `Migration ${applied.version} (${applied.name}) has been modified since it was applied`\r\n );\r\n }\r\n }\r\n\r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n };\r\n }\r\n}\r\n","/**\r\n * Migration Helper Functions\r\n *\r\n * Utilities for creating migrations in a type-safe and convenient way.\r\n */\r\n\r\nimport type { Migration, IMigrationDatabase } from '../interfaces/IMigration';\r\n\r\n/**\r\n * Create a SQL-based migration\r\n *\r\n * @example\r\n * ```typescript\r\n * export default sqlMigration({\r\n * version: '20240101000000',\r\n * name: 'create_users',\r\n * up: `\r\n * CREATE TABLE users (\r\n * id UUID PRIMARY KEY DEFAULT gen_random_uuid(),\r\n * email VARCHAR(255) UNIQUE NOT NULL,\r\n * created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()\r\n * )\r\n * `,\r\n * down: `DROP TABLE users`\r\n * });\r\n * ```\r\n */\r\nexport function sqlMigration(migration: {\r\n version: string;\r\n name: string;\r\n up: string;\r\n down: string;\r\n transactional?: boolean;\r\n}): Migration {\r\n return {\r\n version: migration.version,\r\n name: migration.name,\r\n up: migration.up,\r\n down: migration.down,\r\n transactional: migration.transactional,\r\n };\r\n}\r\n\r\n/**\r\n * Create a function-based migration for complex logic\r\n *\r\n * @example\r\n * ```typescript\r\n * export default createMigration({\r\n * version: '20240102000000',\r\n * name: 'seed_initial_data',\r\n * up: async (db) => {\r\n * await db.raw(`INSERT INTO users (email) VALUES ('admin@example.com')`);\r\n * await db.raw(`INSERT INTO settings (key, value) VALUES ('version', '1.0.0')`);\r\n * },\r\n * down: async (db) => {\r\n * await db.raw(`DELETE FROM settings WHERE key = 'version'`);\r\n * await db.raw(`DELETE FROM users WHERE email = 'admin@example.com'`);\r\n * }\r\n * });\r\n * ```\r\n */\r\nexport function createMigration(migration: {\r\n version: string;\r\n name: string;\r\n up: (db: IMigrationDatabase) => Promise<void>;\r\n down: (db: IMigrationDatabase) => Promise<void>;\r\n transactional?: boolean;\r\n}): Migration {\r\n return {\r\n version: migration.version,\r\n name: migration.name,\r\n up: migration.up,\r\n down: migration.down,\r\n transactional: migration.transactional,\r\n };\r\n}\r\n\r\n/**\r\n * Type-safe migration definition with inference\r\n * Useful for defining migrations in separate files\r\n *\r\n * @example\r\n * ```typescript\r\n * // migrations/20240101000000_create_users.ts\r\n * export const migration = defineMigration({\r\n * version: '20240101000000',\r\n * name: 'create_users',\r\n * up: 'CREATE TABLE users (...)',\r\n * down: 'DROP TABLE users'\r\n * });\r\n *\r\n * // Or with functions\r\n * export const migration = defineMigration({\r\n * version: '20240102000000',\r\n * name: 'complex_migration',\r\n * up: async (db) => { ... },\r\n * down: async (db) => { ... }\r\n * });\r\n * ```\r\n */\r\nexport function defineMigration<\r\n T extends {\r\n version: string;\r\n name: string;\r\n up: string | ((db: IMigrationDatabase) => Promise<void>);\r\n down: string | ((db: IMigrationDatabase) => Promise<void>);\r\n transactional?: boolean;\r\n }\r\n>(migration: T): Migration {\r\n return migration;\r\n}\r\n\r\n/**\r\n * Generate a version timestamp for new migrations\r\n *\r\n * @example\r\n * ```typescript\r\n * const version = generateVersion(); // '20240315143052'\r\n * ```\r\n */\r\nexport function generateVersion(): string {\r\n const now = new Date();\r\n return [\r\n now.getFullYear(),\r\n String(now.getMonth() + 1).padStart(2, '0'),\r\n String(now.getDate()).padStart(2, '0'),\r\n String(now.getHours()).padStart(2, '0'),\r\n String(now.getMinutes()).padStart(2, '0'),\r\n String(now.getSeconds()).padStart(2, '0'),\r\n ].join('');\r\n}\r\n\r\n/**\r\n * Common SQL snippets for migrations\r\n */\r\nexport const SQL = {\r\n /**\r\n * Create updated_at trigger function (PostgreSQL)\r\n */\r\n createUpdatedAtFunction: `\r\n CREATE OR REPLACE FUNCTION update_updated_at_column()\r\n RETURNS TRIGGER AS $$\r\n BEGIN\r\n NEW.updated_at = CURRENT_TIMESTAMP;\r\n RETURN NEW;\r\n END;\r\n $$ language 'plpgsql';\r\n `,\r\n\r\n /**\r\n * Create an updated_at trigger for a table\r\n */\r\n createUpdatedAtTrigger: (tableName: string) => `\r\n CREATE TRIGGER update_${tableName}_updated_at\r\n BEFORE UPDATE ON \"${tableName}\"\r\n FOR EACH ROW\r\n EXECUTE FUNCTION update_updated_at_column();\r\n `,\r\n\r\n /**\r\n * Drop an updated_at trigger\r\n */\r\n dropUpdatedAtTrigger: (tableName: string) => `\r\n DROP TRIGGER IF EXISTS update_${tableName}_updated_at ON \"${tableName}\";\r\n `,\r\n\r\n /**\r\n * Standard timestamp columns\r\n */\r\n timestampColumns: `\r\n created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL,\r\n updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL\r\n `,\r\n\r\n /**\r\n * UUID primary key with default\r\n */\r\n uuidPrimaryKey: `id UUID PRIMARY KEY DEFAULT gen_random_uuid()`,\r\n\r\n /**\r\n * Enable UUID extension\r\n */\r\n enableUuidExtension: `CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"`,\r\n\r\n /**\r\n * Enable pgcrypto for gen_random_uuid()\r\n */\r\n enablePgCrypto: `CREATE EXTENSION IF NOT EXISTS \"pgcrypto\"`,\r\n};\r\n"],"mappings":"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","names":[]}
|