@boringnode/bus
Version:
A simple and lean driver-based service bus implementation
266 lines (258 loc) • 7.41 kB
JavaScript
import {
debug_default
} from "./chunk-ISFQ7A55.js";
// src/bus.ts
import string from "@poppinss/utils/string";
import { createId } from "@paralleldrive/cuid2";
// src/retry_queue_with_duplicates.ts
var RetryQueueWithDuplicates = class {
#queue = /* @__PURE__ */ new Set();
#enabled;
#maxSize;
constructor(params = {}) {
const { enabled = true, maxSize = null } = params;
this.#enabled = enabled;
this.#maxSize = maxSize;
}
size() {
return this.#queue.size;
}
async process(handler) {
if (!this.#enabled) return;
for (const { channel, message } of this.#queue) {
const result = await handler(channel, message).catch(() => false);
if (!result) {
break;
}
this.dequeue();
}
}
enqueue(channel, message) {
if (!this.#enabled) return false;
if (this.#maxSize && this.#queue.size >= this.#maxSize) {
this.dequeue();
}
this.#queue.add({ channel, message });
return true;
}
dequeue() {
if (!this.#enabled) return;
const [first] = this.#queue;
if (first) {
this.#queue.delete(first);
return first.message;
}
}
};
// src/message_hasher.ts
import hash from "object-hash";
var MessageHasher = class {
hash(value) {
return hash(value, { algorithm: "sha1", encoding: "base64" });
}
};
// src/retry_queue_without_duplicates.ts
var RetryQueueWithoutDuplicates = class {
#queue = /* @__PURE__ */ new Map();
#messageHasher;
#enabled;
#maxSize;
constructor(params = {}) {
const { enabled = true, maxSize = null } = params;
this.#enabled = enabled;
this.#maxSize = maxSize;
this.#messageHasher = new MessageHasher();
}
#generateMessageHash(message) {
return this.#messageHasher.hash(message.payload);
}
size() {
return this.#queue.size;
}
async process(handler) {
if (!this.#enabled) return;
for (const { channel, message } of this.#queue.values()) {
const result = await handler(channel, message).catch(() => false);
if (!result) {
break;
}
this.dequeue();
}
}
enqueue(channel, message) {
if (!this.#enabled) return false;
if (this.#maxSize && this.#queue.size >= this.#maxSize) {
this.dequeue();
}
const hash2 = this.#generateMessageHash(message);
if (this.#queue.has(hash2)) {
return false;
}
this.#queue.set(hash2, { channel, message });
return true;
}
dequeue() {
if (!this.#enabled) return;
const { message } = this.#queue.values().next().value;
if (message) {
this.#queue.delete(this.#generateMessageHash(message));
return message;
}
}
};
// src/retry_queue.ts
var RetryQueue = class {
#options;
#queue;
constructor(params = {}) {
const { enabled = true, maxSize = null, removeDuplicates = true } = params;
this.#options = { enabled, maxSize, removeDuplicates };
if (removeDuplicates) {
this.#queue = new RetryQueueWithoutDuplicates({ enabled, maxSize });
return;
}
this.#queue = new RetryQueueWithDuplicates({ enabled, maxSize });
}
getOptions() {
return this.#options;
}
getInternalQueue() {
return this.#queue;
}
size() {
return this.#queue.size();
}
async process(handler) {
return this.#queue.process(handler);
}
enqueue(channel, message) {
return this.#queue.enqueue(channel, message);
}
dequeue() {
this.#queue.dequeue();
}
};
// src/bus.ts
var Bus = class {
#transport;
#busId;
#errorRetryQueue;
#retryQueueInterval;
constructor(transport, options) {
this.#transport = transport;
this.#busId = createId();
this.#errorRetryQueue = new RetryQueue(options?.retryQueue);
if (options?.retryQueue?.retryInterval) {
const intervalValue = typeof options?.retryQueue?.retryInterval === "number" ? options?.retryQueue?.retryInterval : string.milliseconds.parse(options?.retryQueue?.retryInterval);
this.#retryQueueInterval = setInterval(() => {
void this.processErrorRetryQueue();
}, intervalValue);
}
transport.setId(this.#busId).onReconnect(() => this.#onReconnect());
}
getRetryQueue() {
return this.#errorRetryQueue;
}
processErrorRetryQueue() {
debug_default(`start error retry queue processing with ${this.#errorRetryQueue.size()} messages`);
return this.#errorRetryQueue.process(async (channel, message) => {
return await this.publish(channel, message.payload);
});
}
async #onReconnect() {
debug_default(`bus transport ${this.#transport.constructor.name} reconnected`);
await this.processErrorRetryQueue();
}
subscribe(channel, handler) {
debug_default(`subscribing to channel ${channel}`);
return this.#transport.subscribe(channel, async (message) => {
debug_default("received message %j from bus", message);
handler(message);
});
}
async publish(channel, message) {
try {
debug_default('publishing message "%j" to channel "%s"', message, channel);
await this.#transport.publish(channel, message);
return true;
} catch (error) {
debug_default('error publishing message "%j" to channel "%s". Retrying later', message, channel);
const wasAdded = this.#errorRetryQueue.enqueue(channel, {
payload: message,
busId: this.#busId
});
if (!wasAdded) return false;
debug_default(`added message %j to error retry queue`, message);
return false;
}
}
disconnect() {
if (this.#retryQueueInterval) {
clearInterval(this.#retryQueueInterval);
}
return this.#transport.disconnect();
}
unsubscribe(channel) {
return this.#transport.unsubscribe(channel);
}
};
// src/bus_manager.ts
import { RuntimeException } from "@poppinss/utils/exceptions";
var BusManager = class {
#defaultTransportName;
#transports;
#transportsCache = {};
constructor(config) {
debug_default("creating bus manager. config: %O", config);
this.#transports = config.transports;
this.#defaultTransportName = config.default;
}
use(transports) {
let transportToUse = transports || this.#defaultTransportName;
if (!transportToUse) {
throw new RuntimeException(
"Cannot create bus instance. No default transport is defined in the config"
);
}
const cachedTransport = this.#transportsCache[transportToUse];
if (cachedTransport) {
debug_default("returning cached transport instance for %s", transportToUse);
return cachedTransport;
}
const transportConfig = this.#transports[transportToUse];
debug_default("creating new transport instance for %s", transportToUse);
const transportInstance = new Bus(transportConfig.transport(), {
retryQueue: transportConfig.retryQueue
});
this.#transportsCache[transportToUse] = transportInstance;
return transportInstance;
}
async publish(channel, message) {
return this.use().publish(channel, message);
}
subscribe(channel, handler) {
return this.use().subscribe(channel, handler);
}
unsubscribe(channel) {
return this.use().unsubscribe(channel);
}
disconnect() {
return this.use().disconnect();
}
};
// src/define_config.ts
function defineConfig(config) {
return config;
}
export {
Bus,
BusManager,
defineConfig
};
/**
* @boringnode/bus
*
* @license MIT
* @copyright BoringNode
*/
//# sourceMappingURL=index.js.map