UNPKG

pg-transactional-outbox

Version:

A PostgreSQL based transactional outbox and inbox pattern implementation to support exactly once message processing (with at least once message delivery).

68 lines 3.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createAcknowledgeManager = void 0; const error_1 = require("../common/error"); /** * This LSN acknowledge manager cares about remembering the LSN numbers that * were sent from PostgreSQL and acknowledge the LSN only after all the LSNs * before were acknowledged as well. With the `startProcessingLSN` it tracks * the LSN and with `finishProcessingLSN` it marks this LSN as finished. It * checks then which (if any) LSN can be acknowledged (no older LSNs are in * pending state) and executed the `acknowledgeLsn` callback. * @param acknowledgeLsn Callback to actually acknowledge the WAL message * @param logger A logger instance for logging trace up to error logs * @returns two functions - one to be called when starting the WAL processing and one when the processing is done and the acknowledgement can be done. */ const createAcknowledgeManager = (acknowledgeLsn, logger) => { const processingMap = new Map(); const pendingAckMap = new Map(); const lsnToBigInt = (lsn) => { return BigInt('0x' + lsn.replace('/', '')); }; const checkForAcknowledgeableLSN = (currentLsn, currentLsnNumber) => { var _a; const sortedProcessingLsns = Array.from(processingMap.entries()).sort((a, b) => Number(a[1] - b[1])); processingMap.delete(currentLsn); pendingAckMap.set(currentLsn, currentLsnNumber); if (sortedProcessingLsns[0][0] !== currentLsn) { // there is still some message processed with a lower LSN return; } const [_nextLsn, nextLsnNumber] = (_a = sortedProcessingLsns[1]) !== null && _a !== void 0 ? _a : [ '0/ffffffffffffffff', BigInt('18446744073709551615'), // 64bit unsigned int is the maximum Postgres WAL LSN value ]; const sortedPendingAckLsns = Array.from(pendingAckMap.entries()).sort((a, b) => Number(a[1] - b[1])); let ackLsn = undefined; for (const [lsn, lsnNumber] of sortedPendingAckLsns) { if (lsnNumber < nextLsnNumber) { ackLsn = lsn; pendingAckMap.delete(lsn); } else { break; } } if (ackLsn) { logger.trace(`Acknowledging LSN up to ${ackLsn}`); acknowledgeLsn(ackLsn); } }; const startProcessingLSN = (lsn) => { if (processingMap.has(lsn)) { throw new error_1.TransactionalOutboxInboxError(`LSN ${lsn} is already being processed.`, 'LSN_ALREADY_PROCESSED'); } processingMap.set(lsn, lsnToBigInt(lsn)); }; const finishProcessingLSN = (lsn) => { const lsnNumber = processingMap.get(lsn); if (lsnNumber === undefined) { throw new error_1.TransactionalOutboxInboxError(`LSN ${lsn} was not registered as processing.`, 'LSN_NOT_PROCESSING'); } logger.trace(`Finished LSN ${lsn} - waiting for acknowledgement.`); checkForAcknowledgeableLSN(lsn, lsnNumber); }; return { startProcessingLSN, finishProcessingLSN }; }; exports.createAcknowledgeManager = createAcknowledgeManager; //# sourceMappingURL=acknowledge-manager.js.map