"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { MemoryMessageProvider: () => MemoryMessageProvider, MemoryTaskProvider: () => MemoryTaskProvider, Qified: () => Qified, QifiedEvents: () => QifiedEvents, QifiedHooks: () => QifiedHooks }); module.exports = __toCommonJS(index_exports); var import_hookified = require("hookified"); // src/memory/message.ts var defaultMemoryId = "@qified/memory"; var MemoryMessageProvider = class { _subscriptions; _id; /** * Creates an instance of MemoryMessageProvider. * @param {MemoryMessageProviderOptions} options - Optional configuration for the provider. */ constructor(options) { this._subscriptions = /* @__PURE__ */ new Map(); this._id = options?.id ?? defaultMemoryId; } /** * Gets the provider ID for the memory message provider. * @returns {string} The provider ID. */ get id() { return this._id; } /** * Sets the provider ID for the memory message provider. * @param {string} id The new provider ID. */ set id(id) { this._id = id; } /** * Gets the subscriptions map for all topics. * @returns {Map} The subscriptions map. */ get subscriptions() { return this._subscriptions; } /** * Sets the subscriptions map. * @param {Map} value The new subscriptions map. */ set subscriptions(value) { this._subscriptions = value; } /** * Publishes a message to a specified topic. * All handlers subscribed to the topic will be called synchronously in order. * @param {string} topic The topic to publish the message to. * @param {Message} message The message to publish. * @returns {Promise} A promise that resolves when all handlers have been called. */ async publish(topic, message) { const messageWithProvider = { ...message, providerId: this._id }; const subscriptions = this._subscriptions.get(topic) ?? []; for (const subscription of subscriptions) { await subscription.handler(messageWithProvider); } } /** * Subscribes to a specified topic. * @param {string} topic The topic to subscribe to. * @param {TopicHandler} handler The handler to process incoming messages. * @returns {Promise} A promise that resolves when the subscription is complete. */ async subscribe(topic, handler) { if (!this._subscriptions.has(topic)) { this._subscriptions.set(topic, []); } this._subscriptions.get(topic)?.push(handler); } /** * Unsubscribes from a specified topic. * If an ID is provided, only the handler with that ID is removed. * If no ID is provided, all handlers for the topic are removed. * @param {string} topic The topic to unsubscribe from. * @param {string} [id] Optional identifier for the subscription to remove. * @returns {Promise} A promise that resolves when the unsubscription is complete. */ async unsubscribe(topic, id) { if (id) { const subscriptions = this._subscriptions.get(topic); if (subscriptions) { this._subscriptions.set( topic, subscriptions.filter((sub) => sub.id !== id) ); } } else { this._subscriptions.delete(topic); } } /** * Disconnects and clears all subscriptions. * @returns {Promise} A promise that resolves when the disconnection is complete. */ async disconnect() { this._subscriptions.clear(); } }; // src/memory/task.ts var defaultMemoryTaskId = "@qified/memory"; var defaultTimeout = 3e4; var defaultRetries = 3; var MemoryTaskProvider = class { _id; _timeout; _retries; _taskHandlers; _queues; _processing; // Map of queue -> Set of task IDs being processed _deadLetterQueue; _taskIdCounter = 0; _active = true; /** * Creates an instance of MemoryTaskProvider. * @param {MemoryTaskProviderOptions} options - Optional configuration for the provider. */ constructor(options) { this._id = options?.id ?? defaultMemoryTaskId; this._timeout = options?.timeout ?? defaultTimeout; this._retries = options?.retries ?? defaultRetries; this._taskHandlers = /* @__PURE__ */ new Map(); this._queues = /* @__PURE__ */ new Map(); this._processing = /* @__PURE__ */ new Map(); this._deadLetterQueue = /* @__PURE__ */ new Map(); } /** * Gets the provider ID for the memory task provider. * @returns {string} The provider ID. */ get id() { return this._id; } /** * Sets the provider ID for the memory task provider. * @param {string} id The new provider ID. */ set id(id) { this._id = id; } /** * Gets the default timeout for task processing. * @returns {number} The timeout in milliseconds. */ get timeout() { return this._timeout; } /** * Sets the default timeout for task processing. * @param {number} timeout The timeout in milliseconds. */ set timeout(timeout) { this._timeout = timeout; } /** * Gets the default maximum retry attempts. * @returns {number} The maximum retry attempts. */ get retries() { return this._retries; } /** * Sets the default maximum retry attempts. * @param {number} retries The maximum retry attempts. */ set retries(retries) { this._retries = retries; } /** * Gets the task handlers map. * @returns {Map} The task handlers map. */ get taskHandlers() { return this._taskHandlers; } /** * Sets the task handlers map. * @param {Map} value The new task handlers map. */ set taskHandlers(value) { this._taskHandlers = value; } /** * Generates a unique task ID. * @returns {string} A unique task ID. */ generateTaskId() { return `task-${Date.now()}-${++this._taskIdCounter}`; } /** * Enqueues a task to a specific queue. * Automatically assigns ID and timestamp to the task. * @param {string} queue - The queue name to enqueue to. * @param {EnqueueTask} taskData - The task data to enqueue. * @returns {Promise} The ID of the enqueued task. */ async enqueue(queue, taskData) { if (!this._active) { throw new Error("TaskProvider has been disconnected"); } const task = { id: this.generateTaskId(), timestamp: Date.now(), ...taskData }; const queuedTask = { task, attempt: 0, deadlineAt: 0, processing: false }; if (!this._queues.has(queue)) { this._queues.set(queue, []); } this._queues.get(queue)?.push(queuedTask); await this.processQueue(queue); return task.id; } /** * Registers a handler to process tasks from a queue. * Starts processing any pending tasks in the queue. * @param {string} queue - The queue name to dequeue from. * @param {TaskHandler} handler - The handler configuration. * @returns {Promise} */ async dequeue(queue, handler) { if (!this._active) { throw new Error("TaskProvider has been disconnected"); } if (!this._taskHandlers.has(queue)) { this._taskHandlers.set(queue, []); } this._taskHandlers.get(queue)?.push(handler); await this.processQueue(queue); } /** * Processes tasks in a queue by delivering them to registered handlers. * @param {string} queue - The queue name to process. */ async processQueue(queue) { if (!this._active) { return; } const handlers = this._taskHandlers.get(queue); if (!handlers || handlers.length === 0) { return; } const queuedTasks = this._queues.get(queue); if (!queuedTasks || queuedTasks.length === 0) { return; } const processingSet = this._processing.get(queue) ?? /* @__PURE__ */ new Set(); this._processing.set(queue, processingSet); for (const queuedTask of queuedTasks) { if (queuedTask.processing || processingSet.has(queuedTask.task.id)) { continue; } queuedTask.processing = true; processingSet.add(queuedTask.task.id); for (const handler of handlers) { void this.processTask(queue, queuedTask, handler); } } } /** * Processes a single task with a handler. * @param {string} queue - The queue name. * @param {QueuedTask} queuedTask - The queued task to process. * @param {TaskHandler} handler - The handler to process the task. */ async processTask(queue, queuedTask, handler) { const { task } = queuedTask; const maxRetries = task.maxRetries ?? this._retries; const timeout = task.timeout ?? this._timeout; queuedTask.attempt++; queuedTask.deadlineAt = Date.now() + timeout; let acknowledged = false; let rejected = false; const context = { ack: async () => { if (acknowledged || rejected) { return; } acknowledged = true; await this.removeTask(queue, task.id); }, reject: async (requeue = true) => { if (acknowledged || rejected) { return; } rejected = true; if (requeue && queuedTask.attempt < maxRetries) { queuedTask.processing = false; this._processing.get(queue)?.delete(task.id); setTimeout(() => { void this.processQueue(queue); }, 100); } else { await this.moveToDeadLetter(queue, task); await this.removeTask(queue, task.id); } }, extend: async (ttl) => { if (acknowledged || rejected) { return; } queuedTask.deadlineAt = Date.now() + ttl; if (queuedTask.timeoutHandle) { clearTimeout(queuedTask.timeoutHandle); } queuedTask.timeoutHandle = setTimeout(() => { if (!acknowledged && !rejected) { void context.reject(true); } }, ttl); }, metadata: { attempt: queuedTask.attempt, maxRetries } }; queuedTask.timeoutHandle = setTimeout(() => { if (!acknowledged && !rejected) { void context.reject(true); } }, timeout); try { await handler.handler(task, context); if (!acknowledged && !rejected) { await context.ack(); } } catch (_error) { if (!acknowledged && !rejected) { await context.reject(true); } } finally { if (queuedTask.timeoutHandle) { clearTimeout(queuedTask.timeoutHandle); } } } /** * Removes a task from the queue. * @param {string} queue - The queue name. * @param {string} taskId - The task ID to remove. */ async removeTask(queue, taskId) { const queuedTasks = this._queues.get(queue); if (queuedTasks) { const index = queuedTasks.findIndex((qt) => qt.task.id === taskId); if (index !== -1) { queuedTasks.splice(index, 1); } } this._processing.get(queue)?.delete(taskId); } /** * Moves a task to the dead-letter queue. * @param {string} queue - The original queue name. * @param {Task} task - The task to move. */ async moveToDeadLetter(queue, task) { const dlqKey = `${queue}:dead-letter`; if (!this._deadLetterQueue.has(dlqKey)) { this._deadLetterQueue.set(dlqKey, []); } this._deadLetterQueue.get(dlqKey)?.push(task); } /** * Unsubscribes a handler from a queue. * @param {string} queue - The queue name to unsubscribe from. * @param {string} [id] - Optional handler ID. If not provided, removes all handlers. * @returns {Promise} */ async unsubscribe(queue, id) { if (id) { const handlers = this._taskHandlers.get(queue); if (handlers) { this._taskHandlers.set( queue, handlers.filter((h) => h.id !== id) ); } } else { this._taskHandlers.delete(queue); } } /** * Disconnects and clears all queues and handlers. * Stops all task processing. * @returns {Promise} */ async disconnect() { this._active = false; for (const queuedTasks of this._queues.values()) { for (const queuedTask of queuedTasks) { if (queuedTask.timeoutHandle) { clearTimeout(queuedTask.timeoutHandle); } } } this._taskHandlers.clear(); this._queues.clear(); this._processing.clear(); this._deadLetterQueue.clear(); } /** * Gets all tasks in the dead-letter queue for a specific queue. * Useful for debugging and monitoring failed tasks. * @param {string} queue - The queue name. * @returns {Task[]} Array of tasks in the dead-letter queue. */ getDeadLetterTasks(queue) { const dlqKey = `${queue}:dead-letter`; return this._deadLetterQueue.get(dlqKey) ?? []; } /** * Gets the current state of a queue. * Useful for monitoring and debugging. * @param {string} queue - The queue name. * @returns {Object} Queue statistics. */ getQueueStats(queue) { const queuedTasks = this._queues.get(queue) ?? []; const processing = this._processing.get(queue)?.size ?? 0; const waiting = queuedTasks.filter((qt) => !qt.processing).length; const dlqKey = `${queue}:dead-letter`; const deadLetter = this._deadLetterQueue.get(dlqKey)?.length ?? 0; return { waiting, processing, deadLetter }; } }; // src/index.ts var QifiedEvents = /* @__PURE__ */ ((QifiedEvents2) => { QifiedEvents2["error"] = "error"; QifiedEvents2["info"] = "info"; QifiedEvents2["warn"] = "warn"; QifiedEvents2["publish"] = "publish"; QifiedEvents2["subscribe"] = "subscribe"; QifiedEvents2["unsubscribe"] = "unsubscribe"; QifiedEvents2["disconnect"] = "disconnect"; return QifiedEvents2; })(QifiedEvents || {}); var QifiedHooks = /* @__PURE__ */ ((QifiedHooks2) => { QifiedHooks2["beforeSubscribe"] = "before:subscribe"; QifiedHooks2["afterSubscribe"] = "after:subscribe"; QifiedHooks2["beforePublish"] = "before:publish"; QifiedHooks2["afterPublish"] = "after:publish"; QifiedHooks2["beforeUnsubscribe"] = "before:unsubscribe"; QifiedHooks2["afterUnsubscribe"] = "after:unsubscribe"; QifiedHooks2["beforeDisconnect"] = "before:disconnect"; QifiedHooks2["afterDisconnect"] = "after:disconnect"; return QifiedHooks2; })(QifiedHooks || {}); var Qified = class extends import_hookified.Hookified { _messageProviders = []; _taskProviders = []; /** * Creates an instance of Qified. * @param {QifiedOptions} options - Optional configuration for Qified. */ constructor(options) { super(options); if (options?.messageProviders) { if (Array.isArray(options?.messageProviders)) { this._messageProviders = options.messageProviders; } else { this._messageProviders = [options?.messageProviders]; } } if (options?.taskProviders) { if (Array.isArray(options?.taskProviders)) { this._taskProviders = options.taskProviders; } else { this._taskProviders = [options?.taskProviders]; } } } /** * Gets or sets the message providers. * @returns {MessageProvider[]} The array of message providers. */ get messageProviders() { return this._messageProviders; } /** * Sets the message providers. * @param {MessageProvider[]} providers - The array of message providers to set. */ set messageProviders(providers) { this._messageProviders = providers; } /** * Gets or sets the task providers. * @returns {TaskProvider[]} The array of task providers. */ get taskProviders() { return this._taskProviders; } /** * Sets the task providers. * @param {TaskProvider[]} providers - The array of task providers to set. */ set taskProviders(providers) { this._taskProviders = providers; } /** * Subscribes to a topic. If you have multiple message providers, it will subscribe to the topic on all of them. * @param {string} topic - The topic to subscribe to. * @param {TopicHandler} handler - The handler to call when a message is published to the topic. */ async subscribe(topic, handler) { try { const context = { topic, handler }; await this.hook("before:subscribe" /* beforeSubscribe */, context); const promises = this._messageProviders.map( async (provider) => provider.subscribe(context.topic, context.handler) ); await Promise.all(promises); await this.hook("after:subscribe" /* afterSubscribe */, { topic: context.topic, handler: context.handler }); this.emit("subscribe" /* subscribe */, { topic: context.topic, handler: context.handler }); } catch (error) { this.emit("error" /* error */, error); } } /** * Publishes a message to a topic. If you have multiple message providers, it will publish the message to all of them. * @param {string} topic - The topic to publish to. * @param {Message} message - The message to publish. */ async publish(topic, message) { try { const context = { topic, message }; await this.hook("before:publish" /* beforePublish */, context); const promises = this._messageProviders.map( async (provider) => provider.publish(context.topic, context.message) ); await Promise.all(promises); await this.hook("after:publish" /* afterPublish */, { topic: context.topic, message: context.message }); this.emit("publish" /* publish */, { topic: context.topic, message: context.message }); } catch (error) { this.emit("error" /* error */, error); } } /** * Unsubscribes from a topic. If you have multiple message providers, it will unsubscribe from the topic on all of them. * If an ID is provided, it will unsubscribe only that handler. If no ID is provided, it will unsubscribe all handlers for the topic. * @param topic - The topic to unsubscribe from. * @param id - The optional ID of the handler to unsubscribe. If not provided, all handlers for the topic will be unsubscribed. */ async unsubscribe(topic, id) { try { const context = { topic, id }; await this.hook("before:unsubscribe" /* beforeUnsubscribe */, context); const promises = this._messageProviders.map( async (provider) => provider.unsubscribe(context.topic, context.id) ); await Promise.all(promises); await this.hook("after:unsubscribe" /* afterUnsubscribe */, { topic: context.topic, id: context.id }); this.emit("unsubscribe" /* unsubscribe */, { topic: context.topic, id: context.id }); } catch (error) { this.emit("error" /* error */, error); } } /** * Disconnects from all providers. * This method will call the `disconnect` method on each message provider. */ async disconnect() { try { const context = { providerCount: this._messageProviders.length }; await this.hook("before:disconnect" /* beforeDisconnect */, context); const promises = this._messageProviders.map( async (provider) => provider.disconnect() ); await Promise.all(promises); this._messageProviders = []; await this.hook("after:disconnect" /* afterDisconnect */, { providerCount: context.providerCount }); this.emit("disconnect" /* disconnect */); } catch (error) { this.emit("error" /* error */, error); } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MemoryMessageProvider, MemoryTaskProvider, Qified, QifiedEvents, QifiedHooks }); /* v8 ignore next -- @preserve */