@onurege3467/zerohelper
Version:
ZeroHelper is a versatile high-performance utility library and database framework for Node.js, fully written in TypeScript.
212 lines (211 loc) • 8.72 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SQLiteDatabase = void 0;
const IDatabase_1 = require("./IDatabase");
const sqlite3_1 = __importDefault(require("sqlite3"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
class SQLiteDatabase extends IDatabase_1.IDatabase {
constructor(config) {
super();
this._queue = [];
this._isOpen = false;
if (!config || !config.path)
throw new Error('SQLite "path" gereklidir.');
const dir = path_1.default.dirname(config.path);
if (!fs_1.default.existsSync(dir))
fs_1.default.mkdirSync(dir, { recursive: true });
// SQLite open is technically async but the object is returned immediately.
// However, we simulate a queue to serialize operations strictly.
this.db = new sqlite3_1.default.Database(config.path, (err) => {
if (err) {
this._flushQueueWithError(err);
}
else {
this._isOpen = true;
this._flushQueue();
}
});
}
_flushQueue() {
while (this._queue.length > 0) {
const item = this._queue.shift();
if (item)
item.operation().then(item.resolve).catch(item.reject);
}
}
_flushQueueWithError(error) {
while (this._queue.length > 0) {
const item = this._queue.shift();
if (item)
item.reject(error);
}
}
async _execute(op, table, fn) {
const operation = async () => {
const start = Date.now();
const res = await fn();
this.recordMetric(op, table, Date.now() - start);
return res;
};
if (this._isOpen)
return operation();
return new Promise((resolve, reject) => {
this._queue.push({ operation, resolve, reject });
});
}
async query(sql, params = []) {
return new Promise((resolve, reject) => {
const s = sql.trim().toUpperCase();
if (s.startsWith('SELECT') || s.startsWith('PRAGMA')) {
this.db.all(sql, params, (err, rows) => err ? reject(err) : resolve(rows));
}
else {
this.db.run(sql, params, function (err) { err ? reject(err) : resolve({ changes: this.changes, lastID: this.lastID }); });
}
});
}
async ensureTable(table, data = {}) {
try {
await this.query(`SELECT 1 FROM "${table}" LIMIT 1`);
const info = await this.query(`PRAGMA table_info("${table}")`);
const names = info.map(c => c.name);
for (const key of Object.keys(data)) {
if (key !== '_id' && !names.includes(key)) {
await this.query(`ALTER TABLE "${table}" ADD COLUMN "${key}" TEXT`);
}
}
}
catch {
const defs = Object.keys(data).map(k => `"${k}" TEXT`);
await this.query(`CREATE TABLE "${table}" (_id INTEGER PRIMARY KEY AUTOINCREMENT ${defs.length ? ', ' + defs.join(',') : ''})`);
}
}
async insert(table, data) {
await this.runHooks('beforeInsert', table, data);
return this._execute('insert', table, async () => {
await this.ensureTable(table, data);
const keys = Object.keys(data);
const sql = `INSERT INTO "${table}" (${keys.map(k => `"${k}"`).join(',')}) VALUES (${keys.map(() => '?').join(',')})`;
const res = await this.query(sql, Object.values(data).map(v => typeof v === 'object' ? JSON.stringify(v) : v));
const finalData = { _id: res.lastID, ...data };
await this.runHooks('afterInsert', table, finalData);
return res.lastID;
});
}
async update(table, data, where) {
await this.runHooks('beforeUpdate', table, { data, where });
return this._execute('update', table, async () => {
await this.ensureTable(table, { ...data, ...where });
const set = Object.keys(data).map(k => `"${k}" = ?`).join(',');
const { whereClause, values: whereValues } = this._buildWhereClause(where);
const sql = `UPDATE "${table}" SET ${set} ${whereClause}`;
const res = await this.query(sql, [...Object.values(data).map(v => typeof v === 'object' ? JSON.stringify(v) : v), ...whereValues]);
return res.changes;
});
}
async delete(table, where) {
await this.runHooks('beforeDelete', table, where);
return this._execute('delete', table, async () => {
await this.ensureTable(table, where);
const { whereClause, values } = this._buildWhereClause(where);
const sql = `DELETE FROM "${table}" ${whereClause}`;
const res = await this.query(sql, values);
return res.changes;
});
}
async select(table, where = null) {
return this._execute('select', table, async () => {
await this.ensureTable(table, where || {});
const { whereClause, values } = this._buildWhereClause(where);
const rows = await this.query(`SELECT * FROM "${table}" ${whereClause}`, values);
return rows.map((r) => {
const nr = {};
for (const k in r) {
try {
nr[k] = JSON.parse(r[k]);
}
catch {
nr[k] = r[k];
}
}
return nr;
});
});
}
async selectOne(table, where = null) {
const res = await this.select(table, where);
return res[0] || null;
}
async set(table, data, where) {
const ex = await this.selectOne(table, where);
return ex ? this.update(table, data, where) : this.insert(table, { ...where, ...data });
}
async bulkInsert(table, dataArray) {
if (!dataArray.length)
return 0;
return this._execute('bulkInsert', table, async () => {
await this.ensureTable(table, dataArray[0]);
await this.query('BEGIN TRANSACTION');
try {
const keys = Object.keys(dataArray[0]);
const sql = `INSERT INTO "${table}" (${keys.map(k => `"${k}"`).join(',')}) VALUES (${keys.map(() => '?').join(',')})`;
for (const d of dataArray) {
await this.query(sql, Object.values(d).map(v => typeof v === 'object' ? JSON.stringify(v) : v));
}
await this.query('COMMIT');
return dataArray.length;
}
catch (error) {
await this.query('ROLLBACK');
throw error;
}
});
}
async increment(table, incs, where) {
return this._execute('increment', table, async () => {
await this.ensureTable(table, where);
const set = Object.keys(incs).map(f => `"${f}" = CAST("${f}" AS NUMERIC) + ?`);
const { whereClause, values } = this._buildWhereClause(where);
const sql = `UPDATE "${table}" SET ${set} ${whereClause}`;
const res = await this.query(sql, [...Object.values(incs), ...values]);
return res.changes;
});
}
async decrement(table, decs, where) {
const incs = {};
for (const k in decs)
incs[k] = -decs[k];
return this.increment(table, incs, where);
}
async close() {
return new Promise((resolve, reject) => {
this.db.close(err => {
this._isOpen = false;
err ? reject(err) : resolve();
});
});
}
_serializeValue(v) {
if (v instanceof Date)
return v.toISOString().slice(0, 19).replace('T', ' ');
return (typeof v === 'object' && v !== null) ? JSON.stringify(v) : v;
}
_buildWhereClause(where) {
if (!where)
return { whereClause: '', values: [] };
const safeWhere = where;
const keys = Object.keys(safeWhere);
if (!keys.length)
return { whereClause: '', values: [] };
return {
whereClause: 'WHERE ' + keys.map(k => `"${k}" = ?`).join(' AND '),
values: keys.map(k => this._serializeValue(safeWhere[k]))
};
}
}
exports.SQLiteDatabase = SQLiteDatabase;
exports.default = SQLiteDatabase;