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).

81 lines 4.88 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.initiateMessageProcessing = void 0; const utils_1 = require("../common/utils"); /** * This function makes sure the message was not and is not currently being * worked on and acquires a lock to prevent other processes to work with this * message. It locks the record via `SELECT ... FOR NO KEY UPDATE`. It updates * the message object and sets the started_attempts, finished_attempts, * locked_until, and processed_at values (again) on the message to be sure no * other process altered them. * @param message The message for which to acquire a lock and set the updatable properties (again). * @param client The database client. Must be part of the transaction where the message handling changes are later done. * @param config The configuration settings for the polling or replication listener. * @param messageNotFoundRetryStrategy The retry strategy if the message could not be found in the database. * @returns 'MESSAGE_NOT_FOUND' if the message was not found, 'ALREADY_PROCESSED' if it was processed, and otherwise assigns the properties to the message and returns it. */ const initiateMessageProcessing = (message, client, settings, messageNotFoundRetryStrategy) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b; const selectResult = yield loadAndLockMessage(message, client, settings, messageNotFoundRetryStrategy); if (selectResult === 'MESSAGE_NOT_FOUND') { return 'MESSAGE_NOT_FOUND'; } const { started_attempts, finished_attempts, processed_at, abandoned_at, locked_until, } = selectResult.rows[0]; // ensures latest values (e.g. if the `startedAttemptsIncrement` was not called or another // process changed them between the `startedAttemptsIncrement` and this call. message.startedAttempts = started_attempts; message.finishedAttempts = finished_attempts; message.lockedUntil = locked_until === null || locked_until === void 0 ? void 0 : locked_until.toISOString(); message.processedAt = (_a = processed_at === null || processed_at === void 0 ? void 0 : processed_at.toISOString()) !== null && _a !== void 0 ? _a : null; message.abandonedAt = (_b = abandoned_at === null || abandoned_at === void 0 ? void 0 : abandoned_at.toISOString()) !== null && _b !== void 0 ? _b : null; if (processed_at) { return 'ALREADY_PROCESSED'; } if (abandoned_at) { return 'ABANDONED_MESSAGE'; } return true; }); exports.initiateMessageProcessing = initiateMessageProcessing; /** * Load and lock (and potentially retry) a message. * @param message The message for which to acquire the lock. * @param client The database client. Must be part of the transaction where the message handling changes are later done. * @param config The configuration settings for the polling or replication listener. * @param messageNotFoundRetryStrategy The retry strategy if the message could not be found in the database. * @returns 'MESSAGE_NOT_FOUND' if the message was not found, otherwise the message details from the database. */ const loadAndLockMessage = (message, client, settings, messageNotFoundRetryStrategy) => __awaiter(void 0, void 0, void 0, function* () { let selectResult; let attempts = 0; do { // Use a NOWAIT select to immediately fail if another process is locking that message selectResult = yield client.query( /* sql */ ` SELECT started_attempts, finished_attempts, processed_at, abandoned_at, locked_until FROM ${settings.dbSchema}.${settings.dbTable} WHERE id = $1 FOR NO KEY UPDATE NOWAIT;`, [message.id]); if (selectResult.rowCount === 0 || selectResult.rowCount === null) { const { retry, delayInMs } = messageNotFoundRetryStrategy(message, ++attempts); if (retry) { yield (0, utils_1.sleep)(delayInMs); } else { return 'MESSAGE_NOT_FOUND'; } } else { return selectResult; } // eslint-disable-next-line no-constant-condition } while (true); }); //# sourceMappingURL=initiate-message-processing.js.map