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
JavaScript
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, []);
}
}