'use strict'; const { cppdb } = require('../util'); const controllers = new WeakMap(); module.exports = function transaction(fn) { if (typeof fn !== 'function') throw new TypeError('Expected first argument to be a function'); const db = this[cppdb]; const controller = getController(db, this); const { apply } = Function.prototype; // Each version of the transaction function has these same properties const properties = { default: { value: wrapTransaction(apply, fn, db, controller.default) }, deferred: { value: wrapTransaction(apply, fn, db, controller.deferred) }, immediate: { value: wrapTransaction(apply, fn, db, controller.immediate) }, exclusive: { value: wrapTransaction(apply, fn, db, controller.exclusive) }, database: { value: this, enumerable: true }, }; Object.defineProperties(properties.default.value, properties); Object.defineProperties(properties.deferred.value, properties); Object.defineProperties(properties.immediate.value, properties); Object.defineProperties(properties.exclusive.value, properties); // Return the default version of the transaction function return properties.default.value; }; // Return the database's cached transaction controller, or create a new one const getController = (db, self) => { let controller = controllers.get(db); if (!controller) { const shared = { commit: db.prepare('COMMIT', self, false), rollback: db.prepare('ROLLBACK', self, false), savepoint: db.prepare('SAVEPOINT `\t_bs3.\t`', self, false), release: db.prepare('RELEASE `\t_bs3.\t`', self, false), rollbackTo: db.prepare('ROLLBACK TO `\t_bs3.\t`', self, false), }; controllers.set(db, controller = { default: Object.assign({ begin: db.prepare('BEGIN', self, false) }, shared), deferred: Object.assign({ begin: db.prepare('BEGIN DEFERRED', self, false) }, shared), immediate: Object.assign({ begin: db.prepare('BEGIN IMMEDIATE', self, false) }, shared), exclusive: Object.assign({ begin: db.prepare('BEGIN EXCLUSIVE', self, false) }, shared), }); } return controller; }; // Return a new transaction function by wrapping the given function const wrapTransaction = (apply, fn, db, { begin, commit, rollback, savepoint, release, rollbackTo }) => function sqliteTransaction() { let before, after, undo; if (db.inTransaction) { before = savepoint; after = release; undo = rollbackTo; } else { before = begin; after = commit; undo = rollback; } before.run(); try { const result = apply.call(fn, this, arguments); after.run(); return result; } catch (ex) { if (db.inTransaction) { undo.run(); if (undo !== rollback) after.run(); } throw ex; } };