@cap-js-community/event-queue 2.1.0-beta.1 → 2.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/db/Event.cds CHANGED
@@ -24,6 +24,6 @@ entity Event: cuid {
24
24
  createdAt: Timestamp @cds.on.insert : $now;
25
25
  startAfter: Timestamp;
26
26
  context: LargeString;
27
- error: String;
27
+ error: LargeString;
28
28
  namespace: String default 'default';
29
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js-community/event-queue",
3
- "version": "2.1.0-beta.1",
3
+ "version": "2.1.0-beta.3",
4
4
  "description": "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.",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -504,10 +504,16 @@ class EventQueueProcessorBase {
504
504
  if (!data.startAfter && [EventProcessingStatus.Error, EventProcessingStatus.Open].includes(data.status)) {
505
505
  data.startAfter = new Date(
506
506
  Date.now() +
507
- (EventProcessingStatus.Error ? this.#eventConfig.retryFailedAfter : this.#eventConfig.retryOpenAfter)
507
+ (data.status === EventProcessingStatus.Error
508
+ ? this.#eventConfig.retryFailedAfter
509
+ : this.#eventConfig.retryOpenAfter)
508
510
  );
509
511
  }
510
512
 
513
+ if (data.status === EventProcessingStatus.Open && !("attempts" in data)) {
514
+ data.attempts = { "-=": 1 };
515
+ }
516
+
511
517
  if (data.startAfter) {
512
518
  this.#eventSchedulerInstance.scheduleEvent(
513
519
  this.__context.tenant,
package/src/config.js CHANGED
@@ -64,7 +64,7 @@ const ALLOWED_EVENT_OPTIONS_AD_HOC = [
64
64
  "checkForNextChunk",
65
65
  "retryFailedAfter",
66
66
  "propagateHeaders",
67
- "propagatedContextProperties",
67
+ "propagateContextProperties",
68
68
  "retryOpenAfter",
69
69
  "multiInstanceProcessing",
70
70
  "kind",
@@ -518,7 +518,7 @@ class Config {
518
518
  ? Object.fromEntries(new Map(event.appInstances.map((a) => [a, true])))
519
519
  : null;
520
520
  event.propagateHeaders = event.propagateHeaders ?? [];
521
- event.propagatedContextProperties = event.propagatedContextProperties ?? [];
521
+ event.propagateContextProperties = event.propagateContextProperties ?? [];
522
522
  event.retryFailedAfter = event.retryFailedAfter ?? DEFAULT_RETRY_AFTER;
523
523
  event.retryOpenAfter = event.retryOpenAfter ?? DEFAULT_RETRY_AFTER;
524
524
  }
@@ -64,7 +64,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
64
64
  }
65
65
  } else {
66
66
  for (const actionName in genericClusterEvents) {
67
- const reg = new cds.Request({
67
+ const req = new cds.Request({
68
68
  event: EVENT_QUEUE_ACTIONS.CLUSTER,
69
69
  user: this.context.user,
70
70
  eventQueue: {
@@ -77,14 +77,14 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
77
77
  this.#clusterByDataProperty(actionName, genericClusterEvents[actionName], propertyName, cb),
78
78
  },
79
79
  });
80
- const clusterResult = await this.__srvUnboxed.tx(this.context).send(reg);
80
+ const clusterResult = await this.__srvUnboxed.tx(this.context).send(req);
81
81
  if (this.#validateCluster(clusterResult)) {
82
82
  Object.assign(clusterMap, clusterResult);
83
83
  } else {
84
84
  this.logger.error(
85
85
  "cluster result of handler is not valid. Check the documentation for the expected structure. Continuing without clustering!",
86
86
  {
87
- handler: reg.event,
87
+ handler: req.event,
88
88
  clusterResult: JSON.stringify(clusterResult),
89
89
  }
90
90
  );
@@ -95,7 +95,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
95
95
  }
96
96
 
97
97
  for (const actionName in specificClusterEvents) {
98
- const reg = new cds.Request({
98
+ const req = new cds.Request({
99
99
  event: `${EVENT_QUEUE_ACTIONS.CLUSTER}.${actionName}`,
100
100
  user: this.context.user,
101
101
  eventQueue: {
@@ -108,14 +108,14 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
108
108
  this.#clusterByDataProperty(actionName, specificClusterEvents[actionName], propertyName, cb),
109
109
  },
110
110
  });
111
- const clusterResult = await this.__srvUnboxed.tx(this.context).send(reg);
111
+ const clusterResult = await this.__srvUnboxed.tx(this.context).send(req);
112
112
  if (this.#validateCluster(clusterResult)) {
113
113
  Object.assign(clusterMap, clusterResult);
114
114
  } else {
115
115
  this.logger.error(
116
116
  "cluster result of handler is not valid. Check the documentation for the expected structure. Continuing without clustering!",
117
117
  {
118
- handler: reg.event,
118
+ handler: req.event,
119
119
  clusterResult: JSON.stringify(clusterResult),
120
120
  }
121
121
  );
@@ -267,12 +267,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
267
267
  return payload;
268
268
  }
269
269
 
270
- const { reg, userId } = this.#buildDispatchData(this.context, payload, {
270
+ const { req, userId } = this.#buildDispatchData(payload, {
271
271
  queueEntries: [queueEntry],
272
272
  });
273
- reg.event = handlerName;
274
- await this.#setContextUser(this.context, userId, reg);
275
- const data = await this.__srvUnboxed.tx(this.context).send(reg);
273
+ req.event = handlerName;
274
+ await this.#setContextUser(this.context, userId, req);
275
+ const data = await this.__srvUnboxed.tx(this.context).send(req);
276
276
  if (data) {
277
277
  payload.data = data;
278
278
  return payload;
@@ -288,12 +288,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
288
288
  return await super.hookForExceededEvents(exceededEvent);
289
289
  }
290
290
 
291
- const { reg, userId } = this.#buildDispatchData(this.context, exceededEvent.payload, {
291
+ const { req, userId } = this.#buildDispatchData(exceededEvent.payload, {
292
292
  queueEntries: [exceededEvent],
293
293
  });
294
- await this.#setContextUser(this.context, userId, reg);
295
- reg.event = handlerName;
296
- await this.__srvUnboxed.tx(this.context).send(reg);
294
+ await this.#setContextUser(this.context, userId, req);
295
+ req.event = handlerName;
296
+ await this.__srvUnboxed.tx(this.context).send(req);
297
297
  }
298
298
 
299
299
  // NOTE: Currently not exposed to CAP service; we wait for a valid use case
@@ -326,40 +326,47 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
326
326
 
327
327
  async processPeriodicEvent(processContext, key, queueEntry) {
328
328
  const { actionName } = config.normalizeSubType(this.eventType, this.eventSubType);
329
- const reg = new cds.Event({ event: actionName, eventQueue: { processor: this, key, queueEntries: [queueEntry] } });
330
- await this.#setContextUser(processContext, config.userId, reg);
331
- await this.__srvUnboxed.tx(processContext).emit(reg);
329
+ const req = new cds.Event({ event: actionName, eventQueue: { processor: this, key, queueEntries: [queueEntry] } });
330
+ await this.#setContextUser(processContext, config.userId, req);
331
+ await this.__srvUnboxed.tx(processContext).emit(req);
332
332
  }
333
333
 
334
- #buildDispatchData(context, payload, { key, queueEntries } = {}) {
334
+ #buildDispatchData(payload, { key, queueEntries } = {}) {
335
335
  const { useEventQueueUser } = this.eventConfig;
336
336
  const userId = useEventQueueUser ? config.userId : payload.contextUser;
337
- const reg = payload._fromSend ? new cds.Request(payload) : new cds.Event(payload);
337
+ const req = payload._fromSend ? new cds.Request(payload) : new cds.Event(payload);
338
338
  const invocationFn = payload._fromSend ? "send" : "emit";
339
- delete reg._fromSend;
340
- delete reg.contextUser;
341
- reg.eventQueue = { processor: this, key, queueEntries, payload };
342
- return { reg, userId, invocationFn };
339
+ delete req._fromSend;
340
+ delete req.contextUser;
341
+ req.eventQueue = { processor: this, key, queueEntries, payload };
342
+
343
+ if (this.eventConfig.propagateContextProperties?.length && this.transactionMode === "isolated" && cds.context) {
344
+ for (const prop of this.eventConfig.propagateContextProperties) {
345
+ req[prop] && (cds.context[prop] = req[prop]);
346
+ }
347
+ }
348
+
349
+ return { req, userId, invocationFn };
343
350
  }
344
351
 
345
- async #setContextUser(context, userId, reg) {
352
+ async #setContextUser(context, userId, req) {
346
353
  const authInfo = await common.getAuthContext(context.tenant);
347
354
  context.user = new cds.User.Privileged({
348
355
  id: userId,
349
356
  authInfo,
350
357
  tokenInfo: authInfo?.token,
351
358
  });
352
- if (reg) {
353
- reg.user = context.user;
359
+ if (req) {
360
+ req.user = context.user;
354
361
  }
355
362
  }
356
363
 
357
364
  async processEvent(processContext, key, queueEntries, payload) {
358
365
  let statusTuple;
359
- const { userId, invocationFn, reg } = this.#buildDispatchData(processContext, payload, { key, queueEntries });
366
+ const { userId, invocationFn, req } = this.#buildDispatchData(payload, { key, queueEntries });
360
367
  try {
361
- await this.#setContextUser(processContext, userId, reg);
362
- const result = await this.__srvUnboxed.tx(processContext)[invocationFn](reg);
368
+ await this.#setContextUser(processContext, userId, req);
369
+ const result = await this.__srvUnboxed.tx(processContext)[invocationFn](req);
363
370
  statusTuple = this.#determineResultStatus(result, queueEntries);
364
371
  } catch (err) {
365
372
  this.logger.error("error processing outboxed service call", err, {
@@ -374,7 +381,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
374
381
  ]);
375
382
  }
376
383
 
377
- await this.#publishFollowupEvents(processContext, reg, statusTuple);
384
+ await this.#publishFollowupEvents(processContext, req, statusTuple);
378
385
  return statusTuple;
379
386
  }
380
387
 
@@ -11,6 +11,8 @@ const CDS_EVENT_TYPE = "CAP_OUTBOX";
11
11
  const COMPONENT_NAME = "/eventQueue/eventQueueAsOutbox";
12
12
  const EVENT_QUEUE_SPECIFIC_FIELDS = ["startAfter", "referenceEntity", "referenceEntityKey", "namespace"];
13
13
 
14
+ const TO_COPY = ["inbound", "event", "data", "queue", "results", "method", "path", "params", "entity", "service"];
15
+
14
16
  function outboxed(srv, customOpts) {
15
17
  if (!(new.target || customOpts)) {
16
18
  const former = srv[OUTBOXED];
@@ -43,7 +45,7 @@ function outboxed(srv, customOpts) {
43
45
 
44
46
  const outboxOpts = config.getEventConfig(CDS_EVENT_TYPE, subType, srvConfig.namespace);
45
47
  const eventHeaders = getPropagatedHeaders(outboxOpts, req);
46
- const contextProperties = getPropagatedContextProperties(outboxOpts, req);
48
+ const contextProperties = getPropagateContextProperties(outboxOpts, req);
47
49
  if (["persistent-outbox", "persistent-queue"].includes(outboxOpts.kind)) {
48
50
  await _mapToEventAndPublish(req, srvConfig.namespace, subType, eventHeaders, contextProperties);
49
51
  return;
@@ -81,8 +83,8 @@ const getPropagatedHeaders = (config, req) => {
81
83
  return Object.assign(propagateHeaders, req.headers);
82
84
  };
83
85
 
84
- const getPropagatedContextProperties = (config, req) => {
85
- return config.propagatedContextProperties.reduce((properties, name) => {
86
+ const getPropagateContextProperties = (config, req) => {
87
+ return config.propagateContextProperties.reduce((properties, name) => {
86
88
  if (name in req.tx.context) {
87
89
  properties[name] = req.tx.context[name];
88
90
  }
@@ -102,18 +104,27 @@ const _mapToEventAndPublish = async (req, namespace, subType, eventHeaders, cont
102
104
  }
103
105
  }
104
106
  }
105
-
106
107
  const event = {
107
108
  contextUser: context.user.id,
108
109
  ...(req._fromSend || (req.reply && { _fromSend: true })), // send or emit
109
- ...(req.inbound && { inbound: req.inbound }),
110
- ...(req.event && { event: req.event }),
111
- ...(req.data && { data: req.data }),
112
110
  ...(eventHeaders && { headers: eventHeaders }),
113
- ...(req.query && { query: req.query }),
114
111
  ...(Object.keys(contextProperties).length && { ...contextProperties }),
115
112
  };
116
113
 
114
+ for (const prop of TO_COPY) {
115
+ if (req[prop]) {
116
+ event[prop] = req[prop];
117
+ }
118
+ }
119
+
120
+ if (req.query) {
121
+ event.query = typeof req.query.flat === "function" ? req.query.flat() : req.query;
122
+ delete event.query._target;
123
+ delete event.query.__target;
124
+ delete event.query.target;
125
+ delete event.data; // `req.data` should be a getter to whatever is in `req.query`
126
+ }
127
+
117
128
  await publishEvent(
118
129
  cds.tx(context),
119
130
  {