UNPKG

@boringnode/bus

Version:

A simple and lean driver-based service bus implementation

266 lines (258 loc) 7.41 kB
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