import * as retry from 'async/retry' import * as Bluebird from 'bluebird' import { QueryTypes, Transaction } from 'sequelize' import { Model } from 'sequelize-typescript' import { sequelizeTypescript } from '@server/initializers/database' import { logger } from './logger' function retryTransactionWrapper ( functionToRetry: (arg1: A, arg2: B, arg3: C, arg4: D) => Promise | Bluebird, arg1: A, arg2: B, arg3: C, arg4: D, ): Promise function retryTransactionWrapper ( functionToRetry: (arg1: A, arg2: B, arg3: C) => Promise | Bluebird, arg1: A, arg2: B, arg3: C ): Promise function retryTransactionWrapper ( functionToRetry: (arg1: A, arg2: B) => Promise | Bluebird, arg1: A, arg2: B ): Promise function retryTransactionWrapper ( functionToRetry: (arg1: A) => Promise | Bluebird, arg1: A ): Promise function retryTransactionWrapper ( functionToRetry: () => Promise | Bluebird ): Promise function retryTransactionWrapper ( functionToRetry: (...args: any[]) => Promise | Bluebird, ...args: any[] ): Promise { return transactionRetryer(callback => { functionToRetry.apply(null, args) .then((result: T) => callback(null, result)) .catch(err => callback(err)) }) .catch(err => { logger.error(`Cannot execute ${functionToRetry.name} with many retries.`, { err }) throw err }) } function transactionRetryer (func: (err: any, data: T) => any) { return new Promise((res, rej) => { retry( { times: 5, errorFilter: err => { const willRetry = (err.name === 'SequelizeDatabaseError') logger.debug('Maybe retrying the transaction function.', { willRetry, err, tags: [ 'sql', 'retry' ] }) return willRetry } }, func, (err, data) => err ? rej(err) : res(data) ) }) } // --------------------------------------------------------------------------- function updateInstanceWithAnother > (instanceToUpdate: T, baseInstance: U) { const obj = baseInstance.toJSON() for (const key of Object.keys(obj)) { instanceToUpdate[key] = obj[key] } } function resetSequelizeInstance (instance: Model, savedFields: object) { Object.keys(savedFields).forEach(key => { instance[key] = savedFields[key] }) } function deleteNonExistingModels > ( fromDatabase: T[], newModels: T[], t: Transaction ) { return fromDatabase.filter(f => !newModels.find(newModel => newModel.hasSameUniqueKeysThan(f))) .map(f => f.destroy({ transaction: t })) } // Sequelize always skip the update if we only update updatedAt field function setAsUpdated (table: string, id: number, transaction?: Transaction) { return sequelizeTypescript.query( `UPDATE "${table}" SET "updatedAt" = :updatedAt WHERE id = :id`, { replacements: { table, id, updatedAt: new Date() }, type: QueryTypes.UPDATE, transaction } ) } // --------------------------------------------------------------------------- function runInReadCommittedTransaction (fn: (t: Transaction) => Promise) { const options = { isolationLevel: Transaction.ISOLATION_LEVELS.READ_COMMITTED } return sequelizeTypescript.transaction(options, t => fn(t)) } function afterCommitIfTransaction (t: Transaction, fn: Function) { if (t) return t.afterCommit(() => fn()) return fn() } // --------------------------------------------------------------------------- export { resetSequelizeInstance, retryTransactionWrapper, transactionRetryer, updateInstanceWithAnother, afterCommitIfTransaction, deleteNonExistingModels, setAsUpdated, runInReadCommittedTransaction }