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.

140 lines (122 loc) 4.34 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", "namespace"]; function outboxed(srv, customOpts) { if (!(new.target || customOpts)) { const former = srv[OUTBOXED]; if (former) { return former; } } const logger = cds.log(COMPONENT_NAME); 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; const hasSpecificSettings = !!config.getCdsOutboxEventSpecificConfig(srv.name, req.event); const subType = hasSpecificSettings ? [srv.name, req.event].join(".") : srv.name; let srvConfig = config.findBaseCAPServiceWithoutNamespace(subType); // NOTE: service is outboxed without config in cds.env.requires[srv] if (!srvConfig) { config.addCAPServiceWithoutEnvConfig(subType, srv, customOpts); srvConfig = config.findBaseCAPServiceWithoutNamespace(subType); } const outboxOpts = config.getEventConfig(CDS_EVENT_TYPE, subType, srvConfig.namespace); const eventHeaders = getPropagatedHeaders(outboxOpts, req); if (["persistent-outbox", "persistent-queue"].includes(outboxOpts.kind)) { await _mapToEventAndPublish(context, subType, req, eventHeaders, srvConfig.namespace); 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 getPropagatedHeaders = (config, req) => { const propagateHeaders = config.propagateHeaders.reduce((headers, headerName) => { if (headerName in req.tx.context.headers) { headers[headerName] = req.tx.context.headers[headerName]; } return headers; }, {}); return Object.assign(propagateHeaders, req.headers); }; const _mapToEventAndPublish = async (context, subType, req, eventHeaders, namespace) => { 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 eventHeaders[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 }), ...(eventHeaders && { headers: eventHeaders }), ...(req.query && { query: req.query }), }; await publishEvent( cds.tx(context), { type: CDS_EVENT_TYPE, subType, payload: JSON.stringify(event), namespace: eventQueueSpecificValues.namespace ?? namespace, ...eventQueueSpecificValues, }, { allowNotExistingConfiguration: !!eventQueueSpecificValues.namespace } ); }; 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, };