UNPKG

@cap-js-community/event-queue

Version:

An event queue that enables secure transactional processing of asynchronous and periodic events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.

112 lines (100 loc) 4.42 kB
"use strict"; const { CronExpressionParser } = require("cron-parser"); const config = require("./config"); const common = require("./shared/common"); const EventQueueError = require("./EventQueueError"); const openTelemetry = require("./shared/openTelemetry"); /** * Asynchronously publishes a series of events to the event queue. * * @param {Transaction} tx - The transaction object to be used for database operations. * @param {Array|Object} events - An array of event objects or a single event object. Each event object should match the Event table structure: * { * type: String, // Event type. This is a required field. * subType: String, // Event subtype. This is a required field. * referenceEntity: String, // Reference entity associated with the event. * referenceEntityKey: UUID, // UUID key of the reference entity. * status: Status, // Status of the event, defaults to 0. * payload: LargeString, // Payload of the event. * attempts: Integer, // The number of attempts made, defaults to 0. * lastAttemptTimestamp: Timestamp, // Timestamp of the last attempt. * createdAt: Timestamp, // Timestamp of event creation. This field is automatically set on insert. * startAfter: Timestamp, // Timestamp indicating when the event should start after. * } * @param {Object} [options] - Optional settings. * @param {Boolean} [options.skipBroadcast=false] - If set to true, event broadcasting will be skipped. Defaults to false. * @param {Boolean} [options.skipInsertEventsBeforeCommit=false] - If set to true, events will not be inserted before the transaction commit. Defaults to false. * @throws {EventQueueError} Throws an error if the configuration is not initialized. * @throws {EventQueueError} Throws an error if the event type is unknown. * @throws {EventQueueError} Throws an error if the startAfter field is not a valid date. * @returns {Promise<*>} Returns a promise which resolves to the result of the database insert operation. */ const publishEvent = async ( tx, events, { skipBroadcast = false, skipInsertEventsBeforeCommit = false, addTraceContext = true, allowNotExistingConfiguration = false, } = {} ) => { if (!config.initialized) { throw EventQueueError.notInitialized(); } const eventsForProcessing = Array.isArray(events) ? events : [events]; for (const event of eventsForProcessing) { const { type, subType, startAfter, namespace } = event; const eventConfig = config.getEventConfig(type, subType, namespace); if (!eventConfig && !allowNotExistingConfiguration) { throw EventQueueError.unknownEventType(type, subType); } if (startAfter && !common.isValidDate(startAfter)) { throw EventQueueError.malformedDate(startAfter); } if (eventConfig?.isPeriodic) { throw EventQueueError.manuelPeriodicEventInsert(type, subType); } if (typeof event.payload !== "string") { event.payload = JSON.stringify(event.payload); } if (addTraceContext) { event.context = JSON.stringify({ traceContext: openTelemetry.getCurrentTraceContext() }); } if (eventConfig?.timeBucket && !(startAfter in event)) { event.startAfter = CronExpressionParser.parse(eventConfig.timeBucket).next().toISOString(); } if (event.namespace === undefined) { event.namespace = config.namespace; } } if (config.insertEventsBeforeCommit && !skipInsertEventsBeforeCommit) { _registerHandlerAndAddEvents(tx, events, skipBroadcast); } else { let result; tx._skipEventQueueBroadcast = skipBroadcast; result = await tx.run(INSERT.into(config.tableNameEventQueue).entries(events)); tx._skipEventQueueBroadcast = false; return result; } }; const _registerHandlerAndAddEvents = (tx, events, skipBroadcast) => { tx._eventQueue ??= { events: [], handlerRegistered: false }; tx._eventQueue.events = tx._eventQueue.events.concat(events); if (tx._eventQueue.handlerRegistered) { return; } tx._eventQueue.handlerRegistered = true; tx.context.before("commit", async () => { if (!tx._eventQueue.events?.length) { return; } tx._skipEventQueueBroadcast = skipBroadcast; await tx.run(INSERT.into(config.tableNameEventQueue).entries(tx._eventQueue.events)); tx._skipEventQueueBroadcast = false; tx._eventQueue = null; }); }; module.exports = { publishEvent, };