{ "version": 3, "sources": ["../src/transaction.js"], "sourcesContent": ["'use strict';\n\n/**\n * The transaction object is used to identify a running transaction.\n * It is created by calling `Sequelize.transaction()`.\n * To run a query under a transaction, you should pass the transaction in the options object.\n *\n * @class Transaction\n * @see {@link Sequelize.transaction}\n */\nclass Transaction {\n /**\n * Creates a new transaction instance\n *\n * @param {Sequelize} sequelize A configured sequelize Instance\n * @param {object} options An object with options\n * @param {string} [options.type] Sets the type of the transaction. Sqlite only\n * @param {string} [options.isolationLevel] Sets the isolation level of the transaction.\n * @param {string} [options.deferrable] Sets the constraints to be deferred or immediately checked. PostgreSQL only\n */\n constructor(sequelize, options) {\n this.sequelize = sequelize;\n this.savepoints = [];\n this._afterCommitHooks = [];\n\n // get dialect specific transaction options\n const generateTransactionId = this.sequelize.dialect.queryGenerator.generateTransactionId;\n\n this.options = {\n type: sequelize.options.transactionType,\n isolationLevel: sequelize.options.isolationLevel,\n readOnly: false,\n ...options\n };\n\n this.parent = this.options.transaction;\n\n if (this.parent) {\n this.id = this.parent.id;\n this.parent.savepoints.push(this);\n this.name = `${this.id}-sp-${this.parent.savepoints.length}`;\n } else {\n this.id = this.name = generateTransactionId();\n }\n\n delete this.options.transaction;\n }\n\n /**\n * Commit the transaction\n *\n * @returns {Promise}\n */\n async commit() {\n if (this.finished) {\n throw new Error(`Transaction cannot be committed because it has been finished with state: ${this.finished}`);\n }\n\n try {\n await this.sequelize.getQueryInterface().commitTransaction(this, this.options);\n this.cleanup();\n } catch (e) {\n console.warn(`Committing transaction ${this.id} failed with error ${JSON.stringify(e.message)}. We are killing its connection as it is now in an undetermined state.`);\n await this.forceCleanup();\n\n throw e;\n } finally {\n this.finished = 'commit';\n for (const hook of this._afterCommitHooks) {\n await hook.apply(this, [this]);\n }\n }\n }\n\n /**\n * Rollback (abort) the transaction\n *\n * @returns {Promise}\n */\n async rollback() {\n if (this.finished) {\n throw new Error(`Transaction cannot be rolled back because it has been finished with state: ${this.finished}`);\n }\n\n if (!this.connection) {\n throw new Error('Transaction cannot be rolled back because it never started');\n }\n\n try {\n await this\n .sequelize\n .getQueryInterface()\n .rollbackTransaction(this, this.options);\n\n this.cleanup();\n } catch (e) {\n console.warn(`Rolling back transaction ${this.id} failed with error ${JSON.stringify(e.message)}. We are killing its connection as it is now in an undetermined state.`);\n await this.forceCleanup();\n\n throw e;\n }\n }\n\n /**\n * Called to acquire a connection to use and set the correct options on the connection.\n * We should ensure all of the environment that's set up is cleaned up in `cleanup()` below.\n *\n * @param {boolean} useCLS Defaults to true: Use CLS (Continuation Local Storage) with Sequelize. With CLS, all queries within the transaction callback will automatically receive the transaction object.\n * @returns {Promise}\n */\n async prepareEnvironment(useCLS = true) {\n let connectionPromise;\n\n if (this.parent) {\n connectionPromise = Promise.resolve(this.parent.connection);\n } else {\n const acquireOptions = { uuid: this.id };\n if (this.options.readOnly) {\n acquireOptions.type = 'SELECT';\n }\n connectionPromise = this.sequelize.connectionManager.getConnection(acquireOptions);\n }\n\n let result;\n const connection = await connectionPromise;\n this.connection = connection;\n this.connection.uuid = this.id;\n\n try {\n await this.begin();\n result = await this.setDeferrable();\n } catch (setupErr) {\n try {\n result = await this.rollback();\n } finally {\n throw setupErr; // eslint-disable-line no-unsafe-finally\n }\n }\n\n // TODO (@ephys) [>=7.0.0]: move this inside of sequelize.transaction, remove parameter.\n if (useCLS && this.sequelize.constructor._cls) {\n this.sequelize.constructor._cls.set('transaction', this);\n }\n\n return result;\n }\n\n async setDeferrable() {\n if (this.options.deferrable) {\n return await this\n .sequelize\n .getQueryInterface()\n .deferConstraints(this, this.options);\n }\n }\n\n async begin() {\n const queryInterface = this.sequelize.getQueryInterface();\n\n if ( this.sequelize.dialect.supports.settingIsolationLevelDuringTransaction ) {\n await queryInterface.startTransaction(this, this.options);\n return queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options);\n }\n\n await queryInterface.setIsolationLevel(this, this.options.isolationLevel, this.options);\n\n return queryInterface.startTransaction(this, this.options);\n }\n\n cleanup() {\n // Don't release the connection if there's a parent transaction or\n // if we've already cleaned up\n if (this.parent || this.connection.uuid === undefined) {\n return;\n }\n\n this._clearCls();\n this.sequelize.connectionManager.releaseConnection(this.connection);\n this.connection.uuid = undefined;\n }\n\n /**\n * Kills the connection this transaction uses.\n * Used as a last resort, for instance because COMMIT or ROLLBACK resulted in an error\n * and the transaction is left in a broken state,\n * and releasing the connection to the pool would be dangerous.\n */\n async forceCleanup() {\n // Don't release the connection if there's a parent transaction or\n // if we've already cleaned up\n if (this.parent || this.connection.uuid === undefined) {\n return;\n }\n\n this._clearCls();\n await this.sequelize.connectionManager.destroyConnection(this.connection);\n this.connection.uuid = undefined;\n }\n\n _clearCls() {\n const cls = this.sequelize.constructor._cls;\n\n if (cls) {\n if (cls.get('transaction') === this) {\n cls.set('transaction', null);\n }\n }\n }\n\n /**\n * A hook that is run after a transaction is committed\n *\n * @param {Function} fn A callback function that is called with the committed transaction\n * @name afterCommit\n * @memberof Sequelize.Transaction\n */\n afterCommit(fn) {\n if (!fn || typeof fn !== 'function') {\n throw new Error('\"fn\" must be a function');\n }\n this._afterCommitHooks.push(fn);\n }\n\n /**\n * Types can be set per-transaction by passing `options.type` to `sequelize.transaction`.\n * Default to `DEFERRED` but you can override the default type by passing `options.transactionType` in `new Sequelize`.\n * Sqlite only.\n *\n * Pass in the desired level as the first argument:\n *\n * @example\n * try {\n * await sequelize.transaction({ type: Sequelize.Transaction.TYPES.EXCLUSIVE }, transaction => {\n * // your transactions\n * });\n * // transaction has been committed. Do something after the commit if required.\n * } catch(err) {\n * // do something with the err.\n * }\n *\n * @property DEFERRED\n * @property IMMEDIATE\n * @property EXCLUSIVE\n */\n static get TYPES() {\n return {\n DEFERRED: 'DEFERRED',\n IMMEDIATE: 'IMMEDIATE',\n EXCLUSIVE: 'EXCLUSIVE'\n };\n }\n\n /**\n * Isolation levels can be set per-transaction by passing `options.isolationLevel` to `sequelize.transaction`.\n * Sequelize uses the default isolation level of the database, you can override this by passing `options.isolationLevel` in Sequelize constructor options.\n *\n * Pass in the desired level as the first argument:\n *\n * @example\n * try {\n * const result = await sequelize.transaction({isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE}, transaction => {\n * // your transactions\n * });\n * // transaction has been committed. Do something after the commit if required.\n * } catch(err) {\n * // do something with the err.\n * }\n *\n * @property READ_UNCOMMITTED\n * @property READ_COMMITTED\n * @property REPEATABLE_READ\n * @property SERIALIZABLE\n */\n static get ISOLATION_LEVELS() {\n return {\n READ_UNCOMMITTED: 'READ UNCOMMITTED',\n READ_COMMITTED: 'READ COMMITTED',\n REPEATABLE_READ: 'REPEATABLE READ',\n SERIALIZABLE: 'SERIALIZABLE'\n };\n }\n\n\n /**\n * Possible options for row locking. Used in conjunction with `find` calls:\n *\n * @example\n * // t1 is a transaction\n * Model.findAll({\n * where: ...,\n * transaction: t1,\n * lock: t1.LOCK...\n * });\n *\n * @example