@cap-js-community/event-queue 1.3.6 → 1.4.1

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/cds-plugin.js CHANGED
@@ -6,5 +6,5 @@ const eventQueue = require("./src");
6
6
  const COMPONENT_NAME = "/eventQueue/plugin";
7
7
 
8
8
  if (!cds.build.register && cds.env.eventQueue) {
9
- eventQueue.initialize().catch((err) => cds.log(COMPONENT_NAME).error(err));
9
+ module.exports = eventQueue.initialize().catch((err) => cds.log(COMPONENT_NAME).error(err));
10
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cap-js-community/event-queue",
3
- "version": "1.3.6",
3
+ "version": "1.4.1",
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
  "files": [
@@ -42,12 +42,13 @@
42
42
  "node": ">=18"
43
43
  },
44
44
  "dependencies": {
45
- "redis": "4.6.13",
46
- "verror": "1.10.1",
47
- "yaml": "2.4.1"
45
+ "@sap/xssec": "^3.6.1",
46
+ "redis": "^4.6.13",
47
+ "verror": "^1.10.1",
48
+ "yaml": "^2.4.1"
48
49
  },
49
50
  "devDependencies": {
50
- "@cap-js/hana": "^0.0.6",
51
+ "@cap-js/hana": "^0.1.0",
51
52
  "@cap-js/sqlite": "^1.5.0",
52
53
  "@sap/cds": "^7.7.0",
53
54
  "@sap/cds-dk": "^7.5.1",
@@ -10,7 +10,7 @@ const { arrayToFlatMap } = require("./shared/common");
10
10
  const eventScheduler = require("./shared/eventScheduler");
11
11
  const eventConfig = require("./config");
12
12
  const PerformanceTracer = require("./shared/PerformanceTracer");
13
- const { broadcastEvent } = require("./redisPubSub");
13
+ const { broadcastEvent } = require("./redis/redisPub");
14
14
 
15
15
  const IMPLEMENT_ERROR_MESSAGE = "needs to be reimplemented";
16
16
  const COMPONENT_NAME = "/eventQueue/EventQueueProcessorBase";
@@ -23,8 +23,6 @@ const TRIES_FOR_EXCEEDED_EVENTS = 3;
23
23
  const EVENT_START_AFTER_HEADROOM = 3 * 1000;
24
24
  const ETAG_CHECK_AFTER_MIN = 10;
25
25
 
26
- let serviceBindingCache = null;
27
-
28
26
  class EventQueueProcessorBase {
29
27
  #eventsWithExceededTries = [];
30
28
  #exceededTriesExceeded = [];
@@ -72,8 +70,6 @@ class EventQueueProcessorBase {
72
70
  this.__txMap = {};
73
71
  this.__txRollback = {};
74
72
  this.__queueEntries = [];
75
-
76
- this.#checkGlobalContextToLocalContext();
77
73
  }
78
74
 
79
75
  /**
@@ -537,10 +533,7 @@ class EventQueueProcessorBase {
537
533
  async getQueueEntriesAndSetToInProgress() {
538
534
  let result = [];
539
535
  const refDateStartAfter = new Date(Date.now() + this.#config.runInterval * 1.2);
540
- this.#checkGlobalContextToLocalContext();
541
536
  await executeInNewTransaction(this.__baseContext, "eventQueue-getQueueEntriesAndSetToInProgress", async (tx) => {
542
- this.#checkGlobalContextToLocalContext();
543
- await this.checkTxConsistency(tx);
544
537
  const entries = await tx.run(
545
538
  SELECT.from(this.#config.tableNameEventQueue)
546
539
  .forUpdate({ wait: this.#config.forUpdateTimeout })
@@ -667,57 +660,6 @@ class EventQueueProcessorBase {
667
660
  return result;
668
661
  }
669
662
 
670
- async checkTxConsistency(tx) {
671
- if (!this.#config.enableTxConsistencyCheck) {
672
- return;
673
- }
674
-
675
- const errorHandler = (err) =>
676
- this.logger.error("tx consistency check failed!", err, {
677
- type: this.eventType,
678
- subType: this.eventSubType,
679
- txTenant: tx.context.tenant,
680
- globalCdsTenant: cds.context.tenant,
681
- });
682
- let txSchema, serviceManagerSchema;
683
- try {
684
- const schemaPromise = tx.run("SELECT CURRENT_SCHEMA FROM DUMMY");
685
- const [schema, serviceManagerBindings] = await Promise.allSettled([schemaPromise, this.#getServiceBindings()]);
686
- if (schema.reason) {
687
- errorHandler(schema.reason);
688
- return;
689
- }
690
- if (serviceManagerBindings.reason) {
691
- errorHandler(serviceManagerBindings.reason);
692
- return;
693
- }
694
-
695
- txSchema = schema.value[0].CURRENT_SCHEMA;
696
- serviceManagerSchema = serviceManagerBindings.value.find((t) => t.labels.tenant_id[0] === tx.context.tenant)
697
- .credentials.schema;
698
- } catch (err) {
699
- errorHandler(err);
700
- }
701
- if (serviceManagerSchema && txSchema !== serviceManagerSchema) {
702
- const err = EventQueueError.dbClientSchemaMismatch(tx.context.tenant, txSchema, serviceManagerSchema);
703
- errorHandler(err);
704
- throw err;
705
- }
706
- }
707
-
708
- async #getServiceBindings() {
709
- if (!(serviceBindingCache && serviceBindingCache.expireTs >= Date.now())) {
710
- const mtxServiceManager = require("@sap/cds-mtxs/srv/plugins/hana/srv-mgr");
711
- serviceBindingCache = {
712
- expireTs: Date.now() + 10 * 60 * 1000,
713
- value: mtxServiceManager.getAll().catch(() => {
714
- serviceBindingCache = null;
715
- }),
716
- };
717
- }
718
- return await serviceBindingCache.value;
719
- }
720
-
721
663
  async #selectLastSuccessfulPeriodicTimestamp() {
722
664
  const entry = await SELECT.one
723
665
  .from(this.#config.tableNameEventQueue)
@@ -730,48 +672,6 @@ class EventQueueProcessorBase {
730
672
  return entry.lastAttemptsTs;
731
673
  }
732
674
 
733
- #checkGlobalContextToLocalContext() {
734
- if (!this.#config.enableTxConsistencyCheck) {
735
- return;
736
- }
737
- if (this.__context.tenant !== cds.context.tenant) {
738
- throw EventQueueError.globalCdsContextNotMatchingLocal(
739
- JSON.stringify(
740
- {
741
- correlationId: cds.context.id,
742
- tenantId: cds.context.tenant,
743
- timestamp: cds.context.timestamp,
744
- base: JSON.stringify(
745
- {
746
- correlationId: cds.context.context?.id,
747
- tenantId: cds.context.context?.tenant,
748
- timestamp: cds.context.context?.timestamp,
749
- },
750
- null,
751
- 2
752
- ),
753
- },
754
- null,
755
- 2
756
- ),
757
- JSON.stringify(
758
- {
759
- correlationId: this.__context.id,
760
- tenantId: this.__context.tenant,
761
- timestamp: this.__context.timestamp,
762
- base: JSON.stringify({
763
- correlationId: this.__context.context?.id,
764
- tenantId: this.__context.context?.tenant,
765
- timestamp: this.__context.context?.timestamp,
766
- }),
767
- },
768
- null,
769
- 2
770
- )
771
- );
772
- }
773
- }
774
-
775
675
  #handleDelayedEvents(delayedEvents) {
776
676
  for (const delayedEvent of delayedEvents) {
777
677
  this.#eventSchedulerInstance.scheduleEvent(
@@ -1113,7 +1013,7 @@ class EventQueueProcessorBase {
1113
1013
 
1114
1014
  broadCastEvent() {
1115
1015
  setTimeout(() => {
1116
- broadcastEvent(this.__baseContext.tenant, this.#eventType, this.#eventSubType).catch((err) => {
1016
+ broadcastEvent(this.__baseContext.tenant, { type: this.#eventType, subType: this.#eventSubType }).catch((err) => {
1117
1017
  this.logger.error("could not execute scheduled event", err, {
1118
1018
  tenantId: this.__baseContext.tenant,
1119
1019
  type: this.#eventType,
package/src/config.js CHANGED
@@ -34,6 +34,11 @@ const BASE_PERIODIC_EVENTS = [
34
34
  },
35
35
  ];
36
36
 
37
+ const BASE_TABLES = {
38
+ EVENT: "sap.eventqueue.Event",
39
+ LOCK: "sap.eventqueue.Lock",
40
+ };
41
+
37
42
  class Config {
38
43
  #logger;
39
44
  #config;
@@ -59,8 +64,8 @@ class Config {
59
64
  #thresholdLoggingEventProcessing;
60
65
  #useAsCAPOutbox;
61
66
  #userId;
62
- #enableTxConsistencyCheck;
63
67
  #cleanupLocksAndEventsForDev;
68
+ #redisOptions;
64
69
  static #instance;
65
70
  constructor() {
66
71
  this.#logger = cds.log(COMPONENT_NAME);
@@ -105,7 +110,7 @@ class Config {
105
110
 
106
111
  attachConfigChangeHandler() {
107
112
  this.#attachBlockListChangeHandler();
108
- redis.subscribeRedisChannel(REDIS_CONFIG_CHANNEL, (messageData) => {
113
+ redis.subscribeRedisChannel(this.#redisOptions, REDIS_CONFIG_CHANNEL, (messageData) => {
109
114
  try {
110
115
  const { key, value } = JSON.parse(messageData);
111
116
  if (this[key] !== value) {
@@ -125,13 +130,13 @@ class Config {
125
130
  this.#logger.info("redis not connected, config change won't be published", { key, value });
126
131
  return;
127
132
  }
128
- redis.publishMessage(REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
133
+ redis.publishMessage(this.#redisOptions, REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
129
134
  this.#logger.error(`publishing config change failed key: ${key}, value: ${value}`, error);
130
135
  });
131
136
  }
132
137
 
133
138
  #attachBlockListChangeHandler() {
134
- redis.subscribeRedisChannel(REDIS_CONFIG_BLOCKLIST_CHANNEL, (messageData) => {
139
+ redis.subscribeRedisChannel(this.#redisOptions, REDIS_CONFIG_BLOCKLIST_CHANNEL, (messageData) => {
135
140
  try {
136
141
  const { command, key, tenant } = JSON.parse(messageData);
137
142
  if (command === COMMAND_BLOCK) {
@@ -160,7 +165,11 @@ class Config {
160
165
  }
161
166
 
162
167
  redis
163
- .publishMessage(REDIS_CONFIG_BLOCKLIST_CHANNEL, JSON.stringify({ command: COMMAND_BLOCK, key, tenant }))
168
+ .publishMessage(
169
+ this.#redisOptions,
170
+ REDIS_CONFIG_BLOCKLIST_CHANNEL,
171
+ JSON.stringify({ command: COMMAND_BLOCK, key, tenant })
172
+ )
164
173
  .catch((error) => {
165
174
  this.#logger.error(`publishing config block failed key: ${key}`, error);
166
175
  });
@@ -189,7 +198,11 @@ class Config {
189
198
  }
190
199
 
191
200
  redis
192
- .publishMessage(REDIS_CONFIG_BLOCKLIST_CHANNEL, JSON.stringify({ command: COMMAND_UNBLOCK, key, tenant }))
201
+ .publishMessage(
202
+ this.#redisOptions,
203
+ REDIS_CONFIG_BLOCKLIST_CHANNEL,
204
+ JSON.stringify({ command: COMMAND_UNBLOCK, key, tenant })
205
+ )
193
206
  .catch((error) => {
194
207
  this.#logger.error(`publishing config block failed key: ${key}`, error);
195
208
  });
@@ -386,19 +399,11 @@ class Config {
386
399
  }
387
400
 
388
401
  get tableNameEventQueue() {
389
- return this.#tableNameEventQueue;
390
- }
391
-
392
- set tableNameEventQueue(value) {
393
- this.#tableNameEventQueue = value;
402
+ return BASE_TABLES.EVENT;
394
403
  }
395
404
 
396
405
  get tableNameEventLock() {
397
- return this.#tableNameEventLock;
398
- }
399
-
400
- set tableNameEventLock(value) {
401
- this.#tableNameEventLock = value;
406
+ return BASE_TABLES.LOCK;
402
407
  }
403
408
 
404
409
  set configFilePath(value) {
@@ -473,14 +478,6 @@ class Config {
473
478
  return this.#userId;
474
479
  }
475
480
 
476
- set enableTxConsistencyCheck(value) {
477
- this.#enableTxConsistencyCheck = value;
478
- }
479
-
480
- get enableTxConsistencyCheck() {
481
- return this.#enableTxConsistencyCheck;
482
- }
483
-
484
481
  set cleanupLocksAndEventsForDev(value) {
485
482
  this.#cleanupLocksAndEventsForDev = value;
486
483
  }
@@ -489,6 +486,14 @@ class Config {
489
486
  return this.#cleanupLocksAndEventsForDev;
490
487
  }
491
488
 
489
+ set redisOptions(value) {
490
+ this.#redisOptions = value;
491
+ }
492
+
493
+ get redisOptions() {
494
+ return this.#redisOptions;
495
+ }
496
+
492
497
  get isMultiTenancy() {
493
498
  return !!cds.requires.multitenancy;
494
499
  }
package/src/dbHandler.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  const cds = require("@sap/cds");
4
4
 
5
- const { broadcastEvent } = require("./redisPubSub");
5
+ const { broadcastEvent } = require("./redis/redisPub");
6
6
  const config = require("./config");
7
7
 
8
8
  const COMPONENT_NAME = "/eventQueue/dbHandler";
@@ -31,14 +31,17 @@ const registerEventQueueDbHandler = (dbService) => {
31
31
 
32
32
  eventCombinations.length &&
33
33
  req.on("succeeded", () => {
34
- for (const eventCombination of eventCombinations) {
35
- broadcastEvent(req.tenant, ...eventCombination.split("##")).catch((err) => {
36
- cds.log(COMPONENT_NAME).error("db handler failure during broadcasting event", err, {
37
- tenant: req.tenant,
38
- eventCombination,
39
- });
34
+ const events = eventCombinations.map((eventCombination) => {
35
+ const [type, subType] = eventCombination.split("##");
36
+ return { type, subType };
37
+ });
38
+
39
+ broadcastEvent(req.tenant, events).catch((err) => {
40
+ cds.log(COMPONENT_NAME).error("db handler failure during broadcasting event", err, {
41
+ tenant: req.tenant,
42
+ events,
40
43
  });
41
- }
44
+ });
42
45
  });
43
46
  });
44
47
  };
package/src/initialize.js CHANGED
@@ -7,11 +7,10 @@ const cds = require("@sap/cds");
7
7
  const yaml = require("yaml");
8
8
  const VError = require("verror");
9
9
 
10
- const EventQueueError = require("./EventQueueError");
11
- const runner = require("./runner");
10
+ const runner = require("./runner/runner");
12
11
  const dbHandler = require("./dbHandler");
13
12
  const config = require("./config");
14
- const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
13
+ const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redis/redisSub");
15
14
  const redis = require("./shared/redis");
16
15
  const eventQueueAsOutbox = require("./outbox/eventQueueAsOutbox");
17
16
  const { getAllTenantIds } = require("./shared/cdsHelper");
@@ -21,26 +20,20 @@ const readFileAsync = promisify(fs.readFile);
21
20
 
22
21
  const VERROR_CLUSTER_NAME = "EventQueueInitialization";
23
22
  const COMPONENT = "eventQueue/initialize";
24
- const BASE_TABLES = {
25
- EVENT: "sap.eventqueue.Event",
26
- LOCK: "sap.eventqueue.Lock",
27
- };
23
+
28
24
  const CONFIG_VARS = [
29
25
  ["configFilePath", null],
30
26
  ["registerAsEventProcessor", true],
31
27
  ["processEventsAfterPublish", true],
32
28
  ["isEventQueueActive", true],
33
29
  ["runInterval", 25 * 60 * 1000],
34
- ["tableNameEventQueue", BASE_TABLES.EVENT],
35
- ["tableNameEventLock", BASE_TABLES.LOCK],
36
30
  ["disableRedis", true],
37
- ["skipCsnCheck", false],
38
31
  ["updatePeriodicEvents", true],
39
32
  ["thresholdLoggingEventProcessing", 50],
40
33
  ["useAsCAPOutbox", false],
41
34
  ["userId", null],
42
- ["enableTxConsistencyCheck", false],
43
35
  ["cleanupLocksAndEventsForDev", false],
36
+ ["redisOptions", {}],
44
37
  ];
45
38
 
46
39
  const initialize = async ({
@@ -49,16 +42,13 @@ const initialize = async ({
49
42
  processEventsAfterPublish,
50
43
  isEventQueueActive,
51
44
  runInterval,
52
- tableNameEventQueue,
53
- tableNameEventLock,
54
45
  disableRedis,
55
- skipCsnCheck,
56
46
  updatePeriodicEvents,
57
47
  thresholdLoggingEventProcessing,
58
48
  useAsCAPOutbox,
59
49
  userId,
60
- enableTxConsistencyCheck,
61
50
  cleanupLocksAndEventsForDev,
51
+ redisOptions,
62
52
  } = {}) => {
63
53
  if (config.initialized) {
64
54
  return;
@@ -71,16 +61,13 @@ const initialize = async ({
71
61
  processEventsAfterPublish,
72
62
  isEventQueueActive,
73
63
  runInterval,
74
- tableNameEventQueue,
75
- tableNameEventLock,
76
64
  disableRedis,
77
- skipCsnCheck,
78
65
  updatePeriodicEvents,
79
66
  thresholdLoggingEventProcessing,
80
67
  useAsCAPOutbox,
81
68
  userId,
82
- enableTxConsistencyCheck,
83
- cleanupLocksAndEventsForDev
69
+ cleanupLocksAndEventsForDev,
70
+ redisOptions
84
71
  );
85
72
 
86
73
  const logger = cds.log(COMPONENT);
@@ -95,12 +82,10 @@ const initialize = async ({
95
82
  }
96
83
  });
97
84
  if (redisEnabled) {
98
- config.redisEnabled = await redis.connectionCheck();
85
+ config.redisEnabled = await redis.connectionCheck(config.redisOptions);
99
86
  }
100
87
  config.fileContent = await readConfigFromFile(config.configFilePath);
101
88
 
102
- !config.skipCsnCheck && (await csnCheck());
103
-
104
89
  monkeyPatchCAPOutbox();
105
90
  registerCdsShutdown();
106
91
  logger.info("event queue initialized", {
@@ -172,50 +157,6 @@ const monkeyPatchCAPOutbox = () => {
172
157
  }
173
158
  };
174
159
 
175
- const csnCheck = async () => {
176
- cds.on("loaded", async (csn) => {
177
- if (csn.namespace === "cds.xt") {
178
- return;
179
- }
180
- const eventCsn = csn.definitions[config.tableNameEventQueue];
181
- if (!eventCsn) {
182
- throw EventQueueError.missingTableInCsn(config.tableNameEventQueue);
183
- }
184
-
185
- const lockCsn = csn.definitions[config.tableNameEventLock];
186
- if (!lockCsn) {
187
- throw EventQueueError.missingTableInCsn(config.tableNameEventLock);
188
- }
189
-
190
- if (config.tableNameEventQueue === BASE_TABLES.EVENT && config.tableNameEventLock === BASE_TABLES.LOCK) {
191
- return; // no need to check base tables
192
- }
193
-
194
- const baseEvent = csn.definitions["sap.eventqueue.Event"];
195
- const baseLock = csn.definitions["sap.eventqueue.Lock"];
196
-
197
- checkCustomTable(baseEvent, eventCsn);
198
- checkCustomTable(baseLock, lockCsn);
199
- });
200
- };
201
-
202
- const checkCustomTable = (baseCsn, customCsn) => {
203
- for (const columnName in baseCsn.elements) {
204
- if (!customCsn.elements[columnName]) {
205
- throw EventQueueError.missingElementInTable(customCsn.name, columnName);
206
- }
207
-
208
- if (
209
- customCsn.elements[columnName].type !== "cds.Association" &&
210
- customCsn.elements[columnName].type !== baseCsn.elements[columnName].type &&
211
- columnName === "status" &&
212
- customCsn.elements[columnName].type !== "cds.Integer"
213
- ) {
214
- throw EventQueueError.typeMismatchInTable(customCsn.name, columnName);
215
- }
216
- }
217
- };
218
-
219
160
  const mixConfigVarsWithEnv = (...args) => {
220
161
  CONFIG_VARS.forEach(([configName, defaultValue], index) => {
221
162
  const configValue = args[index];
@@ -96,9 +96,6 @@ const processEventQueue = async (context, eventType, eventSubType, startTime = n
96
96
  }
97
97
  } catch (err) {
98
98
  cds.log(COMPONENT_NAME).error("Processing event queue failed with unexpected error.", err, {
99
- tenantId: context?.tenant,
100
- tenantIdBase: baseInstance?.context?.tenant,
101
- globalTenantId: cds.context?.tenant,
102
99
  eventType,
103
100
  eventSubType,
104
101
  });
@@ -188,9 +185,6 @@ const processPeriodicEvent = async (context, eventTypeInstance) => {
188
185
  cds.log(COMPONENT_NAME).error("Processing periodic events failed with unexpected error.", err, {
189
186
  eventType: eventTypeInstance?.eventType,
190
187
  eventSubType: eventTypeInstance?.eventSubType,
191
- tenantId: context?.tenant,
192
- tenantIdBase: eventTypeInstance?.context?.tenant,
193
- globalTenantId: cds.context?.tenant,
194
188
  });
195
189
  } finally {
196
190
  await eventTypeInstance?.handleReleaseLock();
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+
3
+ const { promisify } = require("util");
4
+
5
+ const cds = require("@sap/cds");
6
+
7
+ const redis = require("../shared/redis");
8
+ const { checkLockExistsAndReturnValue } = require("../shared/distributedLock");
9
+ const config = require("../config");
10
+ const common = require("../shared/common");
11
+ const { runEventCombinationForTenant } = require("../runner/runnerHelper");
12
+
13
+ const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
14
+ const COMPONENT_NAME = "/eventQueue/redisPub";
15
+ const TRIES_FOR_PUBLISH_PERIODIC_EVENT = 10;
16
+ const SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT = 30 * 1000;
17
+
18
+ const wait = promisify(setTimeout);
19
+
20
+ const broadcastEvent = async (tenantId, events) => {
21
+ const logger = cds.log(COMPONENT_NAME);
22
+ events = Array.isArray(events) ? events : [events];
23
+ try {
24
+ if (!config.isEventQueueActive) {
25
+ cds.log(COMPONENT_NAME).info("Skipping processing because runner is deactivated!", {});
26
+ return;
27
+ }
28
+ if (!config.redisEnabled) {
29
+ if (config.registerAsEventProcessor) {
30
+ let context = {};
31
+ if (tenantId) {
32
+ const user = new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
33
+ context = {
34
+ tenant: tenantId,
35
+ user,
36
+ };
37
+ }
38
+
39
+ return await cds.tx(context, async ({ context }) => {
40
+ for (const { type, subType } of events) {
41
+ await runEventCombinationForTenant(context, type, subType);
42
+ }
43
+ });
44
+ }
45
+ return;
46
+ }
47
+ for (const { type, subType } of events) {
48
+ const eventConfig = config.getEventConfig(type, subType);
49
+ for (let i = 0; i < TRIES_FOR_PUBLISH_PERIODIC_EVENT; i++) {
50
+ const result = await checkLockExistsAndReturnValue(
51
+ new cds.EventContext({ tenant: tenantId }),
52
+ [type, subType].join("##")
53
+ );
54
+ if (result) {
55
+ logger.debug("skip publish redis event as no lock is available", {
56
+ type,
57
+ subType,
58
+ index: i,
59
+ isPeriodic: eventConfig.isPeriodic,
60
+ waitInterval: SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT,
61
+ });
62
+ if (!eventConfig.isPeriodic) {
63
+ break;
64
+ }
65
+ await wait(SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT);
66
+ continue;
67
+ }
68
+ logger.debug("publishing redis event", {
69
+ tenantId,
70
+ type,
71
+ subType,
72
+ });
73
+ await redis.publishMessage(
74
+ config.redisOptions,
75
+ EVENT_MESSAGE_CHANNEL,
76
+ JSON.stringify({ tenantId, type, subType })
77
+ );
78
+ break;
79
+ }
80
+ }
81
+ } catch (err) {
82
+ logger.error("publish events failed!", err, {
83
+ tenantId,
84
+ });
85
+ }
86
+ };
87
+
88
+ module.exports = {
89
+ broadcastEvent,
90
+ };
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+
3
+ const cds = require("@sap/cds");
4
+
5
+ const redis = require("../shared/redis");
6
+ const config = require("../config");
7
+ const runnerHelper = require("../runner/runnerHelper");
8
+ const common = require("../shared/common");
9
+
10
+ const EVENT_MESSAGE_CHANNEL = "EVENT_QUEUE_MESSAGE_CHANNEL";
11
+ const COMPONENT_NAME = "/eventQueue/redisSub";
12
+ let subscriberClientPromise;
13
+
14
+ const initEventQueueRedisSubscribe = () => {
15
+ if (subscriberClientPromise || !config.redisEnabled) {
16
+ return;
17
+ }
18
+ redis.subscribeRedisChannel(config.redisOptions, EVENT_MESSAGE_CHANNEL, _messageHandlerProcessEvents);
19
+ };
20
+
21
+ const _messageHandlerProcessEvents = async (messageData) => {
22
+ const logger = cds.log(COMPONENT_NAME);
23
+ try {
24
+ const { tenantId, type, subType } = JSON.parse(messageData);
25
+ logger.debug("received redis event", {
26
+ tenantId,
27
+ type,
28
+ subType,
29
+ });
30
+ if (!config.isEventQueueActive) {
31
+ cds.log(COMPONENT_NAME).info("Skipping processing because runner is deactivated!", {
32
+ type,
33
+ subType,
34
+ });
35
+ return;
36
+ }
37
+
38
+ const user = new cds.User.Privileged({ id: config.userId, authInfo: await common.getAuthInfo(tenantId) });
39
+ const tenantContext = {
40
+ tenant: tenantId,
41
+ user,
42
+ };
43
+
44
+ if (!config.getEventConfig(type, subType)) {
45
+ if (config.isCapOutboxEvent(type)) {
46
+ try {
47
+ const service = await cds.connect.to(subType);
48
+ cds.outboxed(service);
49
+ } catch (err) {
50
+ logger.error("could not connect to outboxed service", err, {
51
+ type,
52
+ subType,
53
+ });
54
+ return;
55
+ }
56
+ } else {
57
+ logger.error("cannot find configuration for published event. Event won't be processed", {
58
+ type,
59
+ subType,
60
+ });
61
+ return;
62
+ }
63
+ }
64
+
65
+ return await cds.tx(tenantContext, async ({ context }) => {
66
+ return await runnerHelper.runEventCombinationForTenant(context, type, subType);
67
+ });
68
+ } catch (err) {
69
+ logger.error("could not parse event information", {
70
+ messageData,
71
+ });
72
+ }
73
+ };
74
+
75
+ const closeSubscribeClient = async () => {
76
+ try {
77
+ const client = await subscriberClientPromise;
78
+ if (client?.quit) {
79
+ await client.quit();
80
+ }
81
+ } catch (err) {
82
+ // ignore errors during shutdown
83
+ }
84
+ };
85
+
86
+ module.exports = {
87
+ initEventQueueRedisSubscribe,
88
+ closeSubscribeClient,
89
+ __: {
90
+ _messageHandlerProcessEvents,
91
+ },
92
+ };