'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /** * Licensed Materials - Property of IBM * IBM Business Analytics (C) Copyright IBM Corp. 2019, 2020 * US Government Users Restricted Rights - Use, duplication or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. */ /** * @class TransactionAPI * @hideconstructor * @classdesc API class that is used to control transactional operations across API calls */ define(['underscore', '../../../../../lib/@waca/dashboard-common/dist/core/APIFactory', '../TransactionAPI'], function (_, APIFactory, TransactionAPI) { var Transaction = function () { function Transaction() { _classCallCheck(this, Transaction); this._transactions = new Map(); this._handlers = new Map(); } Transaction.prototype.getAPI = function getAPI() { return APIFactory.createAPI(this, [TransactionAPI]); }; Transaction.prototype.startTransaction = function startTransaction(parentToken) { if (!(parentToken && parentToken.transactionId)) { // create a new transaction entry var entry = this._createTransactionEntry(_.uniqueId('__transaction__')); if (parentToken && parentToken.source) { entry.token.source = parentToken.source; entry.token.eventGenerated = parentToken.eventGenerated; } return entry.token; } else { var _entry = this._get(parentToken.transactionId); if (_entry) { // add a sub-transaction to an existing transaction entry var subTransactionIdx = this._addSubTransaction(_entry); var res = _.extend({ subTransaction: subTransactionIdx }, _entry.token); if (parentToken && parentToken.source) { res.source = parentToken.source; res.eventGenerated = parentToken.eventGenerated; } return res; } console.warn('Invalid transactionId: ' + parentToken.transactionId); return parentToken; } }; Transaction.prototype.startTransactionById = function startTransactionById(transactionId) { var entry = this._get(transactionId); if (entry) { return this.startTransaction(entry.token); } entry = this._createTransactionEntry(transactionId); return entry.token; }; Transaction.prototype.endTransaction = function endTransaction(token) { var transactionId = token && token.transactionId; var entry = this._get(transactionId); if (entry && Number.isInteger(token.subTransaction)) { entry = entry.children[token.subTransaction]; } if (entry) { entry.timestamp.end = Date.now(); entry.end(); } else { console.warn('Invalid transactionId: ' + transactionId); } return token; }; Transaction.prototype.getTransactionById = function getTransactionById(id) { var entry = this._get(id); return entry ? entry.token : {}; }; Transaction.prototype.registerTransactionHandler = function registerTransactionHandler(token, handlerId, handler, args) { var isValid = this.isValidTransaction(token); if (isValid) { // register and invoke the handler this._register(handlerId, handler, token.transactionId, args); return this._invoke(handlerId); } else { // directly invoke the handler and move on var rv = handler([args]); return rv instanceof Promise ? rv : Promise.resolve(); } }; Transaction.prototype.whenTransactionComplete = function whenTransactionComplete(token) { // we only need wait for the root promise to complete var entry = this._get(token.transactionId); // return the transaction promise which disposes the transaction at the end return entry._promise.then(this._dispose.bind(this, token.transactionId)); }; Transaction.prototype.isValidTransaction = function isValidTransaction(token) { return !!this._get(token.transactionId); }; Transaction.prototype.getActiveTransactionList = function getActiveTransactionList() { return _.map(this._getAll(), function (entry) { return entry.token; }); }; /** * @private * Create a new transaction entry * @param {String} uniqueId unique transaction identifier * { * token: { * transactionId: 'xyz' * }, * end: function() { ... }, * timestamp: { * start: 1551142727899, * end: 1551142741757 * } * } */ Transaction.prototype._createTransactionEntry = function _createTransactionEntry(uniqueId) { var entry = { 'token': { 'transactionId': uniqueId }, 'timestamp': { 'start': Date.now() } }; entry._promise = new Promise(function (resolve) { this.end = function () { if (this.children && this.children.length > 0) { this.children[0]._promise.then(resolve); } else { resolve(); } }.bind(this); }.bind(entry)); this._set(uniqueId, entry); return entry; }; /** * @private * Add a sub-transcation to an existing transaction * @param {Object} entry parent transaction entry object * { * token: { * transactionId: 'xyz' * }, * children: [{ * end: function() { ... }, * next: { ... } * timestamp: { * start: 1551142727899, * end: 1551142741757 * } * }, { * end: function() { ... }, * timestamp: { * start: 1551142727899, * end: 1551142741757 * } * }] * end: function() { ... }, * timestamp: { * start: 1551142727899 * } * } */ Transaction.prototype._addSubTransaction = function _addSubTransaction(entry) { // prepare the subtransactions list if (!entry.children) { entry.children = []; } var subTransactionEntry = { timestamp: { start: Date.now() } }; var transactionIndex = entry.children.length; var lastItem = entry.children && entry.children[entry.children.length - 1]; // link the current transaction as the next transaction for the previous transaction if (lastItem) { lastItem.next = subTransactionEntry; } subTransactionEntry._promise = new Promise(function (resolve) { this.end = function () { if (this.next) { // resolve the my promise once the next promise is resolved this.next._promise.then(resolve); } else { resolve(); } }.bind(this); }.bind(subTransactionEntry)); // add the subtransaction to the existing entry entry.children.push(subTransactionEntry); return transactionIndex; }; Transaction.prototype._getAll = function _getAll() { var all = []; this._transactions.forEach(function (tranaction) { all.push(tranaction); }); return all; }; Transaction.prototype._get = function _get(transactionId) { return this._transactions.get(transactionId); }; Transaction.prototype._set = function _set(transactionId, entry) { this._transactions.set(transactionId, entry); }; Transaction.prototype._dispose = function _dispose(transactionId) { this._transactions.delete(transactionId); }; Transaction.prototype._register = function _register(handlerId, handler, transactionId, args) { var entry = this._handlers.get(handlerId); if (entry) { // update the transactions related to current handler if (entry.transactionIds.indexOf(transactionId) < 0) { entry.transactionIds.push(transactionId); } entry.args.push(args); } else { // register the handler this._handlers.set(handlerId, { 'transactionIds': [transactionId], 'handler': handler, 'args': [args] }); } }; Transaction.prototype._isRegistered = function _isRegistered(handlerId) { return !!this._handlers.get(handlerId); }; /** * This is an enhancement to replace Promise.all to handle cases * which the array of transactions can grow asynchronously. * Once the initial handler waits for all transactions complete, * transactions that are added afterwards may result the handler * to be invoked prematurely. */ Transaction.prototype._waitForAllTransactions = function _waitForAllTransactions(transactionIds) { var _this = this; var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; if (transactionIds.length > index) { // We can not rely on Promise.all since a new transaction can be added // after the handler was initially register and invoked var transaction = this._get(transactionIds[index]); if (transaction) { // wait for the current transaction to complete return transaction._promise.then(function () { // seek for the next transaction during the runtime return _this._waitForAllTransactions(transactionIds, index + 1); }); } else { // continue with the next transaction return this._waitForAllTransactions(transactionIds, index + 1); } } return Promise.resolve(); }; Transaction.prototype._invoke = function _invoke(handlerId) { var _this2 = this; var entry = this._handlers.get(handlerId); if (!entry._promise) { // @todo Use Transaction._waitForAllTransactions to support optimized rendering var transactions = _.map(entry.transactionIds, function (id) { return _this2._get(id)._promise; }); // invoke the handler once all related transactions are complete entry._promise = Promise.all(transactions).then(function () { // entry._promise = this._waitForAllTransactions(entry.transactionIds).then(() => { // dispose all transactions that are registered for this handler _.each(entry.transactionIds, _this2._dispose.bind(_this2)); try { return entry.handler(entry.args); } finally { // clean up the handler _this2._handlers.delete(handlerId); } }); } return entry._promise; }; return Transaction; }(); return Transaction; }); //# sourceMappingURL=Transaction.js.map