@leapfrogtechnology/db-model
Version:
Low-footprint database abstraction layer and model built on top of Knex.
480 lines • 16.8 kB
JavaScript
"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