76 lines
2.6 KiB
JavaScript
76 lines
2.6 KiB
JavaScript
|
'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;
|
||
|
}
|
||
|
};
|