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
JavaScript
;
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