import retry from 'async/retry.js' import Bluebird from 'bluebird' import { Transaction } from 'sequelize' import { Model } from 'sequelize-typescript' import { sequelizeTypescript } from '@server/initializers/database.js' import { logger } from './logger.js' function retryTransactionWrapper ( functionToRetry: (arg1: A, arg2: B, arg3: C, arg4: D) => Promise, arg1: A, arg2: B, arg3: C, arg4: D, ): Promise function retryTransactionWrapper ( functionToRetry: (arg1: A, arg2: B, arg3: C) => Promise, arg1: A, arg2: B, arg3: C ): Promise function retryTransactionWrapper ( functionToRetry: (arg1: A, arg2: B) => Promise, arg1: A, arg2: B ): Promise function retryTransactionWrapper ( functionToRetry: (arg1: A) => Promise, arg1: A ): Promise function retryTransactionWrapper ( functionToRetry: () => Promise | Bluebird ): Promise function retryTransactionWrapper ( functionToRetry: (...args: any[]) => Promise, ...args: any[] ): Promise { return transactionRetryer(callback => { functionToRetry.apply(null, args) .then((result: T) => callback(null, result)) .catch(err => callback(err)) }) .catch(err => { logger.warn(`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 saveInTransactionWithRetries > (model: T) { const changedKeys = model.changed() if (!changedKeys) throw new Error('No changed keys found') return retryTransactionWrapper(() => { return sequelizeTypescript.transaction(async transaction => { try { await model.save({ transaction }) } catch { // Reinit changed keys for (const key of changedKeys) { model.changed(key as keyof Model, true) } } }) }) } // --------------------------------------------------------------------------- function resetSequelizeInstance (instance: Model) { return instance.reload() } function filterNonExistingModels ( fromDatabase: T[], newModels: T[] ) { return fromDatabase.filter(f => !newModels.find(newModel => newModel.hasSameUniqueKeysThan(f))) } function deleteAllModels > (models: T[], transaction: Transaction) { return Promise.all(models.map(f => f.destroy({ 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, saveInTransactionWithRetries, afterCommitIfTransaction, filterNonExistingModels, deleteAllModels, runInReadCommittedTransaction }