UNPKG

@leapfrogtechnology/db-model

Version:

Low-footprint database abstraction layer and model built on top of Knex.

480 lines 16.8 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); var Knex = require("knex"); var debug = require("debug"); var constants_1 = require("./constants"); var object = require("./utils/object"); var log = debug(constants_1.NS_DB); /** * Check if the provided object is a knex connection instance. * * @param {any} obj * @returns {boolean} */ function isKnexInstance(obj) { return !!(obj.prototype && obj.prototype.constructor && obj.prototype.constructor.name === 'knex'); } exports.isKnexInstance = isKnexInstance; /** * Creates a knex database connection instance from * the provided database configuration. * * @param {Knex.Config} dbConfig * @returns {Knex} */ function createInstance(dbConfig) { return Knex(dbConfig); } exports.createInstance = createInstance; /** * Check database connection from provided knex params. * * @param {Knex.Config | Knex | Knex.Transaction} connection * @returns {Promise<boolean>} */ function isValidConnection(connection) { return __awaiter(this, void 0, void 0, function () { var conn, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: conn = isKnexInstance(connection) ? connection : createInstance(connection); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, conn.raw('SELECT 1')]; case 2: _a.sent(); return [2 /*return*/, true]; case 3: err_1 = _a.sent(); log('Cannot connect to database', err_1); return [2 /*return*/, false]; case 4: return [2 /*return*/]; } }); }); } exports.isValidConnection = isValidConnection; /** * Returns a query builder instance depending on the provided transaction. * * @param {Knex} connection * @param {Knex.Transaction} [trx] * @returns {(Knex.Transaction | Knex)} */ function queryBuilder(connection, trx) { return trx || connection; } exports.queryBuilder = queryBuilder; /** * Push 'updated_at' key to params if the column exists in the table being updated. * * @param {Knex} connection * @param {string} table * @param {any} [params={}] * @returns {Promise<any>} */ function withTimestamp(connection, table, params) { if (params === void 0) { params = {}; } return __awaiter(this, void 0, void 0, function () { var exists; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, connection.schema.hasColumn(table, 'updated_at')]; case 1: exists = _a.sent(); if (!exists || (exists && params.updatedAt)) { return [2 /*return*/, params]; } return [2 /*return*/, __assign(__assign({}, params), { updated_at: connection.fn.now() })]; } }); }); } /** * Finds a record based on the params. * * @param {Knex} connection * @param {string} table * @param {object} [params={}] * @param {Function} callback * @param {Knex.Transaction} [trx] * @returns {Knex.QueryBuilder} */ function find(connection, table, params, callback, trx) { if (params === void 0) { params = {}; } var qb = queryBuilder(connection, trx).select('*').from(table).where(params); if (callback) callback(qb); return qb; } exports.find = find; /** * Finds a single record based on the params. * * @param {Knex} connection * @param {string} table * @param {object} [params={}] * @param {Function} callback * @param {Knex.Transaction} [trx] * @returns {(Promise<T | null>)} */ function findFirst(connection, table, params, callback, trx) { if (params === void 0) { params = {}; } var qb = queryBuilder(connection, trx) .select('*') .from(table) .where(params) .limit(1) .then(function (_a) { var result = _a[0]; return result ? result : null; }); if (callback) callback(qb); return qb; } exports.findFirst = findFirst; /** * Insert all records sent in data object. * * @param {Knex} connection * @param {string} table * @param {(object | object[])} data * @param {Knex.Transaction} [trx] * @returns {Knex.QueryBuilder} */ function insert(connection, table, data, trx) { return queryBuilder(connection, trx).insert(data).into(table).returning('*'); } exports.insert = insert; /** * Update records by where condition. * * @param {object} where * @param {object} params * @param {Transaction} transaction * @returns {Promise<T[]>} */ function update(connection, table, where, params, trx) { return __awaiter(this, void 0, void 0, function () { var updateParams; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, withTimestamp(connection, table, params)]; case 1: updateParams = _a.sent(); return [2 /*return*/, queryBuilder(connection, trx).update(updateParams).table(table).where(where).returning('*')]; } }); }); } exports.update = update; /** * Delete row in table. * * @param {Knex} connection * @param {string} table * @param {object} params * @param {Transaction} trx * @returns {Promise<T[]>} */ function remove(connection, table, params, trx) { return queryBuilder(connection, trx).where(params).from(table).del().returning('*'); } exports.remove = remove; /** * Execute SQL raw query and return results. * * @param {Knex} connection * @param {string} sql * @param {RawBindingParams | ValueMap} [params] * @param {Knex.Transaction} [trx] * @returns {Promise<T[]>} */ function query(connection, sql, params, trx) { return params ? queryBuilder(connection, trx).raw(sql, params) : queryBuilder(connection, trx).raw(sql); } exports.query = query; /** * Batch inserts the given data. * * @param {Knex} connection * @param {string} table * @param {object[]} data * @param {number} chunkSize * @param {Knex.Transaction} [trx] * @returns {Promise<T[]>} */ function batchInsert(connection, table, data, chunkSize, trx) { return queryBuilder(connection, trx).batchInsert(table, data, chunkSize).returning('*'); } exports.batchInsert = batchInsert; /** * Execute SQL raw query returning a scalar value. * * Example: * const count:number = await db.getValue<number>( * connection, * 'SELECT count(id) FROM jobs WHERE is_active = 1' * ); * * @param {Knex} connection * @param {string} sql * @param {any} params * @param {Knex.Transaction} trx * @returns {Promise<T | null>} */ function getValue(connection, sql, params, trx) { return __awaiter(this, void 0, void 0, function () { var qb, data, value; return __generator(this, function (_a) { switch (_a.label) { case 0: qb = queryBuilder(connection, trx); return [4 /*yield*/, qb.raw(sql, params)]; case 1: data = (_a.sent())[0]; if (!data) { return [2 /*return*/, null]; } value = Object.values(data)[0]; return [2 /*return*/, value]; } }); }); } exports.getValue = getValue; /** * Execute SQL raw query returning a boolean result. * * Example: * const result:boolean = await db.check( * connection, * 'SELECT is_active FROM jobs WHERE id = :jobId' * ); * * @param {Knex} connection * @param {string} sql * @param {any} params * @param {Knex.Transaction} trx * @returns {Promise<boolean>} */ function check(connection, sql, params, trx) { return __awaiter(this, void 0, void 0, function () { var value; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getValue(connection, sql, params, trx)]; case 1: value = _a.sent(); return [2 /*return*/, !!value]; } }); }); } exports.check = check; /** * Execute SQL raw query returning a JSON encoded result * and produce the parsed object. * * Example: * * const result:JobDetail = await db.getJson<JobDetail>( * connection, * 'SELECT * FROM jobs WHERE id = :jobId FOR JSON AUTO' * ); * * @param {Knex} connection * @param {string} sql * @param {any} params * @param {Knex.Transaction} trx * @returns {Promise<T | null>} */ function getJson(connection, sql, params, trx) { return __awaiter(this, void 0, void 0, function () { var value; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, getValue(connection, sql, params, trx)]; case 1: value = _a.sent(); if (value) { return [2 /*return*/, object.fromJson(value)]; } return [2 /*return*/, null]; } }); }); } exports.getJson = getJson; /** * Invoke a scalar-valued function and return results. * * Example usage: * * const username = await db.invoke<string>(con, 'dbo.can_user_access_object', { userId: 10, objectId: 15 }); * * // => Runs SQL: SELECT dbo.can_user_access_object(:userId, :objectId) * // => Binds params: { userId: 10, objectId: 15 } * * @param {(Knex | Knex.Transaction)} connection * @param {string} objectName * @param {(RawBindingParams | ValueMap)} [params] * @returns {(Promise<T | null>)} */ function invoke(connection, objectName, params) { return __awaiter(this, void 0, void 0, function () { var _a, procName, expr, sql, result; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = getProcParamsExpr(objectName, params), procName = _a.procName, expr = _a.expr; log("Invoke function '" + procName + "' with: [" + expr + "]"); sql = "SELECT " + procName + "(" + expr + ")"; return [4 /*yield*/, query(connection, sql, params)]; case 1: result = (_b.sent())[0]; return [2 /*return*/, result]; } }); }); } exports.invoke = invoke; /** * Execute a procedure and return the results returned (if any). * * Example usage: * * await db.exec<string>(trx, 'dbo.update_top_recommendations', { userId: 10, type }); * * // => Runs SQL: EXEC dbo.update_top_recommendations :userId, :type * // => Binds params: { userId: 10, type } * * @param {(Knex | Knex.Transaction)} connection * @param {string} objectName * @param {(RawBindingParams | ValueMap)} [params] * @returns {Promise<T[]>} */ function exec(connection, objectName, params) { return __awaiter(this, void 0, void 0, function () { var _a, procName, expr, sql; return __generator(this, function (_b) { _a = getProcParamsExpr(objectName, params), procName = _a.procName, expr = _a.expr; log("Execute procedure '" + procName + "' with: [" + expr + "]"); sql = "EXEC " + procName + " " + expr; return [2 /*return*/, query(connection, sql, params)]; }); }); } exports.exec = exec; /** * Extract parameter binding expression for procedure or a function. * * @param {string} objectName * @param {(RawBindingParams | ValueMap)} [params] * @returns {{ procName: string, expr: string }} */ function getProcParamsExpr(objectName, params) { var procName = objectName && objectName.trim && objectName.trim(); if (!procName) { throw new Error("Invalid function or procedure name '" + procName + "'."); } var expr = params ? Object.keys(params) .map(function (key) { return ':' + key; }) .join(', ') : ''; return { procName: procName, expr: expr }; } exports.getProcParamsExpr = getProcParamsExpr; /** * Run updates in batch via given query. The query supplied should have an UPDATE TOP * statement, as it is the preferred method of updating given N rows. * * Example usage: * * await db.batchUpdateViaQuery(trx, UPDATE_QUERY, { dataSize: 1000, batchSize: 100 }); * * // => Runs the query and binds params: { batchSize: 100 } * * @param {Knex.Transaction} trx * @param {string} updateQuery * @param {BatchUpdateOptions} options */ function batchUpdateViaQuery(trx, updateQuery, options) { return __awaiter(this, void 0, void 0, function () { var dataSize, batchSize, writeLog, totalBatch, currentBatch; return __generator(this, function (_a) { switch (_a.label) { case 0: dataSize = options.dataSize, batchSize = options.batchSize; writeLog = options.log || log; totalBatch = Math.ceil(dataSize / batchSize); currentBatch = 1; _a.label = 1; case 1: if (!(currentBatch <= totalBatch)) return [3 /*break*/, 4]; writeLog("Processing batch " + currentBatch + " of " + totalBatch); return [4 /*yield*/, trx.raw(updateQuery, { batchSize: batchSize })]; case 2: _a.sent(); _a.label = 3; case 3: currentBatch++; return [3 /*break*/, 1]; case 4: return [2 /*return*/]; } }); }); } exports.batchUpdateViaQuery = batchUpdateViaQuery; //# sourceMappingURL=db.js.map