@cap-js-community/event-queue 0.2.5 → 0.3.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": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "An event queue that enables secure transactional processing of asynchronous events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.",
5
5
  "main": "src/index.js",
6
6
  "files": [
package/src/config.js CHANGED
@@ -12,6 +12,7 @@ const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
12
12
  const COMPONENT_NAME = "eventQueue/config";
13
13
  const MIN_INTERVAL_SEC = 10;
14
14
  const DEFAULT_LOAD = 1;
15
+ const SUFFIX_PERIODIC = "_PERIODIC";
15
16
 
16
17
  class Config {
17
18
  #logger;
@@ -42,7 +43,7 @@ class Config {
42
43
  this.#runInterval = null;
43
44
  this.#redisEnabled = null;
44
45
  this.#initialized = false;
45
- this.#instanceLoadLimit = null;
46
+ this.#instanceLoadLimit = 100;
46
47
  this.#tableNameEventQueue = null;
47
48
  this.#tableNameEventLock = null;
48
49
  this.#isRunnerDeactivated = false;
@@ -115,7 +116,6 @@ class Config {
115
116
  }, {});
116
117
  this.#eventMap = config.periodicEvents.reduce((result, event) => {
117
118
  event.load = event.load ?? DEFAULT_LOAD;
118
- const SUFFIX_PERIODIC = "_PERIODIC";
119
119
  event.type = `${event.type}${SUFFIX_PERIODIC}`;
120
120
  event.isPeriodic = true;
121
121
  this.validatePeriodicConfig(result, event);
package/src/initialize.js CHANGED
@@ -29,7 +29,6 @@ const CONFIG_VARS = [
29
29
  ["processEventsAfterPublish", true],
30
30
  ["isRunnerDeactivated", false],
31
31
  ["runInterval", 5 * 60 * 1000],
32
- ["instanceLoadLimit", 20],
33
32
  ["tableNameEventQueue", BASE_TABLES.EVENT],
34
33
  ["tableNameEventLock", BASE_TABLES.LOCK],
35
34
  ["disableRedis", false],
@@ -43,7 +42,6 @@ const initialize = async ({
43
42
  processEventsAfterPublish,
44
43
  isRunnerDeactivated,
45
44
  runInterval,
46
- instanceLoadLimit,
47
45
  tableNameEventQueue,
48
46
  tableNameEventLock,
49
47
  disableRedis,
@@ -52,7 +50,7 @@ const initialize = async ({
52
50
  } = {}) => {
53
51
  // TODO: initialize check:
54
52
  // - content of yaml check
55
- // - betweenRuns and instanceLoadLimit
53
+ // - betweenRuns
56
54
 
57
55
  if (config.initialized) {
58
56
  return;
@@ -65,7 +63,6 @@ const initialize = async ({
65
63
  processEventsAfterPublish,
66
64
  isRunnerDeactivated,
67
65
  runInterval,
68
- instanceLoadLimit,
69
66
  tableNameEventQueue,
70
67
  tableNameEventLock,
71
68
  disableRedis,
@@ -92,7 +89,6 @@ const initialize = async ({
92
89
  multiTenancyEnabled: config.isMultiTenancy,
93
90
  redisEnabled: config.redisEnabled,
94
91
  runInterval: config.runInterval,
95
- config: config.instanceLoadLimit,
96
92
  });
97
93
  };
98
94
 
@@ -166,7 +166,7 @@ const processPeriodicEvent = async (eventTypeInstance) => {
166
166
  throw new TriggerRollback();
167
167
  }
168
168
  if (
169
- eventTypeInstance.transactionMode !== TransactionMode.alwaysCommit ||
169
+ eventTypeInstance.transactionMode === TransactionMode.alwaysRollback ||
170
170
  eventTypeInstance.shouldRollbackTransaction(queueEntry.ID)
171
171
  ) {
172
172
  throw new TriggerRollback();
@@ -4,6 +4,7 @@ const redis = require("./shared/redis");
4
4
  const { checkLockExistsAndReturnValue } = require("./shared/distributedLock");
5
5
  const config = require("./config");
6
6
  const { runEventCombinationForTenant } = require("./runner");
7
+ const { getSubdomainForTenantId } = require("./shared/cdsHelper");
7
8
 
8
9
  const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
9
10
  const COMPONENT_NAME = "eventQueue/redisPubSub";
@@ -26,7 +27,15 @@ const messageHandlerProcessEvents = async (messageData) => {
26
27
  type,
27
28
  subType,
28
29
  });
29
- await runEventCombinationForTenant(tenantId, type, subType);
30
+ const subdomain = await getSubdomainForTenantId(tenantId);
31
+ const tenantContext = {
32
+ tenant: tenantId,
33
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
34
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
35
+ };
36
+ return await cds.tx(tenantContext, async ({ context }) => {
37
+ return await runEventCombinationForTenant(context, type, subType);
38
+ });
30
39
  } catch (err) {
31
40
  logger.error("could not parse event information", {
32
41
  messageData,
@@ -36,13 +45,25 @@ const messageHandlerProcessEvents = async (messageData) => {
36
45
 
37
46
  const broadcastEvent = async (tenantId, type, subType) => {
38
47
  const logger = cds.log(COMPONENT_NAME);
39
- if (!config.redisEnabled) {
40
- if (config.registerAsEventProcessor) {
41
- await runEventCombinationForTenant(tenantId, type, subType);
42
- }
43
- return;
44
- }
45
48
  try {
49
+ if (!config.redisEnabled) {
50
+ if (config.registerAsEventProcessor) {
51
+ let context = {};
52
+ if (tenantId) {
53
+ const subdomain = await getSubdomainForTenantId(tenantId);
54
+ context = {
55
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
56
+ tenant: tenantId,
57
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
58
+ };
59
+ }
60
+
61
+ return await cds.tx(context, async ({ context }) => {
62
+ return await runEventCombinationForTenant(context, type, subType);
63
+ });
64
+ }
65
+ return;
66
+ }
46
67
  const result = await checkLockExistsAndReturnValue(
47
68
  new cds.EventContext({ tenant: tenantId }),
48
69
  [type, subType].join("##")
package/src/runner.js CHANGED
@@ -94,45 +94,61 @@ const _checkAndTriggerPeriodicEventUpdate = (tenantIds) => {
94
94
 
95
95
  const _executeEventsAllTenants = (tenantIds, runId) => {
96
96
  const events = eventQueueConfig.allEvents;
97
- const promises = [];
98
- tenantIds.forEach((tenantId) => {
97
+ const product = tenantIds.reduce((result, tenantId) => {
99
98
  events.forEach((event) => {
100
- promises.push(
101
- WorkerQueue.instance.addToQueue(event.load, async () => {
102
- try {
103
- const lockId = `${runId}_${event.type}_${event.subType}`;
104
- const tenantContext = new cds.EventContext({ tenant: tenantId });
105
- const couldAcquireLock = await distributedLock.acquireLock(tenantContext, lockId, {
106
- expiryTime: eventQueueConfig.runInterval * 0.95,
107
- });
108
- if (!couldAcquireLock) {
109
- return;
110
- }
111
- await runEventCombinationForTenant(tenantId, event.type, event.subType, true);
112
- } catch (err) {
113
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
114
- tenantId,
115
- });
99
+ result.push([tenantId, event]);
100
+ });
101
+ return result;
102
+ }, []);
103
+
104
+ return product.map(async ([tenantId, event]) => {
105
+ const subdomain = await getSubdomainForTenantId(tenantId);
106
+ const tenantContext = {
107
+ tenant: tenantId,
108
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
109
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
110
+ };
111
+ return await cds.tx(tenantContext, async ({ context }) => {
112
+ return await WorkerQueue.instance.addToQueue(event.load, async () => {
113
+ try {
114
+ const lockId = `${runId}_${event.type}_${event.subType}`;
115
+ const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
116
+ expiryTime: eventQueueConfig.runInterval * 0.95,
117
+ });
118
+ if (!couldAcquireLock) {
119
+ return;
116
120
  }
117
- })
118
- );
121
+ await runEventCombinationForTenant(context, event.type, event.subType, true);
122
+ } catch (err) {
123
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
124
+ tenantId,
125
+ });
126
+ }
127
+ });
119
128
  });
120
129
  });
121
- return promises;
122
130
  };
123
131
 
124
132
  const _executePeriodicEventsAllTenants = (tenantIds, runId) => {
125
133
  tenantIds.forEach((tenantId) => {
126
134
  WorkerQueue.instance.addToQueue(1, async () => {
127
135
  try {
128
- const tenantContext = new cds.EventContext({ tenant: tenantId });
129
- const couldAcquireLock = await distributedLock.acquireLock(tenantContext, runId, {
130
- expiryTime: eventQueueConfig.runInterval * 0.95,
136
+ const subdomain = await getSubdomainForTenantId(tenantId);
137
+ const tenantContext = {
138
+ tenant: tenantId,
139
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
140
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
141
+ };
142
+
143
+ return await cds.tx(tenantContext, async ({ context }) => {
144
+ const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
145
+ expiryTime: eventQueueConfig.runInterval * 0.95,
146
+ });
147
+ if (!couldAcquireLock) {
148
+ return;
149
+ }
150
+ await _checkPeriodicEventsSingleTenant(context);
131
151
  });
132
- if (!couldAcquireLock) {
133
- return;
134
- }
135
- await _checkPeriodicEventsSingleTenant(tenantId);
136
152
  } catch (err) {
137
153
  cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
138
154
  tenantId,
@@ -147,7 +163,8 @@ const _singleTenantDb = async (tenantId) => {
147
163
  events.forEach((event) => {
148
164
  WorkerQueue.instance.addToQueue(event.load, async () => {
149
165
  try {
150
- await runEventCombinationForTenant(tenantId, event.type, event.subType, true);
166
+ const context = new cds.EventContext({ tenant: tenantId });
167
+ await runEventCombinationForTenant(context, event.type, event.subType, true);
151
168
  } catch (err) {
152
169
  cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
153
170
  tenantId,
@@ -215,15 +232,8 @@ const _calculateOffsetForFirstRun = async () => {
215
232
  return offsetDependingOnLastRun;
216
233
  };
217
234
 
218
- const runEventCombinationForTenant = async (tenantId, type, subType, skipWorkerPool) => {
235
+ const runEventCombinationForTenant = async (context, type, subType, skipWorkerPool) => {
219
236
  try {
220
- const subdomain = await getSubdomainForTenantId(tenantId);
221
- const context = new cds.EventContext({
222
- tenant: tenantId,
223
- // NOTE: we need this because of logging otherwise logs would not contain the subdomain
224
- http: { req: { authInfo: { getSubdomain: () => subdomain } } },
225
- });
226
- cds.context = context;
227
237
  if (skipWorkerPool) {
228
238
  return await processEventQueue(context, type, subType);
229
239
  } else {
@@ -236,7 +246,7 @@ const runEventCombinationForTenant = async (tenantId, type, subType, skipWorkerP
236
246
  } catch (err) {
237
247
  const logger = cds.log(COMPONENT_NAME);
238
248
  logger.error("error executing event combination for tenant", err, {
239
- tenantId,
249
+ tenantId: context.tenant,
240
250
  type,
241
251
  subType,
242
252
  });
@@ -266,7 +276,7 @@ const _multiTenancyPeriodicEvents = async () => {
266
276
  }
267
277
  };
268
278
 
269
- const _checkPeriodicEventsSingleTenant = async (tenantId) => {
279
+ const _checkPeriodicEventsSingleTenant = async (context = {}) => {
270
280
  const logger = cds.log(COMPONENT_NAME);
271
281
  if (!eventQueueConfig.updatePeriodicEvents || !eventQueueConfig.periodicEvents.length) {
272
282
  logger.info("updating of periodic events is disabled or no periodic events configured", {
@@ -276,23 +286,16 @@ const _checkPeriodicEventsSingleTenant = async (tenantId) => {
276
286
  return;
277
287
  }
278
288
  try {
279
- const subdomain = await cdsHelper.getSubdomainForTenantId(tenantId);
280
- const context = new cds.EventContext({
281
- tenant: tenantId,
282
- // NOTE: we need this because of logging otherwise logs would not contain the subdomain
283
- http: { req: { authInfo: { getSubdomain: () => subdomain } } },
284
- });
285
- cds.context = context;
286
289
  logger.info("executing updating periotic events", {
287
- tenantId,
288
- subdomain,
290
+ tenantId: context.tenant,
291
+ subdomain: context.http?.req.authInfo.getSubdomain(),
289
292
  });
290
293
  await cdsHelper.executeInNewTransaction(context, "update-periodic-events", async (tx) => {
291
294
  await periodicEvents.checkAndInsertPeriodicEvents(tx.context);
292
295
  });
293
296
  } catch (err) {
294
297
  logger.error("Couldn't update periodic events for tenant! Next try after defined interval.", err, {
295
- tenantId,
298
+ tenantId: context.tenant,
296
299
  redisEnabled: eventQueueConfig.redisEnabled,
297
300
  });
298
301
  }
@@ -8,9 +8,9 @@ const EventQueueError = require("../EventQueueError");
8
8
  const COMPONENT_NAME = "eventQueue/WorkerQueue";
9
9
  const NANO_TO_MS = 1e6;
10
10
  const THRESHOLD = {
11
- INFO: 5 * 1000,
12
- WARN: 10 * 1000,
13
- ERROR: 15 * 1000,
11
+ INFO: 15 * 1000,
12
+ WARN: 30 * 1000,
13
+ ERROR: 45 * 1000,
14
14
  };
15
15
 
16
16
  class WorkerQueue {