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).
78 lines • 4.28 kB
JavaScript
;
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.defaultReplicationListenerAndSlotRestartStrategy = exports.defaultReplicationListenerRestartStrategy = void 0;
const pg_1 = require("pg");
const error_1 = require("../../common/error");
/**
* The default listener restart strategy checks if the error is a PostgreSQL
* error. If the PostgreSQL error is about the replication slot being in use, it
* logs a trace entry and waits for the configured `restartDelaySlotInUseInMs`
* time. Otherwise, it logs an error entry and waits for the configured
* `restartDelayInMs`.
*/
const defaultReplicationListenerRestartStrategy = (config) => {
return handleError(config);
};
exports.defaultReplicationListenerRestartStrategy = defaultReplicationListenerRestartStrategy;
/**
* The default listener and slot restart strategy uses the same logic as the
* `defaultListenerRestartStrategy`. In addition, it checks if a PostgreSQL error
* is about the replication slot not existing (e.g. after a DB failover). Then
* it tries to create the replication slot with the connection details of the
* replication user slot and waits for the configured `restartDelayInMs`.
*/
const defaultReplicationListenerAndSlotRestartStrategy = (config) => {
return handleError(config, createReplicationSlot);
};
exports.defaultReplicationListenerAndSlotRestartStrategy = defaultReplicationListenerAndSlotRestartStrategy;
const handleError = ({ settings: { restartDelayInMs, restartDelaySlotInUseInMs, dbReplicationSlot, }, dbListenerConfig: pgReplicationConfig, }, replicationSlotNotFoundCallback) => {
return (error, logger, outboxOrInbox) => __awaiter(void 0, void 0, void 0, function* () {
if ('routine' in error && error.routine === 'ReplicationSlotAcquire') {
if ('code' in error && error.code === '55006') {
logger.trace(error, `The replication slot for the ${outboxOrInbox} listener is currently in use.`);
return restartDelaySlotInUseInMs;
}
else if ('code' in error && error.code === '42704') {
// replication slot not found - best effort to create it again
logger.error(error, error.message);
yield (replicationSlotNotFoundCallback === null || replicationSlotNotFoundCallback === void 0 ? void 0 : replicationSlotNotFoundCallback(pgReplicationConfig, dbReplicationSlot, logger, outboxOrInbox));
}
return restartDelayInMs;
}
if (!(error instanceof error_1.MessageError) &&
error.constructor.name !== error_1.MessageError.name // needed for jest which has an "instanceof" bug
) {
// Message based errors are already logged
logger.error(error, `Transactional ${outboxOrInbox} listener error`);
}
return restartDelayInMs;
});
};
const createReplicationSlot = (pgReplicationConfig, dbReplicationSlot, logger, outboxOrInbox) => __awaiter(void 0, void 0, void 0, function* () {
const pool = new pg_1.Pool(pgReplicationConfig);
try {
yield pool.query(
/** sql*/ `select pg_create_logical_replication_slot('${dbReplicationSlot}', 'pgoutput');`);
}
catch (err) {
logger.trace((0, error_1.ensureExtendedError)(err, 'DB_ERROR'), `Failed to create the replication slot for the ${outboxOrInbox} which does not exist.`);
}
finally {
try {
yield pool.end();
}
catch (_a) {
// ignore
}
}
});
//# sourceMappingURL=listener-restart-strategy.js.map