@cap-js-community/event-queue 0.2.1 → 0.2.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": "0.2.1",
3
+ "version": "0.2.3",
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": [
@@ -12,6 +12,10 @@ const ERROR_CODES = {
12
12
  MISSING_ELEMENT_IN_TABLE: "MISSING_ELEMENT_IN_TABLE",
13
13
  TYPE_MISMATCH_TABLE: "TYPE_MISMATCH_TABLE",
14
14
  NO_VALID_DATE: "NO_VALID_DATE",
15
+ INVALID_INTERVAL: "INVALID_INTERVAL",
16
+ MISSING_IMPL: "MISSING_IMPL",
17
+ DUPLICATE_EVENT_REGISTRATION: "DUPLICATE_EVENT_REGISTRATION",
18
+ NO_MANUEL_INSERT_OF_PERIODIC: "NO_MANUEL_INSERT_OF_PERIODIC",
15
19
  };
16
20
 
17
21
  const ERROR_CODES_META = {
@@ -43,6 +47,18 @@ const ERROR_CODES_META = {
43
47
  [ERROR_CODES.NO_VALID_DATE]: {
44
48
  message: "One or more events contain a date in a malformed format.",
45
49
  },
50
+ [ERROR_CODES.INVALID_INTERVAL]: {
51
+ message: "Invalid interval, the value needs to greater than 10 seconds.",
52
+ },
53
+ [ERROR_CODES.MISSING_IMPL]: {
54
+ message: "Missing path to event class implementation.",
55
+ },
56
+ [ERROR_CODES.DUPLICATE_EVENT_REGISTRATION]: {
57
+ message: "Duplicate event registration, check the uniqueness of type and subType.",
58
+ },
59
+ [ERROR_CODES.NO_MANUEL_INSERT_OF_PERIODIC]: {
60
+ message: "Periodic events are managed by the framework and are not allowed to insert manually.",
61
+ },
46
62
  };
47
63
 
48
64
  class EventQueueError extends VError {
@@ -146,6 +162,50 @@ class EventQueueError extends VError {
146
162
  message
147
163
  );
148
164
  }
165
+
166
+ static invalidInterval(type, subType, interval) {
167
+ const { message } = ERROR_CODES_META[ERROR_CODES.INVALID_INTERVAL];
168
+ return new EventQueueError(
169
+ {
170
+ name: ERROR_CODES.INVALID_INTERVAL,
171
+ info: { type, subType, interval },
172
+ },
173
+ message
174
+ );
175
+ }
176
+
177
+ static missingImpl(type, subType) {
178
+ const { message } = ERROR_CODES_META[ERROR_CODES.MISSING_IMPL];
179
+ return new EventQueueError(
180
+ {
181
+ name: ERROR_CODES.MISSING_IMPL,
182
+ info: { type, subType },
183
+ },
184
+ message
185
+ );
186
+ }
187
+
188
+ static duplicateEventRegistration(type, subType) {
189
+ const { message } = ERROR_CODES_META[ERROR_CODES.DUPLICATE_EVENT_REGISTRATION];
190
+ return new EventQueueError(
191
+ {
192
+ name: ERROR_CODES.DUPLICATE_EVENT_REGISTRATION,
193
+ info: { type, subType },
194
+ },
195
+ message
196
+ );
197
+ }
198
+
199
+ static manuelPeriodicEventInsert(type, subType) {
200
+ const { message } = ERROR_CODES_META[ERROR_CODES.NO_MANUEL_INSERT_OF_PERIODIC];
201
+ return new EventQueueError(
202
+ {
203
+ name: ERROR_CODES.NO_MANUEL_INSERT_OF_PERIODIC,
204
+ info: { type, subType },
205
+ },
206
+ message
207
+ );
208
+ }
149
209
  }
150
210
 
151
211
  module.exports = EventQueueError;
@@ -7,8 +7,8 @@ const { EventProcessingStatus, TransactionMode } = require("./constants");
7
7
  const distributedLock = require("./shared/distributedLock");
8
8
  const EventQueueError = require("./EventQueueError");
9
9
  const { arrayToFlatMap } = require("./shared/common");
