transaction.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. 'use strict';
  2. const { cppdb } = require('../util');
  3. const controllers = new WeakMap();
  4. module.exports = function transaction(fn) {
  5. if (typeof fn !== 'function') throw new TypeError('Expected first argument to be a function');
  6. const db = this[cppdb];
  7. const controller = getController(db, this);
  8. const { apply } = Function.prototype;
  9. // Each version of the transaction function has these same properties
  10. const properties = {
  11. default: { value: wrapTransaction(apply, fn, db, controller.default) },
  12. deferred: { value: wrapTransaction(apply, fn, db, controller.deferred) },
  13. immediate: { value: wrapTransaction(apply, fn, db, controller.immediate) },
  14. exclusive: { value: wrapTransaction(apply, fn, db, controller.exclusive) },
  15. database: { value: this, enumerable: true },
  16. };
  17. Object.defineProperties(properties.default.value, properties);
  18. Object.defineProperties(properties.deferred.value, properties);
  19. Object.defineProperties(properties.immediate.value, properties);
  20. Object.defineProperties(properties.exclusive.value, properties);
  21. // Return the default version of the transaction function
  22. return properties.default.value;
  23. };
  24. // Return the database's cached transaction controller, or create a new one
  25. const getController = (db, self) => {
  26. let controller = controllers.get(db);
  27. if (!controller) {
  28. const shared = {
  29. commit: db.prepare('COMMIT', self, false),
  30. rollback: db.prepare('ROLLBACK', self, false),
  31. savepoint: db.prepare('SAVEPOINT `\t_bs3.\t`', self, false),
  32. release: db.prepare('RELEASE `\t_bs3.\t`', self, false),
  33. rollbackTo: db.prepare('ROLLBACK TO `\t_bs3.\t`', self, false),
  34. };
  35. controllers.set(db, controller = {
  36. default: Object.assign({ begin: db.prepare('BEGIN', self, false) }, shared),
  37. deferred: Object.assign({ begin: db.prepare('BEGIN DEFERRED', self, false) }, shared),
  38. immediate: Object.assign({ begin: db.prepare('BEGIN IMMEDIATE', self, false) }, shared),
  39. exclusive: Object.assign({ begin: db.prepare('BEGIN EXCLUSIVE', self, false) }, shared),
  40. });
  41. }
  42. return controller;
  43. };
  44. // Return a new transaction function by wrapping the given function
  45. const wrapTransaction = (apply, fn, db, { begin, commit, rollback, savepoint, release, rollbackTo }) => function sqliteTransaction() {
  46. let before, after, undo;
  47. if (db.inTransaction) {
  48. before = savepoint;
  49. after = release;
  50. undo = rollbackTo;
  51. } else {
  52. before = begin;
  53. after = commit;
  54. undo = rollback;
  55. }
  56. before.run();
  57. try {
  58. const result = apply.call(fn, this, arguments);
  59. after.run();
  60. return result;
  61. } catch (ex) {
  62. if (db.inTransaction) {
  63. undo.run();
  64. if (undo !== rollback) after.run();
  65. }
  66. throw ex;
  67. }
  68. };