UNPKG

@trap_stevo/liveql

Version:

Supercharge your database workflow with a visually clean, ultra-intuitive SQL layer. Chain elegant queries, trigger instant real-time events, and manage schemas effortlessly — all without ORM overhead while blending raw SQL power with modern developer erg

180 lines (176 loc) 7.31 kB
"use strict"; function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); } function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); } function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); } function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); } function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; } function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); } const RealTimeDispatcher = require("./HUDComponents/RealTimeDispatcher"); const SchemaInspector = require("./HUDComponents/SchemaInspector"); const MigrationEngine = require("./HUDComponents/MigrationEngine"); const SchemaBuilder = require("./HUDComponents/SchemaBuilder"); const QueryBuilder = require("./HUDComponents/QueryBuilder"); function usingPostgres(client) { return client?.constructor?.name === "Client" || client?.constructor?.name === "Pool"; } function usingMySQL(client) { return typeof client.execute === "function" || typeof client.query === "function"; } function usingSQLite(client) { return client?.constructor?.name?.toLowerCase().includes("sqlite"); } var _client = /*#__PURE__*/new WeakMap(); var _realtime = /*#__PURE__*/new WeakMap(); var _inspector = /*#__PURE__*/new WeakMap(); var _migrator = /*#__PURE__*/new WeakMap(); var _ddlEnabled = /*#__PURE__*/new WeakMap(); var _LiveQL_brand = /*#__PURE__*/new WeakSet(); class LiveQL { constructor(client) { _classPrivateMethodInitSpec(this, _LiveQL_brand); _classPrivateFieldInitSpec(this, _client, void 0); _classPrivateFieldInitSpec(this, _realtime, void 0); _classPrivateFieldInitSpec(this, _inspector, void 0); _classPrivateFieldInitSpec(this, _migrator, void 0); _classPrivateFieldInitSpec(this, _ddlEnabled, false); _classPrivateFieldSet(_client, this, client); _classPrivateFieldSet(_realtime, this, new RealTimeDispatcher(client)); _classPrivateFieldSet(_inspector, this, new SchemaInspector(client)); _classPrivateFieldSet(_migrator, this, new MigrationEngine(client)); } query(sql, params = []) { return _classPrivateFieldGet(_client, this).query(sql, params); } table(name) { return new QueryBuilder(_classPrivateFieldGet(_client, this), name, _assertClassBrand(_LiveQL_brand, this, _detectDialect).call(this)); } schema(name) { return new SchemaBuilder(_classPrivateFieldGet(_client, this), name); } get realtime() { return _classPrivateFieldGet(_realtime, this); } get inspector() { return _classPrivateFieldGet(_inspector, this); } get migrator() { return { runFromFolder: (dir, direction = "up") => _classPrivateFieldGet(_migrator, this).runFromFolder(dir, direction), run: (...args) => _classPrivateFieldGet(_migrator, this).run(...args) }; } enableRealtime(table, eventTypes = ["insert", "update", "delete"], options = {}) { const pollInterval = options.pollInterval || 2000; const dialect = _assertClassBrand(_LiveQL_brand, this, _detectDialect).call(this); for (const eventType of eventTypes) { const channel = `${table}__${eventType}`; this.realtime.listen(channel); if (dialect === "postgres") { _assertClassBrand(_LiveQL_brand, this, _enablePostgresTrigger).call(this, table, eventType, channel); } else if (dialect === "mysql" || dialect === "sqlite") { this.realtime.startPolling(table, eventType, pollInterval, rows => { this.realtime.emit(table, eventType, rows); }, { updatedAtColumn: options.updatedAtColumn, idColumn: options.idColumn }); } } } disableRealtime(table, eventTypes = ["insert", "update", "delete"]) { for (const eventType of eventTypes) { const channel = `${table}__${eventType}`; this.realtime.unlisten(channel); } } async enableDDLEvents() { if (_assertClassBrand(_LiveQL_brand, this, _detectDialect).call(this) !== "postgres") { console.warn("[LiveQL] ~ DDL tracking only supported on PostgreSQL."); return; } if (_classPrivateFieldGet(_ddlEnabled, this)) { return; } _classPrivateFieldSet(_ddlEnabled, this, true); const fnName = "notify_ddl"; const channel = "ddl__event"; this.realtime.listen(channel); await _classPrivateFieldGet(_client, this).query(` CREATE OR REPLACE FUNCTION ${fnName}() RETURNS event_trigger AS $$ DECLARE obj JSON; BEGIN SELECT json_build_object( 'tag', TG_TAG, 'schema', TG_TABLE_SCHEMA, 'table', TG_TABLE_NAME, 'type', TG_OBJECTTYPE ) INTO obj; PERFORM pg_notify('${channel}', obj::TEXT); END; $$ LANGUAGE plpgsql; DROP EVENT TRIGGER IF EXISTS trg_ddl_events; CREATE EVENT TRIGGER trg_ddl_events ON ddl_command_end EXECUTE FUNCTION ${fnName}(); `); } async disableDDLEvents() { if (!_classPrivateFieldGet(_ddlEnabled, this)) { return; } _classPrivateFieldSet(_ddlEnabled, this, false); const channel = "ddl__event"; await _classPrivateFieldGet(_client, this).query(`DROP EVENT TRIGGER IF EXISTS trg_ddl_events;`); await _classPrivateFieldGet(_client, this).query(`DROP FUNCTION IF EXISTS notify_ddl();`); this.realtime.unlisten(channel); } async setupRealtime(options = {}) { const { tables = [], events = ["insert", "update", "delete"], ddl = false, pollOptions = {} } = options; for (const table of tables) { this.enableRealtime(table, events, pollOptions); } if (ddl === true) { await this.enableDDLEvents(); } } } function _detectDialect() { if (usingPostgres(_classPrivateFieldGet(_client, this))) { return "postgres"; } if (usingSQLite(_classPrivateFieldGet(_client, this))) { return "sqlite"; } if (usingMySQL(_classPrivateFieldGet(_client, this))) { return "mysql"; } return "unknown"; } async function _enablePostgresTrigger(table, eventType, notifyChannel) { const fnName = `notify_${table}_${eventType}`; const trgName = `trg_${table}_${eventType}`; await _classPrivateFieldGet(_client, this).query(` CREATE OR REPLACE FUNCTION ${fnName}() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('${notifyChannel}', row_to_json(NEW)::TEXT); RETURN NEW; END; $$ LANGUAGE plpgsql; `); await _classPrivateFieldGet(_client, this).query(`DROP TRIGGER IF EXISTS ${trgName} ON ${table};`); await _classPrivateFieldGet(_client, this).query(` CREATE TRIGGER ${trgName} AFTER ${eventType.toUpperCase()} ON ${table} FOR EACH ROW EXECUTE FUNCTION ${fnName}(); `); } ; module.exports = LiveQL;