@cap-js-community/event-queue 1.8.5 → 1.8.7

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.8.5",
3
+ "version": "1.8.7",
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
@@ -17,6 +17,7 @@ const COMPONENT_NAME = "/eventQueue/config";
17
17
  const MIN_INTERVAL_SEC = 10;
18
18
  const DEFAULT_LOAD = 1;
19
19
  const DEFAULT_PRIORITY = Priorities.Medium;
20
+ const DEFAULT_INCREASE_PRIORITY = true;
20
21
  const SUFFIX_PERIODIC = "_PERIODIC";
21
22
  const COMMAND_BLOCK = "EVENT_QUEUE_EVENT_BLOCK";
22
23
  const COMMAND_UNBLOCK = "EVENT_QUEUE_EVENT_UNBLOCK";
@@ -317,6 +318,7 @@ class Config {
317
318
  retryFailedAfter: config.retryFailedAfter,
318
319
  priority: config.priority,
319
320
  multiInstanceProcessing: config.multiInstanceProcessing,
321
+ increasePriorityOverTime: config.increasePriorityOverTime,
320
322
  internalEvent: true,
321
323
  };
322
324
 
@@ -364,7 +366,6 @@ class Config {
364
366
  return result;
365
367
  }, {});
366
368
  this.#eventMap = config.periodicEvents.reduce((result, event) => {
367
- event.priority = event.priority ?? DEFAULT_PRIORITY;
368
369
  event.type = `${event.type}${SUFFIX_PERIODIC}`;
369
370
  event.isPeriodic = true;
370
371
  this.#basicEventTransformation(event);
@@ -378,6 +379,7 @@ class Config {
378
379
  #basicEventTransformation(event) {
379
380
  event.load = event.load ?? DEFAULT_LOAD;
380
381
  event.priority = event.priority ?? DEFAULT_PRIORITY;
382
+ event.increasePriorityOverTime = event.increasePriorityOverTime ?? DEFAULT_INCREASE_PRIORITY;
381
383
  }
382
384
 
383
385
  #basicEventTransformationAfterValidate(event) {
package/src/index.d.ts CHANGED
@@ -269,7 +269,13 @@ export const workerQueue: WorkerQueue;
269
269
  declare class WorkerQueue {
270
270
  constructor(concurrency: number);
271
271
 
272
- addToQueue(load: number, label: string, priority?: Priorities, cb?: () => any): Promise<any>;
272
+ addToQueue(
273
+ load: number,
274
+ label: string,
275
+ priority: Priorities,
276
+ increasePriorityOverTime: boolean,
277
+ cb: () => any
278
+ ): Promise<any>;
273
279
 
274
280
  _executeFunction(
275
281
  load: number,
@@ -34,7 +34,8 @@ const publishEvent = async (tx, events, { skipBroadcast = false, skipInsertEvent
34
34
  throw EventQueueError.notInitialized();
35
35
  }
36
36
  const eventsForProcessing = Array.isArray(events) ? events : [events];
37
- for (const { type, subType, startAfter } of eventsForProcessing) {
37
+ for (const event of eventsForProcessing) {
38
+ const { type, subType, startAfter } = event;
38
39
  const eventConfig = config.getEventConfig(type, subType);
39
40
  if (!eventConfig) {
40
41
  throw EventQueueError.unknownEventType(type, subType);
@@ -46,8 +47,11 @@ const publishEvent = async (tx, events, { skipBroadcast = false, skipInsertEvent
46
47
  if (eventConfig.isPeriodic) {
47
48
  throw EventQueueError.manuelPeriodicEventInsert(type, subType);
48
49
  }
49
- }
50
50
 
51
+ if (typeof event.payload !== "string") {
52
+ event.payload = JSON.stringify(event.payload);
53
+ }
54
+ }
51
55
  if (config.insertEventsBeforeCommit && !skipInsertEventsBeforeCommit) {
52
56
  _registerHandlerAndAddEvents(tx, events);
53
57
  } else {
@@ -194,33 +194,39 @@ const _executeEventsAllTenants = async (tenantIds, runId) => {
194
194
  events.map(async (openEvent) => {
195
195
  const eventConfig = config.getEventConfig(openEvent.type, openEvent.subType);
196
196
  const label = `${eventConfig.type}_${eventConfig.subType}`;
197
- return await WorkerQueue.instance.addToQueue(eventConfig.load, label, eventConfig.priority, async () => {
198
- return await cds.tx(tenantContext, async ({ context }) => {
199
- await trace(
200
- context,
201
- label,
202
- async () => {
203
- try {
204
- const lockId = `${runId}_${label}`;
205
- const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
206
- expiryTime: eventQueueConfig.runInterval * 0.95,
207
- });
208
- if (!couldAcquireLock) {
209
- return;
197
+ return await WorkerQueue.instance.addToQueue(
198
+ eventConfig.load,
199
+ label,
200
+ eventConfig.priority,
201
+ eventConfig.increasePriorityOverTime,
202
+ async () => {
203
+ return await cds.tx(tenantContext, async ({ context }) => {
204
+ await trace(
205
+ context,
206
+ label,
207
+ async () => {
208
+ try {
209
+ const lockId = `${runId}_${label}`;
210
+ const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
211
+ expiryTime: eventQueueConfig.runInterval * 0.95,
212
+ });
213
+ if (!couldAcquireLock) {
214
+ return;
215
+ }
216
+ await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, {
217
+ skipWorkerPool: true,
218
+ });
219
+ } catch (err) {
220
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
221
+ tenantId,
222
+ });
210
223
  }
211
- await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, {
212
- skipWorkerPool: true,
213
- });
214
- } catch (err) {
215
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
216
- tenantId,
217
- });
218
- }
219
- },
220
- { newRootSpan: true }
221
- );
222
- });
223
- });
224
+ },
225
+ { newRootSpan: true }
226
+ );
227
+ });
228
+ }
229
+ );
224
230
  })
