@arturwojnar/hermes-postgresql
Version:
Production-Ready TypeScript Outbox Pattern for PostgreSQL
86 lines • 2.98 kB
JavaScript
import { assertNever, CancellationPromise, Duration, noop } from '@arturwojnar/hermes';
import { setTimeout } from 'node:timers/promises';
const createSerializedPublishingQueue = (publish, options) => {
const waitAfterFailedPublish = options?.waitAfterFailedPublish || Duration.ofSeconds(1);
const onFailedPublish = options?.onFailedPublish || (() => Promise.resolve());
const ids = new Set();
const messages = new Array();
let isPublishing = false;
let publishingPromise = CancellationPromise.resolved();
const queue = (messageToPublish) => {
if (ids.has(messageToPublish.transaction.lsn)) {
return messageToPublish;
}
ids.add(messageToPublish.transaction.lsn);
messages.push(messageToPublish);
return messageToPublish;
};
const dequeueOldest = () => {
if (messages.length === 0) {
return;
}
const oldest = messages.shift();
ids.delete(oldest.transaction.lsn);
};
const run = async () => {
if (isPublishing) {
return;
}
isPublishing = true;
publishingPromise = new CancellationPromise();
try {
do {
const result = await _publishOldestMessage();
switch (result) {
case 'published':
continue;
case 'failed':
await onFailedPublish(messages[0].transaction);
if (waitAfterFailedPublish) {
await setTimeout(waitAfterFailedPublish.ms);
}
break;
case 'exhausted':
isPublishing = false;
publishingPromise.resolve();
break;
default:
assertNever(result);
}
} while (isPublishing);
}
finally {
isPublishing = false;
publishingPromise.resolve();
}
};
const _publishOldestMessage = async () => {
if (messages.length === 0) {
return 'exhausted';
}
const oldest = messages[0];
try {
await publish(oldest);
dequeueOldest();
await oldest.acknowledge();
return 'published';
}
catch (error) {
if (messages.length && messages[0].transaction.lsn !== oldest.transaction.lsn) {
ids.add(oldest.transaction.lsn);
messages.unshift(oldest);
}
return 'failed';
}
};
return {
name: () => 'SerializedPublishingQueue',
queue,
run,
size: () => messages.length,
waitUntilIsEmpty: () => publishingPromise,
dispose: noop,
};
};
export { createSerializedPublishingQueue };
//# sourceMappingURL=createSerializedPublishingQueue.js.map