@cap-js-community/event-queue 1.10.0-beta.5 → 1.10.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js-community/event-queue",
3
- "version": "1.10.0-beta.5",
3
+ "version": "1.10.0",
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",
@@ -44,17 +44,17 @@
44
44
  "node": ">=18"
45
45
  },
46
46
  "dependencies": {
47
- "@sap/xssec": "^4.5.0",
48
- "cron-parser": "^5.0.0",
47
+ "@sap/xssec": "^4.6.0",
48
+ "cron-parser": "^5.1.0",
49
49
  "redis": "^4.7.0",
50
50
  "verror": "^1.10.1",
51
- "yaml": "^2.6.1"
51
+ "yaml": "^2.7.1"
52
52
  },
53
53
  "devDependencies": {
54
- "@cap-js/cds-test": "^0.2.0",
55
- "@cap-js/hana": "^1.7.0",
56
- "@cap-js/sqlite": "^1.9.0",
57
- "@sap/cds": "^8.8.0",
54
+ "@cap-js/cds-test": "^0.3.0",
55
+ "@cap-js/hana": "^1.8.0",
56
+ "@cap-js/sqlite": "^1.10.0",
57
+ "@sap/cds": "^8.9.0",
58
58
  "@sap/cds-dk": "^8.8.0",
59
59
  "eslint": "^8.57.0",
60
60
  "eslint-config-prettier": "^9.1.0",
package/src/config.js CHANGED
@@ -153,18 +153,50 @@ class Config {
153
153
  return !!this.#env.redisRequires?.credentials;
154
154
  }
155
155
 
156
+ #parseRegexOrString(str) {
157
+ const regexLiteralPattern = /^\/((?:\\.|[^\\/])*)\/([gimsuy]*)$/;
158
+ const match = str.match(regexLiteralPattern);
159
+
160
+ if (match) {
161
+ try {
162
+ return { type: "regex", value: new RegExp(match[1], match[2]) };
163
+ } catch {
164
+ return { type: "string", value: str };
165
+ }
166
+ }
167
+ return { type: "string", value: str };
168
+ }
169
+
156
170
  shouldBeProcessedInThisApplication(type, subType) {
157
171
  const config = this.#eventMap[this.generateKey(type, subType)];
158
172
  const appNameConfig = config._appNameMap;
159
173
  const appInstanceConfig = config._appInstancesMap;
174
+ let result = true;
160
175
  if (!appNameConfig && !appInstanceConfig) {
161
- return true;
176
+ return result;
162
177
  }
163
178
 
164
179
  if (appNameConfig) {
165
- const shouldBeProcessedBasedOnAppName = appNameConfig[this.#env.applicationName];
166
- if (!shouldBeProcessedBasedOnAppName) {
167
- return false;
180
+ if (config._appNameContainsRegex) {
181
+ for (const configKey in appNameConfig) {
182
+ const config = appNameConfig[configKey];
183
+ if (config.type === "regex") {
184
+ result = config.value.test(this.#env.applicationName);
185
+ } else {
186
+ const shouldBeProcessedBasedOnAppName = appNameConfig[this.#env.applicationName];
187
+ if (!shouldBeProcessedBasedOnAppName) {
188
+ result = config.value === this.#env.applicationName;
189
+ }
190
+ }
191
+ if (result) {
192
+ break;
193
+ }
194
+ }
195
+ } else {
196
+ const shouldBeProcessedBasedOnAppName = appNameConfig[this.#env.applicationName];
197
+ if (!shouldBeProcessedBasedOnAppName) {
198
+ return false;
199
+ }
168
200
  }
169
201
  }
170
202
 
@@ -175,7 +207,7 @@ class Config {
175
207
  }
176
208
  }
177
209
 
178
- return true;
210
+ return result;
179
211
  }
180
212
 
181
213
  checkRedisEnabled() {
@@ -530,7 +562,12 @@ class Config {
530
562
  }
531
563
 
532
564
  #basicEventTransformationAfterValidate(event) {
533
- event._appNameMap = event.appNames ? Object.fromEntries(new Map(event.appNames.map((a) => [a, true]))) : null;
565
+ event._appNameMap = event.appNames
566
+ ? Object.fromEntries(new Map(event.appNames.map((a) => [a, this.#parseRegexOrString(a)])))
567
+ : null;
568
+ event._appNameContainsRegex = event.appNames
569
+ ? event.appNames.some((appName) => this.#parseRegexOrString(appName).type === "regex")
570
+ : null;
534
571
  event._appInstancesMap = event.appInstances
535
572
  ? Object.fromEntries(new Map(event.appInstances.map((a) => [a, true])))
536
573
  : null;
@@ -9,6 +9,12 @@ const config = require("../config");
9
9
 
10
10
  const COMPONENT_NAME = "/eventQueue/outbox/generic";
11
11
 
12
+ const EVENT_QUEUE_ACTIONS = {
13
+ EXCEEDED: "eventQueueRetriesExceeded",
14
+ CLUSTER: "eventQueueCluster",
15
+ CHECK_AND_ADJUST: "eventQueueCheckAndAdjustPayload",
16
+ };
17
+
12
18
  class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
13
19
  constructor(context, eventType, eventSubType, config) {
14
20
  super(context, eventType, eventSubType, config);
@@ -21,7 +27,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
21
27
  this.__srvUnboxed = cds.unboxed(this.__srv);
22
28
  const { handlers, clusterRelevant, specificClusterRelevant } = this.__srvUnboxed.handlers.on.reduce(
23
29
  (result, handler) => {
24
- if (handler.on.startsWith("clusterQueueEntries")) {
30
+ if (handler.on.startsWith(EVENT_QUEUE_ACTIONS.CLUSTER)) {
25
31
  if (handler.on.split(".").length === 2) {
26
32
  result.specificClusterRelevant = true;
27
33
  } else {
@@ -41,8 +47,6 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
41
47
  return await super.getQueueEntriesAndSetToInProgress();
42
48
  }
43
49
 
44
- // document structure is a map of { key: { queueEntries: [], payload: {} }
45
- // TODO: document that clusterQueueEntries is now async!!!
46
50
  async clusterQueueEntries(queueEntriesWithPayloadMap) {
47
51
  if (!this.__genericClusterRelevantAndAvailable && !this.__specificClusterRelevantAndAvailable) {
48
52
  return super.clusterQueueEntries(queueEntriesWithPayloadMap);
@@ -58,7 +62,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
58
62
  } else {
59
63
  for (const actionName in genericClusterEvents) {
60
64
  const msg = new cds.Request({
61
- event: `clusterQueueEntries`,
65
+ event: EVENT_QUEUE_ACTIONS.CLUSTER,
62
66
  user: this.context.user,
63
67
  eventQueue: {
64
68
  processor: this,
@@ -67,7 +71,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
67
71
  clusterByEventProperty: (propertyName, cb) =>
68
72
  this.#clusterByEventProperty(actionName, genericClusterEvents[actionName], propertyName, cb),
69
73
  clusterByDataProperty: (propertyName, cb) =>
70
- this.#clusterByDataProperty(actionName, specificClusterEvents[actionName], propertyName, cb),
74
+ this.#clusterByDataProperty(actionName, genericClusterEvents[actionName], propertyName, cb),
71
75
  },
72
76
  });
73
77
  const clusterResult = await this.__srvUnboxed.tx(this.context).send(msg);
@@ -89,7 +93,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
89
93
 
90
94
  for (const actionName in specificClusterEvents) {
91
95
  const msg = new cds.Request({
92
- event: `clusterQueueEntries.${actionName}`,
96
+ event: `${EVENT_QUEUE_ACTIONS.CLUSTER}.${actionName}`,
93
97
  user: this.context.user,
94
98
  eventQueue: {
95
99
  processor: this,
@@ -249,13 +253,13 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
249
253
  }
250
254
 
251
255
  #hasEventSpecificClusterHandler(queueEntry) {
252
- return !!this.__onHandlers[["clusterQueueEntries", queueEntry.payload.event].join(".")];
256
+ return !!this.__onHandlers[[EVENT_QUEUE_ACTIONS.CLUSTER, queueEntry.payload.event].join(".")];
253
257
  }
254
258
 
255
259
  async checkEventAndGeneratePayload(queueEntry) {
256
260
  const payload = await super.checkEventAndGeneratePayload(queueEntry);
257
261
  const { event } = payload;
258
- const handlerName = this.#checkHandlerExists("checkEventAndGeneratePayload", event);
262
+ const handlerName = this.#checkHandlerExists(EVENT_QUEUE_ACTIONS.CHECK_AND_ADJUST, event);
259
263
  if (!handlerName) {
260
264
  return payload;
261
265
  }
@@ -276,7 +280,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
276
280
 
277
281
  async hookForExceededEvents(exceededEvent) {
278
282
  const { event } = exceededEvent.payload;
279
- const handlerName = this.#checkHandlerExists("hookForExceededEvents", event);
283
+ const handlerName = this.#checkHandlerExists(EVENT_QUEUE_ACTIONS.EXCEEDED, event);
280
284
  if (!handlerName) {
281
285
  return await super.hookForExceededEvents(exceededEvent);
282
286
  }
@@ -63,7 +63,7 @@ const publishEvent = async (
63
63
  event.context = JSON.stringify({ traceContext: openTelemetry.getCurrentTraceContext() });
64
64
  }
65
65
 
66
- if (eventConfig.timeBucket && event.startAfter !== undefined) {
66
+ if (eventConfig.timeBucket && !(startAfter in event)) {
67
67
  event.startAfter = CronExpressionParser.parse(eventConfig.timeBucket).next().toISOString();
68
68
  }
69
69
  }