aa-sqlite3
Version:
async await with sqlite3
153 lines (129 loc) • 4.18 kB
JavaScript
// @ts-check
;
module.exports = asyncSqlite;
const alreadyProcessed = new WeakMap();
const statementParams = new WeakMap();
const boundParams = new WeakMap();
/**
* options: {
* trace: (sql, method, ctorName) => {}
* profile: (sql, msec, method, ctorName) => {}
* usePromise: boolean
* }
*/
const defaultOptions = {
trace: null,
profile: null,
usePromise: false,
asyncMethods: [ // awaitable methods
'run', 'exec', 'get', 'all', 'each',
'close', 'map', 'loadExtension',
'bind', 'reset', 'finalize',
],
traceMethods: [ // traceable methods for trace and profile
'run', 'exec', 'get', 'all', 'each', 'map',
],
getActualOptions: null,
};
/**
* asyncSqlite - make thenable SQLite object. Database or Statement.
* @param {*} object
* @param {*} options
* @returns obj
*/
function asyncSqlite(object, options = {}) {
if (object == null || typeof object !== 'object')
throw new TypeError('Unexpected argument. Database or Statement object expected');
if (alreadyProcessed.get(object)) return object;
alreadyProcessed.set(object, true);
const ctorName = object.constructor.name;
const opts = Object.assign({}, defaultOptions, options);
const thenable = opts.usePromise ? newPromise : makeThenable;
const traceOrProfile = opts.trace || opts.profile ? true : false;
const traceMethods = traceOrProfile ? opts.traceMethods
.reduce((prev, curr) => (prev[curr] = true, prev), {}) : {};
if (typeof opts.getActualOptions === 'function')
opts.getActualOptions(opts);
// make thenable methods for Database and Statement
for (const method of opts.asyncMethods) {
const func = object[method];
if (typeof func === 'function' && method !== 'prepare') {
object[method] = traceOrProfile && traceMethods[method] ?
(...args) => thenable((res, rej) => {
try {
let resolve = res;
const sp = statementParams.get(object);
if (method === 'bind') boundParams.set(object, stringify(sp ? [sp, ...args] : args));
const bp = boundParams.get(object);
const sql = stringify(bp ? [bp, ...args] : sp ? [sp, ...args] : args);
// trace: before call
if (typeof opts.trace === 'function')
opts.trace(sql, method, ctorName);
// profile: after call
if (typeof opts.profile === 'function') {
let hrtime = process.hrtime();
resolve = val => {
// @ts-ignore
hrtime = process.hrtime(hrtime);
opts.profile(sql, hrtime[0] * 1e3 + hrtime[1] / 1e6, method, ctorName);
res(val);
};
}
args.push((err, val) => err ? rej(err) : resolve(val));
func.apply(object, args);
}
catch (err) { rej(err); }
}) :
(...args) => thenable((res, rej) => {
try {
args.push((err, val) => err ? rej(err) : res(val));
func.apply(object, args);
}
catch (err) { rej(err); }
});
}
}
// prepare method of Database for Statement
const prepare = object.prepare;
if (typeof prepare === 'function') {
object.prepare = (...args) => thenable((res, rej) => {
try {
args.push(err => err ? rej(err) : res(stmt));
const stmt = asyncSqlite(prepare.apply(object, args), opts);
if (traceOrProfile) statementParams.set(stmt, stringify(args));
}
catch (err) { rej(err); }
});
}
return object;
}
/**
* makeThenable: make thenable object. object has then method.
* @param {function} func
* @returns promise-like-object
*/
function makeThenable(func) {
return { then: func };
}
/**
* newPromise: make promise object.
* @param {any} func
* @returns promise-object
*/
function newPromise(func) {
return new Promise(func);
}
/**
* stringify: stringfigy arguments.
* @param {any[]} args
* @returns string
*/
function stringify(args) {
return args.reduce((prev, curr, i) => {
if (typeof curr === 'function') return prev;
const delim = i === 0 ? '' : '; ';
if (i === 0 && typeof curr === 'string') prev += delim + curr;
else prev += delim + JSON.stringify(curr);
return prev;
}, '');
}