UNPKG

iagate-querykit

Version:

QueryKit: lightweight TypeScript query toolkit with models, views, triggers, events, scheduler and adapters (better-sqlite3).

150 lines (149 loc) 5.26 kB
import { QueryKitConfig } from './config'; import { QueryBuilder } from './query-builder'; function getExec(explicit) { const exec = explicit || QueryKitConfig.defaultExecutor; if (!exec) throw new Error('No executor configured for QueryKit'); return exec; } function migrationsTableSql(dialect) { switch (dialect) { case 'postgres': return `CREATE TABLE IF NOT EXISTS querykit_migrations (id VARCHAR(255) PRIMARY KEY, applied_at TIMESTAMP DEFAULT NOW())`; case 'mysql': return `CREATE TABLE IF NOT EXISTS querykit_migrations (id VARCHAR(255) PRIMARY KEY, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)`; case 'mssql': return `IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='querykit_migrations' and xtype='U') CREATE TABLE querykit_migrations (id NVARCHAR(255) PRIMARY KEY, applied_at DATETIME DEFAULT GETDATE())`; case 'oracle': return `BEGIN EXECUTE IMMEDIATE 'CREATE TABLE querykit_migrations (id VARCHAR2(255) PRIMARY KEY, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -955 THEN RAISE; END IF; END;`; default: return `CREATE TABLE IF NOT EXISTS querykit_migrations (id TEXT PRIMARY KEY, applied_at DATETIME DEFAULT CURRENT_TIMESTAMP)`; } } async function ensureTable(exec) { const sql = migrationsTableSql(exec.dialect); if (exec.runSync) { exec.runSync(sql, []); } else { await exec.executeQuery(sql, []); } } export async function listAppliedMigrations(executor) { const exec = getExec(executor); await ensureTable(exec); const sql = `SELECT id FROM querykit_migrations ORDER BY applied_at ASC`; if (exec.executeQuerySync) { const res = exec.executeQuerySync(sql, []); return (res?.data || []).map(r => r.id); } const res = await exec.executeQuery(sql, []); return (res?.data || []).map(r => r.id); } async function execStep(step, ctx) { if (typeof step === 'string') { await ctx.query(step); return; } if (Array.isArray(step)) { for (const s of step) await execStep(s, ctx); return; } await Promise.resolve(step(ctx)); } export async function migrateUp(migrations, opts = {}) { const exec = getExec(opts.executor); await ensureTable(exec); const applied = new Set(await listAppliedMigrations(exec)); const target = opts.to; const ctx = { exec, dialect: exec.dialect, query: async (sql, bindings = []) => { await exec.executeQuery(sql, bindings); }, runSync: (sql, bindings = []) => { if (exec.runSync) { exec.runSync(sql, bindings); } else { throw new Error('runSync not supported by executor'); } }, qb: (name) => new QueryBuilder(name) }; const newlyApplied = []; for (const mig of migrations) { if (applied.has(mig.id)) { if (target && mig.id === target) break; continue; } await execStep(mig.up, ctx); const ins = `INSERT INTO querykit_migrations (id) VALUES (?)`; if (exec.runSync) { exec.runSync(ins, [mig.id]); } else { await exec.executeQuery(ins, [mig.id]); } newlyApplied.push(mig.id); if (target && mig.id === target) break; } return { applied: newlyApplied }; } export async function migrateDown(migrations, opts = {}) { const exec = getExec(opts.executor); await ensureTable(exec); const applied = await listAppliedMigrations(exec); const byId = {}; migrations.forEach(m => { byId[m.id] = m; }); const ctx = { exec, dialect: exec.dialect, query: async (sql, bindings = []) => { await exec.executeQuery(sql, bindings); }, runSync: (sql, bindings = []) => { if (exec.runSync) { exec.runSync(sql, bindings); } else { throw new Error('runSync not supported by executor'); } }, qb: (name) => new QueryBuilder(name) }; const target = opts.to; let remaining = typeof opts.steps === 'number' ? Math.max(0, opts.steps) : Infinity; const reverted = []; for (let i = applied.length - 1; i >= 0 && remaining > 0; i--) { const id = applied[i]; const mig = byId[id]; if (!mig) continue; if (mig.down) { await execStep(mig.down, ctx); } const del = `DELETE FROM querykit_migrations WHERE id = ?`; if (exec.runSync) { exec.runSync(del, [id]); } else { await exec.executeQuery(del, [id]); } reverted.push(id); remaining--; if (target && id === target) break; } return { reverted }; } export async function resetMigrations(opts = {}) { const exec = getExec(opts.executor); const dropSql = `DROP TABLE IF EXISTS querykit_migrations`; if (exec.runSync) { exec.runSync(dropSql, []); } else { await exec.executeQuery(dropSql, []); } }