@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.
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+
3
+ const cds = require("@sap/cds");
4
+
5
+ const { EventProcessingStatus } = require("./constants");
6
+ const { processChunkedSync } = require("./shared/common");
7
+ const eventConfig = require("./config");
8
+
9
+ const COMPONENT_NAME = "eventQueue/periodicEvents";
10
+
11
+ const checkAndInsertPeriodicEvents = async (context) => {
12
+ const tx = cds.tx(context);
13
+ const baseCqn = SELECT.from(eventConfig.tableNameEventQueue)
14
+ .where([
15
+ { list: [{ ref: ["type"] }, { ref: ["subType"] }] },
16
+ "IN",
17
+ {
18
+ list: eventConfig.periodicEvents.map((periodicEvent) => ({
19
+ list: [{ val: periodicEvent.type }, { val: periodicEvent.subType }],
20
+ })),
21
+ },
22
+ "AND",
23
+ { ref: ["status"] },
24
+ "=",
25
+ { val: EventProcessingStatus.Open },
26
+ ])
27
+ .columns(["ID", "type", "subType", "startAfter"]);
28
+ const currentPeriodEvents = await tx.run(baseCqn);
29
+
30
+ if (!currentPeriodEvents.length) {
31
+ // fresh insert all
32
+ return await insertPeriodEvents(tx, eventConfig.periodicEvents);
33
+ }
34
+
35
+ const exitingEventMap = currentPeriodEvents.reduce((result, current) => {
36
+ const key = _generateKey(current);
37
+ result[key] = current;
38
+ return result;
39
+ }, {});
40
+
41
+ const { newEvents, existingEvents } = eventConfig.periodicEvents.reduce(
42
+ (result, event) => {
43
+ if (exitingEventMap[_generateKey(event)]) {
44
+ result.existingEvents.push(exitingEventMap[_generateKey(event)]);
45
+ } else {
46
+ result.newEvents.push(event);
47
+ }
48
+ return result;
49
+ },
50
+ { newEvents: [], existingEvents: [] }
51
+ );
52
+
53
+ const currentDate = new Date();
54
+ const exitingWithNotMatchingInterval = existingEvents.filter((existingEvent) => {
55
+ const config = eventConfig.getEventConfig(existingEvent.type, existingEvent.subType);
56
+ const eventStartAfter = new Date(existingEvent.startAfter);
57
+ // check if to far in future
58
+ const dueInWithNewInterval = new Date(currentDate.getTime() + config.interval * 1000);
59
+ return eventStartAfter >= dueInWithNewInterval;
60
+ });
61
+
62
+ exitingWithNotMatchingInterval.length &&
63
+ cds.log(COMPONENT_NAME).info("deleting periodic events because they have changed", {
64
+ changedEvents: exitingWithNotMatchingInterval.map(({ type, subType }) => ({ type, subType })),
65
+ });
66
+ await tx.run(
67
+ DELETE.from(eventConfig.tableNameEventQueue).where(
68
+ "ID IN",
69
+ exitingWithNotMatchingInterval.map(({ ID }) => ID)
70
+ )
71
+ );
72
+
73
+ const newOrChangedEvents = newEvents.concat(exitingWithNotMatchingInterval);
74
+
75
+ if (!newOrChangedEvents.length) {
76
+ return;
77
+ }
78
+
79
+ return await insertPeriodEvents(tx, newOrChangedEvents);
80
+ };
81
+
82
+ const insertPeriodEvents = async (tx, events) => {
83
+ const startAfter = new Date();
84
+ processChunkedSync(events, 4, (chunk) => {
85
+ cds.log(COMPONENT_NAME).info("inserting changed or new periodic events", {
86
+ events: chunk.map(({ type, subType }) => {
87
+ const { interval } = eventConfig.getEventConfig(type, subType);
88
+ return { type, subType, interval };
89
+ }),
90
+ });
91
+ });
92
+ const periodEventsInsert = events.map((periodicEvent) => ({
93
+ type: periodicEvent.type,
94
+ subType: periodicEvent.subType,
95
+ startAfter: startAfter,
96
+ }));
97
+
98
+ tx._skipEventQueueBroadcase = true;
99
+ await tx.run(INSERT.into(eventConfig.tableNameEventQueue).entries(periodEventsInsert));
100
+ tx._skipEventQueueBroadcase = false;
101
+ };
102
+
103
+ const _generateKey = ({ type, subType }) => [type, subType].join("##");
104
+
105
+ module.exports = {
106
+ checkAndInsertPeriodicEvents,
107
+ };
@@ -4,7 +4,7 @@ const pathLib = require("path");
4
4
 
