@cap-js-community/event-queue 1.11.0-beta.2 → 1.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js-community/event-queue",
3
- "version": "1.11.0-beta.2",
3
+ "version": "1.11.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",
package/src/config.js CHANGED
@@ -28,6 +28,7 @@ const DEFAULT_CHECK_FOR_NEXT_CHUNK = true;
28
28
  const SUFFIX_PERIODIC = "_PERIODIC";
29
29
  const CAP_EVENT_TYPE = "CAP_OUTBOX";
30
30
  const CAP_PARALLEL_DEFAULT = 5;
31
+ const CAP_MAX_ATTEMPTS_DEFAULT = 5;
31
32
  const DELETE_TENANT_BLOCK_AFTER_MS = 5 * 60 * 1000;
32
33
  const PRIORITIES = Object.values(Priorities);
33
34
  const UTC_DEFAULT = false;
@@ -387,7 +388,7 @@ class Config {
387
388
  kind: config.kind ?? "persistent-outbox",
388
389
  selectMaxChunkSize: config.selectMaxChunkSize ?? config.chunkSize,
389
390
  parallelEventProcessing: config.parallelEventProcessing ?? (config.parallel && CAP_PARALLEL_DEFAULT),
390
- retryAttempts: config.retryAttempts ?? config.maxAttempts,
391
+ retryAttempts: config.retryAttempts ?? config.maxAttempts ?? CAP_MAX_ATTEMPTS_DEFAULT,
391
392
  ...config,
392
393
  });
393
394
  eventConfig.internalEvent = true;
@@ -61,7 +61,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
61
61
  }