10
- const eventScheduler = require("./shared/EventScheduler");
11
- const eventQueueConfig = require("./config");
10
+ const eventScheduler = require("./shared/eventScheduler");
11
+ const eventConfig = require("./config");
12
12
  const PerformanceTracer = require("./shared/PerformanceTracer");
13
13
 
14
14
  const IMPLEMENT_ERROR_MESSAGE = "needs to be reimplemented";
@@ -31,37 +31,43 @@ class EventQueueProcessorBase {
31
31
  #eventType = null;
32
32
  #eventSubType = null;
33
33
  #config = null;
34
+ #eventSchedulerInstance = null;
35
+ #eventConfig;
36
+ #isPeriodic;
34
37
 
35
38
  constructor(context, eventType, eventSubType, config) {
36
39
  this.__context = context;
37
40
  this.__baseContext = context;
38
41
  this.__tx = cds.tx(context);
39
42
  this.__baseLogger = cds.log(COMPONENT_NAME);
43
+ this.#eventSchedulerInstance = eventScheduler.getInstance();
44
+ this.#config = eventConfig;
45
+ this.#isPeriodic = this.#config.isPeriodicEvent(eventType, eventSubType);
40
46
  this.__logger = null;
41
47
  this.__eventProcessingMap = {};
42
48
  this.__statusMap = {};
43
49
  this.__commitedStatusMap = {};
44
50
  this.#eventType = eventType;
45
51
  this.#eventSubType = eventSubType;
46
- this.__eventConfig = config ?? {};
47
- this.__parallelEventProcessing = this.__eventConfig.parallelEventProcessing ?? DEFAULT_PARALLEL_EVENT_PROCESSING;
52
+ this.#eventConfig = config ?? {};
53
+ this.__parallelEventProcessing = this.#eventConfig.parallelEventProcessing ?? DEFAULT_PARALLEL_EVENT_PROCESSING;
48
54
  if (this.__parallelEventProcessing > LIMIT_PARALLEL_EVENT_PROCESSING) {
49
55
  this.__parallelEventProcessing = LIMIT_PARALLEL_EVENT_PROCESSING;
50
56
  }
51
57
  // NOTE: keep the feature, this might be needed again
52
58
  this.__concurrentEventProcessing = false;
53
- this.__startTime = this.__eventConfig.startTime ?? new Date();
54
- this.__retryAttempts = this.__eventConfig.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;
55
- this.__selectMaxChunkSize = this.__eventConfig.selectMaxChunkSize ?? SELECT_LIMIT_EVENTS_PER_TICK;
56
- this.__selectNextChunk = !!this.__eventConfig.checkForNextChunk;
59
+ this.__startTime = this.#eventConfig.startTime ?? new Date();
60
+ this.__retryAttempts = this.#eventConfig.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;
61
+ this.__selectMaxChunkSize = this.#eventConfig.selectMaxChunkSize ?? SELECT_LIMIT_EVENTS_PER_TICK;
62
+ this.__selectNextChunk = !!this.#eventConfig.checkForNextChunk;
57
63
  this.__keepalivePromises = {};
58
- this.__outdatedCheckEnabled = this.__eventConfig.eventOutdatedCheck ?? true;
59
- this.__transactionMode = this.__eventConfig.transactionMode ?? TransactionMode.isolated;
60
- if (this.__eventConfig.deleteFinishedEventsAfterDays) {
64
+ this.__outdatedCheckEnabled = this.#eventConfig.eventOutdatedCheck ?? true;
65
+ this.__transactionMode = this.#eventConfig.transactionMode ?? TransactionMode.isolated;
66
+ if (this.#eventConfig.deleteFinishedEventsAfterDays) {
61
67
  this.__deleteFinishedEventsAfter =
62
- Number.isInteger(this.__eventConfig.deleteFinishedEventsAfterDays) &&
63
- this.__eventConfig.deleteFinishedEventsAfterDays > 0
64
- ? this.__eventConfig.deleteFinishedEventsAfterDays
68
+ Number.isInteger(this.#eventConfig.deleteFinishedEventsAfterDays) &&
69
+ this.#eventConfig.deleteFinishedEventsAfterDays > 0
70
+ ? this.#eventConfig.deleteFinishedEventsAfterDays
65
71
  : DEFAULT_DELETE_FINISHED_EVENTS_AFTER;
66
72
  } else {
67
73
  this.__deleteFinishedEventsAfter = DEFAULT_DELETE_FINISHED_EVENTS_AFTER;
@@ -71,7 +77,6 @@ class EventQueueProcessorBase {
71
77
  this.__txUsageAllowed = true;
72
78
  this.__txMap = {};
73
79
  this.__txRollback = {};
74
- this.#config = eventQueueConfig.getConfigInstance();
75
80
  this.__queueEntries = [];
76
81
  }
77
82
 
@@ -303,6 +308,29 @@ class EventQueueProcessorBase {
303
308
  return Object.fromEntries(queueEntries.map((queueEntry) => [queueEntry.ID, EventProcessingStatus.Error]));
304
309
  }
305
310
 
311
+ handleErrorDuringPeriodicEventProcessing(error, queueEntry) {
312
+ this.logger.error(
313
+ `Caught error during event periodic processing. Please catch your promises/exceptions. Error: ${error}`,
314
+ {
315
+ eventType: this.#eventType,
316
+ eventSubType: this.#eventSubType,
317
+ queueEntryId: queueEntry.ID,
318
+ }
319
+ );
320
+ }
321
+
322
+ async setPeriodicEventStatus(queueEntryIds) {
323
+ await this.tx.run(
324
+ UPDATE.entity(this.#config.tableNameEventQueue)
325
+ .set({
326
+ status: EventProcessingStatus.Done,
327
+ })
328
+ .where({
329
+ ID: queueEntryIds,
330
+ })
331
+ );
332
+ }
333
+
306
334
  /**
307
335
  * This function validates for all selected events one status has been submitted. It's also validated that only for
308
336
  * selected events a status has been submitted. Persisting the status of events is done in a dedicated database tx.
@@ -587,9 +615,8 @@ class EventQueueProcessorBase {
587
615
  }
588
616
 
589
617
  #handleDelayedEvents(delayedEvents) {
590
- const eventSchedulerInstance = eventScheduler.getInstance();
591
618
  for (const delayedEvent of delayedEvents) {
592
- eventSchedulerInstance.scheduleEvent(
619
+ this.#eventSchedulerInstance.scheduleEvent(
593
620
  this.__context.tenant,
594
621
  this.#eventType,
595
622
  this.#eventSubType,
@@ -812,6 +839,77 @@ class EventQueueProcessorBase {
812
839
  }
813
840
  }
814
841
 
842
+ async scheduleNextPeriodEvent(queueEntry) {
843
+ const interval = this.#eventConfig.interval;
844
+ const newEvent = {
845
+ type: this.#eventType,
846
+ subType: this.#eventSubType,
847
+ startAfter: new Date(new Date(queueEntry.startAfter).getTime() + interval * 1000),
848
+ };
849
+ const { relative } = this.#eventSchedulerInstance.calculateOffset(
850
+ this.#eventType,
851
+ this.#eventSubType,
852
+ newEvent.startAfter
853
+ );
854
+
855
+ // more than one interval behind - shift tick to keep up
856
+ if (relative < 0 && Math.abs(relative) >= this.#eventConfig.interval * 1000) {
857
+ newEvent.startAfter = new Date(Date.now() + 5 * 1000);
858
+ this.logger.info("interval adjusted because shifted more than one interval", {
859
+ eventType: this.#eventType,
860
+ eventSubType: this.#eventSubType,
861
+ newStartAfter: newEvent.startAfter,
862
+ });
863
+ }
864
+
865
+ this.tx._skipEventQueueBroadcase = true;
866
+ await this.tx.run(INSERT.into(this.#config.tableNameEventQueue).entries({ ...newEvent }));
867
+ this.tx._skipEventQueueBroadcase = false;
868
+ if (interval < this.#config.runInterval) {
869
+ this.#handleDelayedEvents([newEvent]);
870
+ const { relative: relativeAfterSchedule } = this.#eventSchedulerInstance.calculateOffset(
871
+ this.#eventType,
872
+ this.#eventSubType,
873
+ newEvent.startAfter
874
+ );
875
+ // next tick is already behind schedule --> execute direct
876
+ if (relativeAfterSchedule <= 0) {
877
+ this.logger.info("running behind schedule - executing next tick immediately", {
878
+ eventType: this.#eventType,
879
+ eventSubType: this.#eventSubType,
880
+ newStartAfter: newEvent.startAfter,
881
+ });
882
+ return true;
883
+ }
884
+ }
885
+ }
886
+
887
+ async handleDuplicatedPeriodicEventEntry(queueEntries) {
888
+ this.logger.error("More than one open events for the same configuration which is not allowed!", {
889
+ eventType: this.#eventType,
890
+ eventSubType: this.#eventSubType,
891
+ queueEntriesIds: queueEntries.map(({ ID }) => ID),
892
+ });
893
+
894
+ let queueEntryToUse;
895
+ const obsoleteEntries = [];
896
+ for (const queueEntry of queueEntries) {
897
+ if (!queueEntryToUse) {
898
+ queueEntryToUse = queueEntry;
899
+ continue;
900
+ }
901
+
902
+ if (queueEntryToUse.startAfter <= queueEntry.queueEntry) {
903
+ obsoleteEntries.push(queueEntryToUse);
904
+ queueEntryToUse = queueEntry;
905
+ } else {
906
+ obsoleteEntries.push(queueEntry);
907
+ }
908
+ }
909
+ await this.setPeriodicEventStatus(obsoleteEntries.map(({ ID }) => ID));
910
+ return queueEntryToUse;
911
+ }
912
+
815
913
  statusMapContainsError(statusMap) {
816
914
  return Object.values(statusMap).includes(EventProcessingStatus.Error);
817
915
  }
@@ -920,6 +1018,10 @@ class EventQueueProcessorBase {
920
1018
  setTxForEventProcessing(key, tx) {
921
1019
  this.__txMap[key] = tx;
922
1020
  }
1021
+
1022
+ get isPeriodicEvent() {
1023
+ return this.#eventConfig.isPeriodic;
1024
+ }
923
1025
  }
924
1026
 
925
1027
  module.exports = EventQueueProcessorBase;
package/src/config.js CHANGED
@@ -4,13 +4,13 @@ const cds = require("@sap/cds");
4
4
 
5
5
  const { getEnvInstance } = require("./shared/env");
6
6
  const redis = require("./shared/redis");
7
-
8
- let instance;
7
+ const EventQueueError = require("./EventQueueError");
9
8
 
10
9
  const FOR_UPDATE_TIMEOUT = 10;
11
10
  const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
12
11
  const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
13
12
  const COMPONENT_NAME = "eventQueue/config";
13
+ const MIN_INTERVAL_SEC = 10;
14
14
 
15
15
  class Config {
16
16
  #logger;
@@ -30,6 +30,8 @@ class Config {
30
30
  #disableRedis;
31
31
  #env;
32
32
  #eventMap;
33
+ #updatePeriodicEvents;
34
+ static #instance;
33
35
  constructor() {
34
36
  this.#logger = cds.log(COMPONENT_NAME);
35
37
  this.#config = null;
@@ -50,11 +52,11 @@ class Config {
50
52
  }
51
53
 
52
54
  getEventConfig(type, subType) {
53
- return this.#eventMap[[type, subType].join("##")];
55
+ return this.#eventMap[this.generateKey(type, subType)];
54
56
  }
55
57
 
56
58
  hasEventAfterCommitFlag(type, subType) {
57
- return this.#eventMap[[type, subType].join("##")]?.processAfterCommit ?? true;
59
+ return this.#eventMap[this.generateKey(type, subType)]?.processAfterCommit ?? true;
58
60
  }
59
61
 
60
62
  _checkRedisIsBound() {
@@ -101,10 +103,51 @@ class Config {
101
103
 
102
104
  set fileContent(config) {
103
105
  this.#config = config;
106
+ config.events = config.events ?? [];
107
+ config.periodicEvents = config.periodicEvents ?? [];
104
108
  this.#eventMap = config.events.reduce((result, event) => {
109
+ this.validateAdHocEvents(result, event);
105
110
  result[[event.type, event.subType].join("##")] = event;
106
111
  return result;
107
112
  }, {});
113
+ this.#eventMap = config.periodicEvents.reduce((result, event) => {
114
+ const SUFFIX_PERIODIC = "_PERIODIC";
115
+ event.type = `${event.type}${SUFFIX_PERIODIC}`;
116
+ event.isPeriodic = true;
117
+ this.validatePeriodicConfig(result, event);
118
+ result[[event.type, event.subType].join("##")] = event;
119
+ return result;
120
+ }, this.#eventMap);
121
+ }
122
+
123
+ validatePeriodicConfig(eventMap, config) {
124
+ const key = this.generateKey(config.type, config.subType);
125
+ if (eventMap[key] && eventMap[key].isPeriodic) {
126
+ throw EventQueueError.duplicateEventRegistration(config.type, config.subType);
127
+ }
128
+
129
+ if (!config.interval || config.interval <= MIN_INTERVAL_SEC) {
130
+ throw EventQueueError.invalidInterval(config.type, config.subType, config.interval);
131
+ }
132
+
133
+ if (!config.impl) {
134
+ throw EventQueueError.missingImpl(config.type, config.subType);
135
+ }
136
+ }
137
+
138
+ validateAdHocEvents(eventMap, config) {
139
+ const key = this.generateKey(config.type, config.subType);
140
+ if (eventMap[key] && !eventMap[key].isPeriodic) {
141
+ throw EventQueueError.duplicateEventRegistration(config.type, config.subType);
142
+ }
143
+
144
+ if (!config.impl) {
145
+ throw EventQueueError.missingImpl(config.type, config.subType);
146
+ }
147
+ }
148
+
149
+ generateKey(type, subType) {
150
+ return [type, subType].join("##");
108
151
  }
109
152
 
110
153
  get fileContent() {
@@ -115,6 +158,18 @@ class Config {
115
158
  return this.#config.events;
116
159
  }
117
160
 
161
+ get periodicEvents() {
162
+ return this.#config.periodicEvents;
163
+ }
164
+
165
+ isPeriodicEvent(type, subType) {
166
+ return this.#eventMap[this.generateKey(type, subType)]?.isPeriodic;
167
+ }
168
+
169
+ get allEvents() {
170
+ return this.#config.events.concat(this.#config.periodicEvents);
171
+ }
172
+
118
173
  get forUpdateTimeout() {
119
174
  return this.#forUpdateTimeout;
120
175
  }
@@ -211,18 +266,29 @@ class Config {
211
266
  return this.#disableRedis;
212
267
  }
213
268
 
269
+ set updatePeriodicEvents(value) {
270
+ this.#updatePeriodicEvents = value;
271
+ }
272
+
273
+ get updatePeriodicEvents() {
274
+ return this.#updatePeriodicEvents;
275
+ }
276
+
214
277
  get isMultiTenancy() {
215
278
  return !!cds.requires.multitenancy;
216
279
  }
217
- }
218
280
 
219
- const getConfigInstance = () => {
220
- if (!instance) {
221
- instance = new Config();
281
+ /**
282
+ @return { Config }
283
+ **/
284
+ static get instance() {
285
+ if (!Config.#instance) {
286
+ Config.#instance = new Config();
287
+ }
288
+ return Config.#instance;
222
289
  }
223
- return instance;
224
- };
290
+ }
291
+
292
+ const instance = Config.instance;
225
293
 
226
- module.exports = {
227
- getConfigInstance,
228
- };
294
+ module.exports = instance;
package/src/dbHandler.js CHANGED
@@ -8,9 +8,11 @@ const config = require("./config");
8
8
  const COMPONENT_NAME = "eventQueue/dbHandler";
9
9
 
10
10
  const registerEventQueueDbHandler = (dbService) => {
11
- const configInstance = config.getConfigInstance();
12
- const def = dbService.model.definitions[configInstance.tableNameEventQueue];
11
+ const def = dbService.model.definitions[config.tableNameEventQueue];
13
12
  dbService.after("CREATE", def, (_, req) => {
13
+ if (req.tx._skipEventQueueBroadcase) {
14
+ return;
15
+ }
14
16
  req.tx._ = req.tx._ ?? {};
15
17
  req.tx._.eventQueuePublishEvents = req.tx._.eventQueuePublishEvents ?? {};
16
18
  const eventQueuePublishEvents = req.tx._.eventQueuePublishEvents;
@@ -18,7 +20,7 @@ const registerEventQueueDbHandler = (dbService) => {
18
20
  const eventCombinations = Object.keys(
19
21
  data.reduce((result, event) => {
20
22
  const key = [event.type, event.subType].join("##");
21
- if (!configInstance.hasEventAfterCommitFlag(event.type, event.subType) || eventQueuePublishEvents[key]) {
23
+ if (!config.hasEventAfterCommitFlag(event.type, event.subType) || eventQueuePublishEvents[key]) {
22
24
  return result;
23
25
  }
24
26
  eventQueuePublishEvents[key] = true;
package/src/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  module.exports = {
13
13
  ...require("./initialize"),
14
- ...require("./config"),
14
+ config: require("./config"),
15
15
  ...require("./processEventQueue"),
16
16
  ...require("./dbHandler"),
17
17
  ...require("./constants"),
package/src/initialize.js CHANGED
@@ -11,7 +11,7 @@ const VError = require("verror");
11
11
  const EventQueueError = require("./EventQueueError");
12
12
  const runner = require("./runner");
13
13
  const dbHandler = require("./dbHandler");
14
- const { getConfigInstance } = require("./config");
14
+ const config = require("./config");
15
15
  const { initEventQueueRedisSubscribe, closeSubscribeClient } = require("./redisPubSub");
16
16
  const { closeMainClient } = require("./shared/redis");
17
17
 
@@ -34,6 +34,7 @@ const CONFIG_VARS = [
34
34
  ["tableNameEventLock", BASE_TABLES.LOCK],
35
35
  ["disableRedis", false],
36
36
  ["skipCsnCheck", false],
37
+ ["updatePeriodicEvents", true],
37
38
  ];
38
39
 
39
40
  const initialize = async ({
@@ -47,16 +48,16 @@ const initialize = async ({
47
48
  tableNameEventLock,
48
49
  disableRedis,
49
50
  skipCsnCheck,
51
+ updatePeriodicEvents,
50
52
  } = {}) => {
51
53
  // TODO: initialize check:
52
54
  // - content of yaml check
53
55
  // - betweenRuns and parallelTenantProcessing
54
56
 
55
- const configInstance = getConfigInstance();
56
- if (configInstance.initialized) {
57
+ if (config.initialized) {
57
58
  return;
58
59
  }
59
- configInstance.initialized = true;
60
+ config.initialized = true;
60
61
 
61
62
  mixConfigVarsWithEnv(
62
63
  configFilePath,
@@ -68,28 +69,29 @@ const initialize = async ({
68
69
  tableNameEventQueue,
69
70
  tableNameEventLock,
70
71
  disableRedis,
71
- skipCsnCheck
72
+ skipCsnCheck,
73
+ updatePeriodicEvents
72
74
  );
73
75
 
74
76
  const logger = cds.log(COMPONENT);
75
- configInstance.fileContent = await readConfigFromFile(configInstance.configFilePath);
76
- configInstance.checkRedisEnabled();
77
+ config.fileContent = await readConfigFromFile(config.configFilePath);
78
+ config.checkRedisEnabled();
77
79
 
78
80
  const dbService = await cds.connect.to("db");
79
81
  await (cds.model ? Promise.resolve() : new Promise((resolve) => cds.on("serving", resolve)));
80
- !configInstance.skipCsnCheck && (await csnCheck());
81
- if (configInstance.processEventsAfterPublish) {
82
+ !config.skipCsnCheck && (await csnCheck());
83
+ if (config.processEventsAfterPublish) {
82
84
  dbHandler.registerEventQueueDbHandler(dbService);
83
85
  }
84
86
 
85
87
  registerEventProcessors();
86
88
  registerCdsShutdown();
87
89
  logger.info("event queue initialized", {
88
- registerAsEventProcessor: configInstance.registerAsEventProcessor,
89
- multiTenancyEnabled: configInstance.isMultiTenancy,
90
- redisEnabled: configInstance.redisEnabled,
91
- runInterval: configInstance.runInterval,
92
- parallelTenantProcessing: configInstance.parallelTenantProcessing,
90
+ registerAsEventProcessor: config.registerAsEventProcessor,
91
+ multiTenancyEnabled: config.isMultiTenancy,
92
+ redisEnabled: config.redisEnabled,
93
+ runInterval: config.runInterval,
94
+ config: config.parallelTenantProcessing,
93
95
  });
94
96
  };
95
97
 
@@ -111,42 +113,38 @@ const readConfigFromFile = async (configFilepath) => {
111
113
  };
112
114
 
113
115
  const registerEventProcessors = () => {
114
- const configInstance = getConfigInstance();
115
-
116
- if (!configInstance.registerAsEventProcessor) {
116
+ if (!config.registerAsEventProcessor) {
117
117
  return;
118
118
  }
119
119
 
120
- if (!configInstance.isMultiTenancy) {
121
- runner.singleTenant();
120
+ const errorHandler = (err) => cds.log(COMPONENT).error("error during init runner", err);
121
+
122
+ if (!config.isMultiTenancy) {
123
+ runner.singleTenant().catch(errorHandler);
122
124
  return;
123
125
  }
124
126
 
125
- if (configInstance.redisEnabled) {
127
+ if (config.redisEnabled) {
126
128
  initEventQueueRedisSubscribe();
127
- configInstance.attachConfigChangeHandler();
128
- runner.multiTenancyRedis();
129
+ config.attachConfigChangeHandler();
130
+ runner.multiTenancyRedis().catch(errorHandler);
129
131
  } else {
130
- runner.multiTenancyDb();
132
+ runner.multiTenancyDb().catch(errorHandler);
131
133
  }
132
134
  };
133
135
 
134
136
  const csnCheck = async () => {
135
- const configInstance = getConfigInstance();
136
- const eventCsn = cds.model.definitions[configInstance.tableNameEventQueue];
137
+ const eventCsn = cds.model.definitions[config.tableNameEventQueue];
137
138
  if (!eventCsn) {
138
- throw EventQueueError.missingTableInCsn(configInstance.tableNameEventQueue);
139
+ throw EventQueueError.missingTableInCsn(config.tableNameEventQueue);
139
140
  }
140
141
 
141
- const lockCsn = cds.model.definitions[configInstance.tableNameEventLock];
142
+ const lockCsn = cds.model.definitions[config.tableNameEventLock];
142
143
  if (!lockCsn) {
143
- throw EventQueueError.missingTableInCsn(configInstance.tableNameEventLock);
144
+ throw EventQueueError.missingTableInCsn(config.tableNameEventLock);
144
145
  }
145
146
 
146
- if (
147
- configInstance.tableNameEventQueue === BASE_TABLES.EVENT &&
148
- configInstance.tableNameEventLock === BASE_TABLES.LOCK
149
- ) {
147
+ if (config.tableNameEventQueue === BASE_TABLES.EVENT && config.tableNameEventLock === BASE_TABLES.LOCK) {
150
148
  return; // no need to check base tables
151
149
  }
152
150
 
@@ -176,10 +174,9 @@ const checkCustomTable = (baseCsn, customCsn) => {
176
174
  };
177
175
 
178
176
  const mixConfigVarsWithEnv = (...args) => {
179
- const configInstance = getConfigInstance();
180
177
  CONFIG_VARS.forEach(([configName, defaultValue], index) => {
181
178
  const configValue = args[index];
182
- configInstance[configName] = configValue ?? cds.env.eventQueue?.[configName] ?? defaultValue;
179
+ config[configName] = configValue ?? cds.env.eventQueue?.[configName] ?? defaultValue;
183
180
  });
184
181
  };
185
182