@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
JavaScript
"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;