62
62
  } else {
63
63
  for (const actionName in genericClusterEvents) {
64
- const msg = new cds.Request({
64
+ const reg = new cds.Request({
65
65
  event: EVENT_QUEUE_ACTIONS.CLUSTER,
66
66
  user: this.context.user,
67
67
  eventQueue: {
@@ -74,14 +74,14 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
74
74
  this.#clusterByDataProperty(actionName, genericClusterEvents[actionName], propertyName, cb),
75
75
  },
76
76
  });
77
- const clusterResult = await this.__srvUnboxed.tx(this.context).send(msg);
77
+ const clusterResult = await this.__srvUnboxed.tx(this.context).send(reg);
78
78
  if (this.#validateCluster(clusterResult)) {
79
79
  Object.assign(clusterMap, clusterResult);
80
80
  } else {
81
81
  this.logger.error(
82
82
  "cluster result of handler is not valid. Check the documentation for the expected structure. Continuing without clustering!",
83
83
  {
84
- handler: msg.event,
84
+ handler: reg.event,
85
85
  clusterResult: JSON.stringify(clusterResult),
86
86
  }
87
87
  );
@@ -92,7 +92,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
92
92
  }
93
93
 
94
94
  for (const actionName in specificClusterEvents) {
95
- const msg = new cds.Request({
95
+ const reg = new cds.Request({
96
96
  event: `${EVENT_QUEUE_ACTIONS.CLUSTER}.${actionName}`,
97
97
  user: this.context.user,
98
98
  eventQueue: {
@@ -105,14 +105,14 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
105
105
  this.#clusterByDataProperty(actionName, specificClusterEvents[actionName], propertyName, cb),
106
106
  },
107
107
  });
108
- const clusterResult = await this.__srvUnboxed.tx(this.context).send(msg);
108
+ const clusterResult = await this.__srvUnboxed.tx(this.context).send(reg);
109
109
  if (this.#validateCluster(clusterResult)) {
110
110
  Object.assign(clusterMap, clusterResult);
111
111
  } else {
112
112
  this.logger.error(
113
113
  "cluster result of handler is not valid. Check the documentation for the expected structure. Continuing without clustering!",
114
114
  {
115
- handler: msg.event,
115
+ handler: reg.event,
116
116
  clusterResult: JSON.stringify(clusterResult),
117
117
  }
118
118
  );
@@ -264,12 +264,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
264
264
  return payload;
265
265
  }
266
266
 
267
- const { msg, userId } = this.#buildDispatchData(this.context, payload, {
267
+ const { reg, userId } = this.#buildDispatchData(this.context, payload, {
268
268
  queueEntries: [queueEntry],
269
269
  });
270
- msg.event = handlerName;
271
- await this.#setContextUser(this.context, userId, msg);
272
- const data = await this.__srvUnboxed.tx(this.context).send(msg);
270
+ reg.event = handlerName;
271
+ await this.#setContextUser(this.context, userId, reg);
272
+ const data = await this.__srvUnboxed.tx(this.context).send(reg);
273
273
  if (data) {
274
274
  payload.data = data;
275
275
  return payload;
@@ -285,12 +285,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
285
285
  return await super.hookForExceededEvents(exceededEvent);
286
286
  }
287
287
 
288
- const { msg, userId } = this.#buildDispatchData(this.context, exceededEvent.payload, {
288
+ const { reg, userId } = this.#buildDispatchData(this.context, exceededEvent.payload, {
289
289
  queueEntries: [exceededEvent],
290
290
  });
291
- await this.#setContextUser(this.context, userId, msg);
292
- msg.event = handlerName;
293
- await this.__srvUnboxed.tx(this.context).send(msg);
291
+ await this.#setContextUser(this.context, userId, reg);
292
+ reg.event = handlerName;
293
+ await this.__srvUnboxed.tx(this.context).send(reg);
294
294
  }
295
295
 
296
296
  // NOTE: Currently not exposed to CAP service; we wait for a valid use case
@@ -310,37 +310,37 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
310
310
 
311
311
  async processPeriodicEvent(processContext, key, queueEntry) {
312
312
  const [, action] = this.eventSubType.split(".");
313
- const msg = new cds.Event({ event: action, eventQueue: { processor: this, key, queueEntries: [queueEntry] } });
314
- await this.#setContextUser(processContext, config.userId, msg);
315
- await this.__srvUnboxed.tx(processContext).emit(msg);
313
+ const reg = new cds.Event({ event: action, eventQueue: { processor: this, key, queueEntries: [queueEntry] } });
314
+ await this.#setContextUser(processContext, config.userId, reg);
315
+ await this.__srvUnboxed.tx(processContext).emit(reg);
316
316
  }
317
317
 
318
318
  #buildDispatchData(context, payload, { key, queueEntries } = {}) {
319
319
  const { useEventQueueUser } = this.eventConfig;
320
320
  const userId = useEventQueueUser ? config.userId : payload.contextUser;
321
- const msg = payload._fromSend ? new cds.Request(payload) : new cds.Event(payload);
321
+ const reg = payload._fromSend ? new cds.Request(payload) : new cds.Event(payload);
322
322
  const invocationFn = payload._fromSend ? "send" : "emit";
323
- delete msg._fromSend; // TODO: this changes the source object --> check after multiple invocations
324
- delete msg.contextUser;
325
- msg.eventQueue = { processor: this, key, queueEntries, payload };
326
- return { msg, userId, invocationFn };
323
+ delete reg._fromSend;
324
+ delete reg.contextUser;
325
+ reg.eventQueue = { processor: this, key, queueEntries, payload };
326
+ return { reg, userId, invocationFn };
327
327
  }
328
328
 
329
- async #setContextUser(context, userId, data) {
329
+ async #setContextUser(context, userId, reg) {
330
330
  context.user = new cds.User.Privileged({
331
331
  id: userId,
332
332
  tokenInfo: await common.getTokenInfo(this.baseContext.tenant),
333
333
  });
334
- if (data) {
335
- data.user = context.user;
334
+ if (reg) {
335
+ reg.user = context.user;
336
336
  }
337
337
  }
338
338
 
339
339
  async processEvent(processContext, key, queueEntries, payload) {
340
340
  try {
341
- const { userId, invocationFn, msg } = this.#buildDispatchData(processContext, payload, { key, queueEntries });
342
- await this.#setContextUser(processContext, userId, msg);
343
- const result = await this.__srvUnboxed.tx(processContext)[invocationFn](msg);
341
+ const { userId, invocationFn, reg } = this.#buildDispatchData(processContext, payload, { key, queueEntries });
342
+ await this.#setContextUser(processContext, userId, reg);
343
+ const result = await this.__srvUnboxed.tx(processContext)[invocationFn](reg);
344
344
  return this.#determineResultStatus(result, queueEntries);
345
345
  } catch (err) {
346
346
  this.logger.error("error processing outboxed service call", err, {
@@ -108,7 +108,7 @@ const _renewLockRedis = async (context, fullKey, expiryTime, { value = "true" }
108
108
 
109
109
  const _checkLockExistsRedis = async (context, fullKey) => {
110
110
  const client = await redis.createMainClientAndConnect(config.redisOptions);
111
- return await client.get(fullKey);
111
+ return await client.exists(fullKey);
112
112
  };
113
113
 
114
114
  const _checkLockExistsDb = async (context, fullKey) => {
@@ -191,39 +191,36 @@ const _generateKey = (context, tenantScoped, key) => {
191
191
  };
192
192
 
193
193
  const getAllLocksRedis = async () => {
194
- const client = await redis.createMainClientAndConnect(config.redisOptions);
195
- const batchSize = 500;
196
- const results = [];
197
- let pipeline = client.multi();
194
+ const clientOrCluster = await redis.createMainClientAndConnect(config.redisOptions);
198
195
  const output = [];
199
- let count = 0;
196
+ const results = [];
200
197
 
201
- // NOTE: use SCAN because KEYS is not supported for cluster clients
202
- for await (const key of client.scanIterator({ MATCH: "EVENT*", COUNT: 1000 })) {
203
- const [, tenant, guidOrType, subType] = key.split("##");
204
- if (!subType) {
205
- continue;
206
- }
198
+ let clients;
199
+ if (redis.isClusterMode()) {
200
+ clients = clientOrCluster.masters.map((master) => master.client);
201
+ } else {
202
+ clients = [clientOrCluster];
203
+ }
207
204
 
208
- output.push({
209
- tenant: tenant,
210
- type: guidOrType,
211
- subType: subType,
212
- });
213
- pipeline.ttl(key).get(key);
214
- count++;
205
+ // NOTE: use SCAN because KEYS is not supported for cluster clients
206
+ for (const client of clients) {
207
+ for await (const key of client.scanIterator({ MATCH: "EVENT*", COUNT: 1000 })) {
208
+ const [, tenant, guidOrType, subType] = key.split("##");
209
+ if (!subType) {
210
+ continue;
211
+ }
215
212
 
216
- if (count >= batchSize) {
213
+ const pipeline = client.multi();
214
+ output.push({
215
+ tenant: tenant,
216
+ type: guidOrType,
217
+ subType: subType,
218
+ });
219
+ pipeline.ttl(key).get(key);
217
220
  const replies = await pipeline.exec();
218
221
  results.push(...replies);
219
- pipeline = client.multi();
220
- count = 0;
221
222
  }
222
223
  }
223
- if (count > 0) {
224
- const replies = await pipeline.exec();
225
- results.push(...replies);
226
- }
227
224
 
228
225
  let counter = 0;
229
226
  for (const row of output) {
@@ -178,6 +178,15 @@ const connectionCheck = async (options) => {
178
178
  });
179
179
  };
180
180
 
181
+ const isClusterMode = () => {
182
+ if (!("__clusterMode" in isClusterMode)) {
183
+ const env = getEnvInstance();
184
+ const { credentials } = env.redisRequires;
185
+ isClusterMode.__clusterMode = credentials.cluster_mode;
186
+ }
187
+ return isClusterMode.__clusterMode;
188
+ };
189
+
181
190
  module.exports = {
182
191
  createClientAndConnect,
183
192
  createMainClientAndConnect,
@@ -186,4 +195,5 @@ module.exports = {
186
195
  closeMainClient,
187
196
  closeSubscribeClient,
188
197
  connectionCheck,
198
+ isClusterMode,
189
199
  };