5
5
  const cds = require("@sap/cds");
6
6
 
7
- const { getConfigInstance } = require("./config");
7
+ const config = require("./config");
8
8
  const { TransactionMode } = require("./constants");
9
9
  const { limiter, Funnel } = require("./shared/common");
10
10
 
@@ -29,7 +29,7 @@ const processEventQueue = async (context, eventType, eventSubType, startTime = n
29
29
  let baseInstance;
30
30
  try {
31
31
  let eventTypeInstance;
32
- const eventConfig = getConfigInstance().getEventConfig(eventType, eventSubType);
32
+ const eventConfig = config.getEventConfig(eventType, eventSubType);
33
33
  const [err, EventTypeClass] = resilientRequire(eventConfig?.impl);
34
34
  if (!eventConfig || err || !(typeof EventTypeClass.constructor === "function")) {
35
35
  cds.log(COMPONENT_NAME).error("No Implementation found in the provided configuration file.", {
@@ -43,6 +43,9 @@ const processEventQueue = async (context, eventType, eventSubType, startTime = n
43
43
  if (!continueProcessing) {
44
44
  return;
45
45
  }
46
+ if (baseInstance.isPeriodicEvent) {
47
+ return await processPeriodicEvent(baseInstance);
48
+ }
46
49
  eventConfig.startTime = startTime;
47
50
  while (shouldContinue) {
48
51
  iterationCounter++;
@@ -131,6 +134,75 @@ const reevaluateShouldContinue = (eventTypeInstance, iterationCounter, startTime
131
134
  return false;
132
135
  };
133
136
 
137
+ // TODO: don't forget to release lock
138
+ const processPeriodicEvent = async (eventTypeInstance) => {
139
+ let queueEntry;
140
+ let processNext = true;
141
+
142
+ try {
143
+ while (processNext) {
144
+ await executeInNewTransaction(
145
+ eventTypeInstance.context,
146
+ `eventQueue-periodic-scheduleNext-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
147
+ async (tx) => {
148
+ eventTypeInstance.processEventContext = tx.context;
149
+ const queueEntries = await eventTypeInstance.getQueueEntriesAndSetToInProgress();
150
+ if (!queueEntries.length) {
151
+ return;
152
+ }
153
+ if (queueEntries.length > 1) {
154
+ queueEntry = await eventTypeInstance.handleDuplicatedPeriodicEventEntry(queueEntries);
155
+ } else {
156
+ queueEntry = queueEntries[0];
157
+ }
158
+ processNext = await eventTypeInstance.scheduleNextPeriodEvent(queueEntry);
159
+ }
160
+ );
161
+
162
+ if (!queueEntry) {
163
+ return;
164
+ }
165
+
166
+ await executeInNewTransaction(
167
+ eventTypeInstance.context,
168
+ `eventQueue-periodic-process-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
169
+ async (tx) => {
170
+ eventTypeInstance.processEventContext = tx.context;
171
+ eventTypeInstance.setTxForEventProcessing(queueEntry.ID, cds.tx(tx.context));
172
+ try {
173
+ await eventTypeInstance.processEvent(tx.context, queueEntry.ID, [queueEntry]);
174
+ } catch (err) {
175
+ eventTypeInstance.handleErrorDuringPeriodicEventProcessing(err, queueEntry);
176
+ throw new TriggerRollback();
177
+ }
178
+ if (
179
+ eventTypeInstance.transactionMode !== TransactionMode.alwaysCommit ||
180
+ eventTypeInstance.shouldRollbackTransaction(queueEntry.ID)
181
+ ) {
182
+ throw new TriggerRollback();
183
+ }
184
+ }
185
+ );
186
+
187
+ await executeInNewTransaction(
188
+ eventTypeInstance.context,
189
+ `eventQueue-periodic-setStatus-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
190
+ async (tx) => {
191
+ eventTypeInstance.processEventContext = tx.context;
192
+ await eventTypeInstance.setPeriodicEventStatus(queueEntry.ID);
193
+ }
194
+ );
195
+ }
196
+ } catch (err) {
197
+ cds.log(COMPONENT_NAME).error("Processing periodic events failed with unexpected error. Error:", err, {
198
+ eventType: eventTypeInstance?.eventType,
199
+ eventSubType: eventTypeInstance?.eventSubType,
200
+ });
201
+ } finally {
202
+ await eventTypeInstance?.handleReleaseLock();
203
+ }
204
+ };
205
+
134
206
  const processEventMap = async (eventTypeInstance) => {
135
207
  eventTypeInstance.startPerformanceTracerEvents();
136
208
  await eventTypeInstance.beforeProcessingEvents();
@@ -21,27 +21,34 @@ const EventQueueError = require("./EventQueueError");
21
21
  * createdAt: Timestamp, // Timestamp of event creation. This field is automatically set on insert.
22
22
  * startAfter: Timestamp, // Timestamp indicating when the event should start after.
23
23
  * }
24
+ * @param {Boolean} skipBroadcast - (Optional) If set to true, event broadcasting will be skipped. Defaults to false.
24
25
  * @throws {EventQueueError} Throws an error if the configuration is not initialized.
25
26
  * @throws {EventQueueError} Throws an error if the event type is unknown.
26
27
  * @throws {EventQueueError} Throws an error if the startAfter field is not a valid date.
27
28
  * @returns {Promise} Returns a promise which resolves to the result of the database insert operation.
28
29
  */
29
- const publishEvent = async (tx, events) => {
30
- const configInstance = config.getConfigInstance();
31
- if (!configInstance.initialized) {
30
+ const publishEvent = async (tx, events, skipBroadcast = false) => {
31
+ if (!config.initialized) {
32
32
  throw EventQueueError.notInitialized();
33
33
  }
34
34
  const eventsForProcessing = Array.isArray(events) ? events : [events];
35
35
  for (const { type, subType, startAfter } of eventsForProcessing) {
36
- const eventConfig = configInstance.getEventConfig(type, subType);
36
+ const eventConfig = config.getEventConfig(type, subType);
37
37
  if (!eventConfig) {
38
38
  throw EventQueueError.unknownEventType(type, subType);
39
39
  }
40
40
  if (startAfter && !common.isValidDate(startAfter)) {
41
41
  throw EventQueueError.malformedDate(startAfter);
42
42
  }
43
+
44
+ if (eventConfig.isPeriodic) {
45
+ throw EventQueueError.manuelPeriodicEventInsert(type, subType);
46
+ }
43
47
  }
44
- return await tx.run(INSERT.into(configInstance.tableNameEventQueue).entries(eventsForProcessing));
48
+ tx._skipEventQueueBroadcase = skipBroadcast;
49
+ const result = await tx.run(INSERT.into(config.tableNameEventQueue).entries(eventsForProcessing));
50
+ tx._skipEventQueueBroadcase = false;
51
+ return result;
45
52
  };
46
53
 
47
54
  module.exports = {
@@ -11,7 +11,7 @@ const COMPONENT_NAME = "eventQueue/redisPubSub";
11
11
  let subscriberClientPromise;
12
12
 
13
13
  const initEventQueueRedisSubscribe = () => {
14
- if (subscriberClientPromise || !config.getConfigInstance().redisEnabled) {
14
+ if (subscriberClientPromise || !config.redisEnabled) {
15
15
  return;
16
16
  }
17
17
  redis.subscribeRedisChannel(EVENT_MESSAGE_CHANNEL, messageHandlerProcessEvents);
@@ -36,9 +36,8 @@ const messageHandlerProcessEvents = async (messageData) => {
36
36
 
37
37
  const broadcastEvent = async (tenantId, type, subType) => {
38
38
  const logger = cds.log(COMPONENT_NAME);
39
- const configInstance = config.getConfigInstance();
40
- if (!configInstance.redisEnabled) {
41
- if (configInstance.registerAsEventProcessor) {
39
+ if (!config.redisEnabled) {
40
+ if (config.registerAsEventProcessor) {
42
41
  await runEventCombinationForTenant(tenantId, type, subType);
43
42
  }
44
43
  return;
package/src/runner.js CHANGED
@@ -9,22 +9,27 @@ const cdsHelper = require("./shared/cdsHelper");
9
9
  const distributedLock = require("./shared/distributedLock");
10
10
  const SetIntervalDriftSafe = require("./shared/SetIntervalDriftSafe");
11
11
  const { getSubdomainForTenantId } = require("./shared/cdsHelper");
12
+ const periodicEvents = require("./periodicEvents");
13
+ const { hashStringTo32Bit } = require("./shared/common");
12
14
 
13
15
  const COMPONENT_NAME = "eventQueue/runner";
14
16
  const EVENT_QUEUE_RUN_ID = "EVENT_QUEUE_RUN_ID";
15
17
  const EVENT_QUEUE_RUN_TS = "EVENT_QUEUE_RUN_TS";
18
+ const EVENT_QUEUE_RUN_PERIODIC_EVENT = "EVENT_QUEUE_RUN_PERIODIC_EVENT";
16
19
  const OFFSET_FIRST_RUN = 10 * 1000;
17
20
 
18
- const singleTenant = () => _scheduleFunction(_executeRunForTenant);
21
+ let tenantIdHash;
22
+ let singleRunDone;
19
23
 
20
- const multiTenancyDb = () => _scheduleFunction(_multiTenancyDb);
24
+ const singleTenant = () => _scheduleFunction(_checkPeriodicEventsSingleTenant, _executeRunForTenant);
21
25
 
22
- const multiTenancyRedis = () => _scheduleFunction(_multiTenancyRedis);
26
+ const multiTenancyDb = () => _scheduleFunction(_multiTenancyPeriodicEvents, _multiTenancyDb);
23
27
 
24
- const _scheduleFunction = async (fn) => {
28
+ const multiTenancyRedis = () => _scheduleFunction(_multiTenancyPeriodicEvents, _multiTenancyRedis);
29
+
30
+ const _scheduleFunction = async (singleRunFn, periodicFn) => {
25
31
  const logger = cds.log(COMPONENT_NAME);
26
- const configInstance = eventQueueConfig.getConfigInstance();
27
- const eventsForAutomaticRun = configInstance.events;
32
+ const eventsForAutomaticRun = eventQueueConfig.allEvents;
28
33
  if (!eventsForAutomaticRun.length) {
29
34
  logger.warn("no events for automatic run are configured - skipping runner registration");
30
35
  return;
@@ -32,11 +37,15 @@ const _scheduleFunction = async (fn) => {
32
37
 
33
38
  const fnWithRunningCheck = () => {
34
39
  const logger = cds.log(COMPONENT_NAME);
35
- if (configInstance.isRunnerDeactivated) {
40
+ if (eventQueueConfig.isRunnerDeactivated) {
36
41
  logger.info("runner is deactivated via config variable. Skipping this run.");
37
42
  return;
38
43
  }
39
- return fn();
44
+ if (!singleRunDone) {
45
+ singleRunDone = true;
46
+ singleRunFn().catch(() => (singleRunDone = false));
47
+ }
48
+ return periodicFn();
40
49
  };
41
50
 
42
51
  const offsetDependingOnLastRun = await _calculateOffsetForFirstRun();
@@ -47,7 +56,7 @@ const _scheduleFunction = async (fn) => {
47
56
 
48
57
  setTimeout(() => {
49
58
  fnWithRunningCheck();
50
- const intervalRunner = new SetIntervalDriftSafe(configInstance.runInterval);
59
+ const intervalRunner = new SetIntervalDriftSafe(eventQueueConfig.runInterval);
51
60
  intervalRunner.run(fnWithRunningCheck);
52
61
  }, offsetDependingOnLastRun).unref();
53
62
  };
@@ -57,6 +66,8 @@ const _multiTenancyRedis = async () => {
57
66
  const emptyContext = new cds.EventContext({});
58
67
  logger.info("executing event queue run for multi instance and tenant");
59
68
  const tenantIds = await cdsHelper.getAllTenantIds();
69
+ _checkAndTriggerPeriodicEventUpdate(tenantIds);
70
+
60
71
  const runId = await _acquireRunId(emptyContext);
61
72
 
62
73
  if (!runId) {
@@ -67,33 +78,33 @@ const _multiTenancyRedis = async () => {
67
78
  _executeAllTenants(tenantIds, runId);
68
79
  };
69
80
 
70
- const _multiTenancyDb = async () => {
71
- const logger = cds.log(COMPONENT_NAME);
72
- try {
73
- logger.info("executing event queue run for single instance and multi tenant");
74
- const tenantIds = await cdsHelper.getAllTenantIds();
75
- _executeAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
76
- } catch (err) {
77
- logger.error(
78
- `Couldn't fetch tenant ids for event queue processing! Next try after defined interval. Error: ${err}`
79
- );
81
+ const _checkAndTriggerPeriodicEventUpdate = (tenantIds) => {
82
+ const hash = hashStringTo32Bit(JSON.stringify(tenantIds));
83
+ if (!tenantIdHash) {
84
+ tenantIdHash = hash;
85
+ return;
86
+ }
87
+ if (tenantIdHash && tenantIdHash !== hash) {
88
+ cds.log(COMPONENT_NAME).info("tenant id hash changed, triggering updating periodic events!");
89
+ _multiTenancyPeriodicEvents().catch((err) => {
90
+ cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events! Error:", err);
91
+ });
80
92
  }
81
93
  };
82
94
 
83
- const _executeAllTenants = (tenantIds, runId) => {
84
- const configInstance = eventQueueConfig.getConfigInstance();
95
+ const _executeAllTenantsGeneric = (tenantIds, runId, fn) => {
85
96
  const workerQueueInstance = getWorkerPoolInstance();
86
97
  tenantIds.forEach((tenantId) => {
87
98
  workerQueueInstance.addToQueue(async () => {
88
99
  try {
89
100
  const tenantContext = new cds.EventContext({ tenant: tenantId });
90
101
  const couldAcquireLock = await distributedLock.acquireLock(tenantContext, runId, {
91
- expiryTime: configInstance.runInterval * 0.95,
102
+ expiryTime: eventQueueConfig.runInterval * 0.95,
92
103
  });
93
104
  if (!couldAcquireLock) {
94
105
  return;
95
106
  }
96
- await _executeRunForTenant(tenantId, runId);
107
+ await fn(tenantId, runId);
97
108
  } catch (err) {
98
109
  cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
99
110
  tenantId,
@@ -103,11 +114,15 @@ const _executeAllTenants = (tenantIds, runId) => {
103
114
  });
104
115
  };
105
116
 
117
+ const _executeAllTenants = (tenantIds, runId) => _executeAllTenantsGeneric(tenantIds, runId, _executeRunForTenant);
118
+
119
+ const _executePeriodicEventsAllTenants = (tenantIds, runId) =>
120
+ _executeAllTenantsGeneric(tenantIds, runId, _checkPeriodicEventsSingleTenant);
121
+
106
122
  const _executeRunForTenant = async (tenantId, runId) => {
107
123
  const logger = cds.log(COMPONENT_NAME);
108
- const configInstance = eventQueueConfig.getConfigInstance();
109
124
  try {
110
- const eventsForAutomaticRun = configInstance.events;
125
+ const eventsForAutomaticRun = eventQueueConfig.allEvents;
111
126
  const subdomain = await cdsHelper.getSubdomainForTenantId(tenantId);
112
127
  const context = new cds.EventContext({
113
128
  tenant: tenantId,
@@ -124,23 +139,22 @@ const _executeRunForTenant = async (tenantId, runId) => {
124
139
  } catch (err) {
125
140
  logger.error(`Couldn't process eventQueue for tenant! Next try after defined interval. Error: ${err}`, {
126
141
  tenantId,
127
- redisEnabled: configInstance.redisEnabled,
142
+ redisEnabled: eventQueueConfig.redisEnabled,
128
143
  });
129
144
  }
130
145
  };
131
146
 
132
147
  const _acquireRunId = async (context) => {
133
- const configInstance = eventQueueConfig.getConfigInstance();
134
148
  let runId = randomUUID();
135
149
  const couldSetValue = await distributedLock.setValueWithExpire(context, EVENT_QUEUE_RUN_ID, runId, {
136
150
  tenantScoped: false,
137
- expiryTime: configInstance.runInterval * 0.95,
151
+ expiryTime: eventQueueConfig.runInterval * 0.95,
138
152
  });
139
153
 
140
154
  if (couldSetValue) {
141
155
  await distributedLock.setValueWithExpire(context, EVENT_QUEUE_RUN_TS, new Date().toISOString(), {
142
156
  tenantScoped: false,
143
- expiryTime: configInstance.runInterval,
157
+ expiryTime: eventQueueConfig.runInterval,
144
158
  overrideValue: true,
145
159
  });
146
160
  } else {
@@ -153,13 +167,12 @@ const _acquireRunId = async (context) => {
153
167
  };
154
168
 
155
169
  const _calculateOffsetForFirstRun = async () => {
156
- const configInstance = eventQueueConfig.getConfigInstance();
157
170
  let offsetDependingOnLastRun = OFFSET_FIRST_RUN;
158
171
  const now = Date.now();
159
172
  // NOTE: this is only supported with Redis, because this is a tenant agnostic information
160
173
  // currently there is no proper place to store this information beside t0 schema
161
174
  try {
162
- if (configInstance.redisEnabled) {
175
+ if (eventQueueConfig.redisEnabled) {
163
176
  const dummyContext = new cds.EventContext({});
164
177
  let lastRunTs = await distributedLock.checkLockExistsAndReturnValue(dummyContext, EVENT_QUEUE_RUN_TS, {
165
178
  tenantScoped: false,
@@ -168,7 +181,7 @@ const _calculateOffsetForFirstRun = async () => {
168
181
  const ts = new Date(now).toISOString();
169
182
  const couldSetValue = await distributedLock.setValueWithExpire(dummyContext, EVENT_QUEUE_RUN_TS, ts, {
170
183
  tenantScoped: false,
171
- expiryTime: configInstance.runInterval,
184
+ expiryTime: eventQueueConfig.runInterval,
172
185
  });
173
186
  if (couldSetValue) {
174
187
  lastRunTs = ts;
@@ -178,7 +191,7 @@ const _calculateOffsetForFirstRun = async () => {
178
191
  });
179
192
  }
180
193
  }
181
- offsetDependingOnLastRun = new Date(lastRunTs).getTime() + configInstance.runInterval - now;
194
+ offsetDependingOnLastRun = new Date(lastRunTs).getTime() + eventQueueConfig.runInterval - now;
182
195
  }
183
196
  } catch (err) {
184
197
  cds
@@ -211,6 +224,59 @@ const runEventCombinationForTenant = async (tenantId, type, subType) => {
211
224
  }
212
225
  };
213
226
 
227
+ const _multiTenancyDb = async () => {
228
+ const logger = cds.log(COMPONENT_NAME);
229
+ try {
230
+ logger.info("executing event queue run for single instance and multi tenant");
231
+ const tenantIds = await cdsHelper.getAllTenantIds();
232
+ _checkAndTriggerPeriodicEventUpdate(tenantIds);
233
+ _executeAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
234
+ } catch (err) {
235
+ logger.error(
236
+ `Couldn't fetch tenant ids for event queue processing! Next try after defined interval. Error: ${err}`
237
+ );
238
+ }
239
+ };
240
+
241
+ const _multiTenancyPeriodicEvents = async () => {
242
+ const logger = cds.log(COMPONENT_NAME);
243
+ try {
244
+ logger.info("executing event queue update periodic events");
245
+ const tenantIds = await cdsHelper.getAllTenantIds();
246
+ _executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
247
+ } catch (err) {
248
+ logger.error(`Couldn't fetch tenant ids for updating periodic event processing! Error: ${err}`);
249
+ }
250
+ };
251
+
252
+ const _checkPeriodicEventsSingleTenant = async (tenantId) => {
253
+ const logger = cds.log(COMPONENT_NAME);
254
+ if (!eventQueueConfig.updatePeriodicEvents) {
255
+ logger.info("updating of periodic events is disabled");
256
+ }
257
+ try {
258
+ const subdomain = await cdsHelper.getSubdomainForTenantId(tenantId);
259
+ const context = new cds.EventContext({
260
+ tenant: tenantId,
261
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
262
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
263
+ });
264
+ cds.context = context;
265
+ logger.info("executing updating periotic events", {
266
+ tenantId,
267
+ subdomain,
268
+ });
269
+ await cdsHelper.executeInNewTransaction(context, "update-periodic-events", async (tx) => {
270
+ await periodicEvents.checkAndInsertPeriodicEvents(tx.context);
271
+ });
272
+ } catch (err) {
273
+ logger.error(`Couldn't process eventQueue for tenant! Next try after defined interval. Error: ${err}`, {
274
+ tenantId,
275
+ redisEnabled: eventQueueConfig.redisEnabled,
276
+ });
277
+ }
278
+ };
279
+
214
280
  module.exports = {
215
281
  singleTenant,
216
282
  multiTenancyDb,
@@ -2,7 +2,7 @@
2
2
 
3
3
  const cds = require("@sap/cds");
4
4
 
5
- const { getConfigInstance } = require("../config");
5
+ const config = require("../config");
6
6
 
7
7
  const COMPONENT_NAME = "eventQueue/WorkerQueue";
8
8
 
@@ -56,8 +56,7 @@ class WorkerQueue {
56
56
  module.exports = {
57
57
  getWorkerPoolInstance: () => {
58
58
  if (!instance) {
59
- const configInstance = getConfigInstance();
60
- instance = new WorkerQueue(configInstance.parallelTenantProcessing);
59
+ instance = new WorkerQueue(config.parallelTenantProcessing);
61
60
  }
62
61
  return instance;
63
62
  },
@@ -94,7 +94,7 @@ class TriggerRollback extends VError {
94
94
  }
95
95
 
96
96
  const getSubdomainForTenantId = async (tenantId) => {
97
- if (!config.getConfigInstance().isMultiTenancy) {
97
+ if (!config.isMultiTenancy) {
98
98
  return null;
99
99
  }
100
100
  if (subdomainCache[tenantId]) {
@@ -111,7 +111,7 @@ const getSubdomainForTenantId = async (tenantId) => {
111
111
  };
112
112
 
113
113
  const getAllTenantIds = async () => {
114
- if (!config.getConfigInstance().isMultiTenancy) {
114
+ if (!config.isMultiTenancy) {
115
115
  return null;
116
116
  }
117
117
  const ssp = await cds.connect.to("cds.xt.SaasProvisioningService");
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
 
3
+ const crypto = require("crypto");
4
+
3
5
  const { floor, abs, min } = Math;
4
6
 
5
7
  const arrayToFlatMap = (array, key = "ID") => {
@@ -118,4 +120,16 @@ const isValidDate = (value) => {
118
120
  }
119
121
  };
120
122
 
121
- module.exports = { arrayToFlatMap, Funnel, limiter, isValidDate };
123
+ const processChunkedSync = (inputs, chunkSize, chunkHandler) => {
124
+ let start = 0;
125
+ while (start < inputs.length) {
126
+ let end = start + chunkSize > inputs.length ? inputs.length : start + chunkSize;
127
+ const chunk = inputs.slice(start, end);
128
+ chunkHandler(chunk);
129
+ start = end;
130
+ }
131
+ };
132
+
133
+ const hashStringTo32Bit = (value) => crypto.createHash("sha256").update(String(value)).digest("base64").slice(0, 32);
134
+
135
+ module.exports = { arrayToFlatMap, Funnel, limiter, isValidDate, processChunkedSync, hashStringTo32Bit };