const db = require('@pocketgems/dynamodb')
const { API } = require('./api')
/**
* Thrown to avoid committing a transaction when an error occurs.
* @access private
*/
class TransactionAborted extends Error {
constructor (respData) {
super()
this.respData = respData
this.retryable = false
}
}
/**
* An API whose response is computed inside a transaction.
* @public
* @class
*/
class TxAPI extends API {
static IS_READ_ONLY = true
async _computeResponse () {
await this.preTxStart()
let ret
try {
ret = await db.Transaction.run(async tx => {
if (this.constructor.IS_READ_ONLY) {
tx.makeReadOnly()
}
this.tx = tx
this.req.tx = tx
let respData = await super._computeResponse()
if (this.__reply.statusCode < 400) {
// pre-commit hook may change the response data (and status code!)
respData = await this._callAndHandleRequestDone(
this.preCommit, respData)
}
// if the response code indicates an error, then don't commit
if (this.__reply.statusCode >= 400) {
throw new TransactionAborted(respData)
}
return respData
})
} catch (e) {
if (e instanceof TransactionAborted) {
return e.respData
} else {
throw e
}
} finally {
delete this.tx
delete this.req.tx
}
// _computeResponse() is called within _callAndHandleRequestDone; so we
// don't need to wrap this call to postCommit() in it (redundant)
return this.postCommit(ret)
}
/**
* Called just before the transaction starts. Useful to do async computation
* outside the transaction (reducing the window for contention).
*/
async preTxStart () {}
/**
* Called just before the transaction ATTEMPTS to commit. May alter the
* response by returning a new response value, or throwing RequestDone.
* Throwing an error will be propagated and handled by Transaction.run(),
* and will prevent the transaction from committing (unless the error is
* retryable).
*
* @param {*} respData the response data
* @returns {*} the (possibly updated) response data
*/
async preCommit (respData) { return respData }
/**
* Called after the transaction commits (and ONLY if it commits
* successfully). May alter the response by returning a new response value,
* or throwing RequestDone.
*
* @param {*} respData the response data
* @returns {*} the (possibly updated) response data
*/
async postCommit (respData) { return respData }
}
module.exports = { TxAPI }