UNPKG

iagate-querykit

Version:

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

130 lines (129 loc) 5.29 kB
import { QueryKitConfig } from './config'; import { scheduler } from './scheduler'; import { table } from './table'; function namesByDialect(dialect) { switch (dialect) { case 'sqlite': return [{ sql: "SELECT name FROM sqlite_master WHERE type='view'", map: (r) => r.name }]; case 'mysql': return [{ sql: "SHOW FULL TABLES WHERE TABLE_TYPE = 'VIEW'", map: (r) => Object.values(r)[0] }]; case 'postgres': return [{ sql: "SELECT table_name FROM information_schema.views WHERE table_schema = current_schema()", map: (r) => r.table_name }]; case 'mssql': return [{ sql: "SELECT name FROM sys.views", map: (r) => r.name }]; case 'oracle': return [{ sql: "SELECT VIEW_NAME AS name FROM USER_VIEWS", map: (r) => r.name }]; default: return [ { sql: "SELECT name FROM sqlite_master WHERE type='view'", map: (r) => r.name }, { sql: "SHOW FULL TABLES WHERE TABLE_TYPE = 'VIEW'", map: (r) => Object.values(r)[0] }, { sql: "SELECT table_name FROM information_schema.views WHERE table_schema = current_schema()", map: (r) => r.table_name }, { sql: "SELECT name FROM sys.views", map: (r) => r.name }, { sql: "SELECT VIEW_NAME AS name FROM USER_VIEWS", map: (r) => r.name }, ]; } } function escapeSqlLiteral(value) { if (value === null || value === undefined) return 'NULL'; if (typeof value === 'number' && isFinite(value)) return String(value); if (typeof value === 'bigint') return String(value); if (typeof value === 'boolean') return value ? '1' : '0'; if (value instanceof Date) return `'${value.toISOString().replace(/'/g, "''")}'`; if (Buffer && Buffer.isBuffer && Buffer.isBuffer(value)) return `X'${value.toString('hex')}'`; if (typeof value === 'object') return `'${JSON.stringify(value).replace(/'/g, "''")}'`; return `'${String(value).replace(/'/g, "''")}'`; } function inlineBindings(sql, bindings) { if (!bindings || bindings.length === 0) return sql; let i = 0; return sql.replace(/\?/g, () => { if (i >= bindings.length) return '?'; const lit = escapeSqlLiteral(bindings[i++]); return lit; }); } export class ViewManager { async createOrReplaceView(viewName, query) { const { sql, bindings } = query.toSql(); await this.dropView(viewName); const inlined = inlineBindings(sql, bindings); const createViewSql = `CREATE VIEW ${viewName} AS ${inlined}`; const exec = QueryKitConfig.defaultExecutor; if (!exec) throw new Error('No executor configured for QueryKit'); if (exec.runSync) exec.runSync(createViewSql, []); else await exec.executeQuery(createViewSql, []); } scheduleViewRefresh(viewName, query, intervalMs) { const task = () => this.createOrReplaceView(viewName, query); scheduler.schedule(`refresh-view-${viewName}`, task, intervalMs); } unscheduleViewRefresh(viewName) { scheduler.unschedule(`refresh-view-${viewName}`); } async dropView(viewName) { const dropViewSql = `DROP VIEW IF EXISTS ${viewName}`; const exec = QueryKitConfig.defaultExecutor; if (!exec) throw new Error('No executor configured for QueryKit'); if (exec.runSync) exec.runSync(dropViewSql, []); else await exec.executeQuery(dropViewSql, []); } listViews() { const exec = QueryKitConfig.defaultExecutor; if (!exec) throw new Error('No executor configured for QueryKit'); if (!exec.executeQuerySync) return []; const candidates = namesByDialect(exec.dialect || QueryKitConfig.defaultDialect); for (const c of candidates) { try { const res = exec.executeQuerySync(c.sql, []); const rows = res?.data || []; const names = rows.map(c.map).filter(Boolean); if (names.length || rows.length >= 0) return names; } catch { } } return []; } viewExists(viewName) { const names = this.listViews(); return names.includes(viewName); } async listViewsAsync() { const exec = QueryKitConfig.defaultExecutor; if (!exec) throw new Error('No executor configured for QueryKit'); if (exec.executeQuerySync) return this.listViews(); const candidates = namesByDialect(exec.dialect || QueryKitConfig.defaultDialect); for (const c of candidates) { try { const res = await exec.executeQuery(c.sql, []); const rows = res?.data || []; const names = rows.map(c.map).filter(Boolean); if (names.length || rows.length >= 0) return names; } catch { } } return []; } async viewExistsAsync(viewName) { const names = await this.listViewsAsync(); return names.includes(viewName); } view(viewName) { return table(viewName); } }