225
231
  );
226
232
  }
@@ -275,33 +281,39 @@ const _singleTenantDb = async () => {
275
281
  events.map(async (openEvent) => {
276
282
  const eventConfig = config.getEventConfig(openEvent.type, openEvent.subType);
277
283
  const label = `${eventConfig.type}_${eventConfig.subType}`;
278
- return await WorkerQueue.instance.addToQueue(eventConfig.load, label, eventConfig.priority, async () => {
279
- return await cds.tx({}, async ({ context }) => {
280
- await trace(
281
- context,
282
- label,
283
- async () => {
284
- try {
285
- const lockId = `${label}`;
286
- const couldAcquireLock = eventConfig.multiInstanceProcessing
287
- ? true
288
- : await distributedLock.acquireLock(context, lockId, {
289
- expiryTime: eventQueueConfig.runInterval * 0.95,
290
- });
291
- if (!couldAcquireLock) {
292
- return;
284
+ return await WorkerQueue.instance.addToQueue(
285
+ eventConfig.load,
286
+ label,
287
+ eventConfig.priority,
288
+ eventConfig.increasePriorityOverTime,
289
+ async () => {
290
+ return await cds.tx({}, async ({ context }) => {
291
+ await trace(
292
+ context,
293
+ label,
294
+ async () => {
295
+ try {
296
+ const lockId = `${label}`;
297
+ const couldAcquireLock = eventConfig.multiInstanceProcessing
298
+ ? true
299
+ : await distributedLock.acquireLock(context, lockId, {
300
+ expiryTime: eventQueueConfig.runInterval * 0.95,
301
+ });
302
+ if (!couldAcquireLock) {
303
+ return;
304
+ }
305
+ await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, {
306
+ skipWorkerPool: true,
307
+ });
308
+ } catch (err) {
309
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed");
293
310
  }
294
- await runEventCombinationForTenant(context, eventConfig.type, eventConfig.subType, {
295
- skipWorkerPool: true,
296
- });
297
- } catch (err) {
298
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed");
299
- }
300
- },
301
- { newRootSpan: true }
302
- );
303
- });
304
- });
311
+ },
312
+ { newRootSpan: true }
313
+ );
314
+ });
315
+ }
316
+ );
305
317
  })
306
318
  );
307
319
  };
