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.

133 lines (116 loc) 3.96 kB
"use strict"; const cds = require("@sap/cds"); const { publishEvent } = require("../publishEvent"); const config = require("../config"); const OUTBOXED = Symbol("outboxed"); const UNBOXED = Symbol("unboxed"); const CDS_EVENT_TYPE = "CAP_OUTBOX"; const COMPONENT_NAME = "/eventQueue/eventQueueAsOutbox"; const EVENT_QUEUE_SPECIFIC_FIELDS = ["startAfter", "referenceEntity", "referenceEntityKey"]; function outboxed(srv, customOpts) { if (!(new.target || customOpts)) { const former = srv[OUTBOXED]; if (former) { return former; } } const logger = cds.log(COMPONENT_NAME); let outboxOpts = Object.assign( {}, (typeof cds.requires.outbox === "object" && cds.requires.outbox) || {}, (typeof srv.options?.outbox === "object" && srv.options.outbox) || {}, customOpts || {} ); config.addCAPOutboxEventBase(srv.name, outboxOpts); const originalSrv = srv[UNBOXED] || srv; const outboxedSrv = Object.create(originalSrv); outboxedSrv[UNBOXED] = originalSrv; if (!new.target) { if (!srv[OUTBOXED]) { Object.defineProperty(srv, OUTBOXED, { value: outboxedSrv }); } } outboxedSrv.handle = async function (req) { const context = req.context || cds.context; outboxOpts = Object.assign( {}, (typeof cds.requires.outbox === "object" && cds.requires.outbox) || {}, (typeof srv.options?.outbox === "object" && srv.options.outbox) || {}, customOpts || {} ); config.addCAPOutboxEventBase(srv.name, outboxOpts); const specificSettings = config.getCdsOutboxEventSpecificConfig(srv.name, req.event); if (specificSettings) { outboxOpts = config.addCAPOutboxEventSpecificAction(srv.name, req.event); } if (["persistent-outbox", "persistent-queue"].includes(outboxOpts.kind)) { await _mapToEventAndPublish(context, srv.name, req, !!specificSettings); return; } context.on("succeeded", async () => { try { if (req.reply) { await originalSrv.send(req); } else { await originalSrv.emit(req); } } catch (err) { logger.error("In memory processing failed", { event: req.event, cause: err }); if (isUnrecoverable(originalSrv, err) && outboxOpts.crashOnError !== false) { cds.exit(1); } } }); }; return outboxedSrv; } function unboxed(srv) { return srv[UNBOXED] || srv; } const _mapToEventAndPublish = async (context, name, req, actionSpecific) => { const eventQueueSpecificValues = {}; for (const header in req.headers ?? {}) { for (const field of EVENT_QUEUE_SPECIFIC_FIELDS) { if (header.toLocaleLowerCase() === `x-eventqueue-${field.toLocaleLowerCase()}`) { eventQueueSpecificValues[field] = req.headers[header]; delete req.headers[header]; break; } } } const event = { contextUser: context.user.id, ...(req._fromSend || (req.reply && { _fromSend: true })), // send or emit ...(req.inbound && { inbound: req.inbound }), ...(req.event && { event: req.event }), ...(req.data && { data: req.data }), ...(req.headers && { headers: req.headers }), ...(req.query && { query: req.query }), }; await publishEvent(cds.tx(context), { type: CDS_EVENT_TYPE, subType: actionSpecific ? [name, req.event].join(".") : name, payload: JSON.stringify(event), ...eventQueueSpecificValues, }); }; const isUnrecoverable = (service, error) => { let unrecoverable = service.isUnrecoverableError && service.isUnrecoverableError(error); if (unrecoverable === undefined) { unrecoverable = error.unrecoverable; } return unrecoverable || isStandardError(error); }; const isStandardError = (err) => { return ( err instanceof TypeError || err instanceof ReferenceError || err instanceof SyntaxError || err instanceof RangeError || err instanceof URIError ); }; module.exports = { outboxed, unboxed, };