@@ -23,6 +23,7 @@ const runEventCombinationForTenant = async (context, type, subType, { skipWorker
23
23
  eventConfig.load,
24
24
  label,
25
25
  eventConfig.priority,
26
+ eventConfig.increasePriorityOverTime,
26
27
  AsyncResource.bind(async () => {
27
28
  const _exec = async () => {
28
29
  if (!eventConfig.multiInstanceProcessing && lockId) {
@@ -52,7 +52,7 @@ class WorkerQueue {
52
52
  runner.run(this.#adjustPriority.bind(this));
53
53
  }
54
54
 
55
- addToQueue(load, label, priority = Priorities.Medium, cb) {
55
+ addToQueue(load, label, priority = Priorities.Medium, increasePriorityOverTime, cb) {
56
56
  if (load > this.#concurrencyLimit) {
57
57
  throw EventQueueError.loadHigherThanLimit(load, label);
58
58
  }
@@ -63,7 +63,7 @@ class WorkerQueue {
63
63
 
64
64
  const startTime = process.hrtime.bigint();
65
65
  const p = new Promise((resolve, reject) => {
66
- this.#queue[priority].push([load, label, cb, resolve, reject, startTime]);
66
+ this.#queue[priority].push([load, label, cb, resolve, reject, increasePriorityOverTime, startTime]);
67
67
  });
68
68
  this.#checkForNext();
69
69
  return p;
@@ -78,7 +78,12 @@ class WorkerQueue {
78
78
  const nextPriority = priorityValues[i + 1];
79
79
  for (let i = 0; i < this.queue[priority].length; i++) {
80
80
  const queueEntry = this.queue[priority][i];
81
- const startTime = queueEntry[6] ?? queueEntry[5];
81
+ // NOTE: index 5 - increasingPrioOverTime
82
+ if (!queueEntry[5]) {
83
+ continue;
84
+ }
85
+ // NOTE: index 6 original time; index 7 shifted time
86
+ const startTime = queueEntry[7] ?? queueEntry[6];
82
87
  if (Math.round(Number(checkTime - startTime) / NANO_TO_MS) > INCREASE_PRIORITY_AFTER * MIN_TO_MS) {
83
88
  const [entry] = this.queue[priority].splice(i, 1);
84
89
  entry.push(checkTime);
@@ -88,7 +93,7 @@ class WorkerQueue {
88
93
  }
89
94
  }
90
95
 
91
- _executeFunction(load, label, cb, resolve, reject, startTime, priority) {
96
+ _executeFunction(priority, load, label, cb, resolve, reject, skipIncreasingPrioOverTime, startTime) {
92
97
  this.#checkAndLogWaitingTime(startTime, label, priority);
93
98
  const promise = Promise.resolve().then(() => cb());
94
99
  this.#runningPromises.push(promise);
@@ -123,7 +128,7 @@ class WorkerQueue {
123
128
  const [load] = this.#queue[priority][i];
124
129
  if (this.#runningLoad + load <= this.#concurrencyLimit) {
125
130
  const [args] = this.#queue[priority].splice(i, 1);
126
- this._executeFunction(...args, priority);
131
+ this._executeFunction(priority, ...args);
127
132
  entryFound = true;
128
133
  break;
129
134
  }
@@ -101,18 +101,27 @@ const _getNewTokenInfo = async (tenantId) => {
101
101
  return tokenInfo;
102
102
  } catch (err) {
103
103
  tokenInfoCache[tenantId] = null;
104
- cds.log(COMPONENT_NAME).warn("failed to request tokenInfo", err);
104
+ cds.log(COMPONENT_NAME).warn("failed to request tokenInfo", {
105
+ err: err.message,
106
+ responseCode: err.responseCode,
107
+ responseText: err.responseText,
108
+ });
105
109
  }
106
110
  };
107
111
 
108
112
  const getTokenInfo = async (tenantId) => {
109
- if (!isTenantIdValidCb(TenantIdCheckTypes.getTokenInfo, tenantId)) {
113
+ if (!(await isTenantIdValidCb(TenantIdCheckTypes.getTokenInfo, tenantId))) {
110
114
  return null;
111
115
  }
112
116
 
113
117
  if (!cds.requires?.auth?.credentials) {
114
118
  return null; // no credentials not tokenInfo
115
119
  }
120
+
121
+ if (!config.isMultiTenancy) {
122
+ return null; // does only make sense for multi tenancy
123
+ }
124
+
116
125
  if (!cds.requires?.auth.kind.match(/jwt|xsuaa/i)) {
117
126
  cds.log(COMPONENT_NAME).warn("Only 'jwt' or 'xsuaa' are supported as values for auth.kind.");
118
127
  return null;