@cap-js-community/event-queue 0.1.49 → 0.1.51

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/src/config.js CHANGED
@@ -23,7 +23,6 @@ class Config {
23
23
  this.__tableNameEventLock = null;
24
24
  this.__vcapServices = this._parseVcapServices();
25
25
  this.__isRunnerDeactivated = false;
26
- this.__eventsForAutomaticRun = null;
27
26
  this.__configFilePath = null;
28
27
  this.__processEventsAfterPublish = null;
29
28
  this.__skipCsnCheck = null;
@@ -34,9 +33,7 @@ class Config {
34
33
  }
35
34
 
36
35
  hasEventAfterCommitFlag(type, subType) {
37
- return (
38
- this.__eventMap[[type, subType].join("##")]?.processAfterCommit ?? true
39
- );
36
+ return this.__eventMap[[type, subType].join("##")]?.processAfterCommit ?? true;
40
37
  }
41
38
 
42
39
  _checkRedisIsBound() {
package/src/constants.js CHANGED
@@ -8,4 +8,9 @@ module.exports = {
8
8
  Error: 3,
9
9
  Exceeded: 4,
10
10
  },
11
+ TransactionMode: {
12
+ isolated: "isolated",
13
+ alwaysCommit: "alwaysCommit",
14
+ alwaysRollback: "alwaysRollback",
15
+ },
11
16
  };
package/src/dbHandler.js CHANGED
@@ -14,10 +14,7 @@ const registerEventQueueDbHandler = (dbService) => {
14
14
  const eventCombinations = Object.keys(
15
15
  data.reduce((result, event) => {
16
16
  const key = [event.type, event.subType].join("##");
17
- if (
18
- !configInstance.hasEventAfterCommitFlag(event.type, event.subType) ||
19
- eventQueuePublishEvents[key]
20
- ) {
17
+ if (!configInstance.hasEventAfterCommitFlag(event.type, event.subType) || eventQueuePublishEvents[key]) {
21
18
  return result;
22
19
  }
23
20
  eventQueuePublishEvents[key] = true;
package/src/initialize.js CHANGED
@@ -56,26 +56,13 @@ const initialize = async ({
56
56
  );
57
57
 
58
58
  const logger = cds.log(COMPONENT);
59
- configInstance.fileContent = await readConfigFromFile(
60
- configInstance.configFilePath
61
- );
59
+ configInstance.fileContent = await readConfigFromFile(configInstance.configFilePath);
62
60
  configInstance.calculateIsRedisEnabled();
63
61
 
64
62
  const dbService = await cds.connect.to("db");
65
- await (cds.model
66
- ? Promise.resolve()
67
- : new Promise((resolve) => cds.on("serving", resolve)));
63
+ await (cds.model ? Promise.resolve() : new Promise((resolve) => cds.on("serving", resolve)));
68
64
  !configInstance.skipCsnCheck && (await csnCheck());
69
65
  if (configInstance.processEventsAfterPublish) {
70
- // TODO: remove this as soon as CDS fixes the current plugin model issues --> cds 7
71
- if (BASE_TABLES.EVENT === configInstance.tableNameEventQueue) {
72
- cds.db.model.definitions[BASE_TABLES.EVENT] =
73
- cds.model.definitions[BASE_TABLES.EVENT];
74
- }
75
- if (BASE_TABLES.LOCK === configInstance.tableNameEventLock) {
76
- cds.db.model.definitions[BASE_TABLES.LOCK] =
77
- cds.model.definitions[BASE_TABLES.LOCK];
78
- }
79
66
  dbHandler.registerEventQueueDbHandler(dbService);
80
67
  }
81
68
 
@@ -161,8 +148,7 @@ const checkCustomTable = (baseCsn, customCsn) => {
161
148
 
162
149
  if (
163
150
  customCsn.elements[columnName].type !== "cds.Association" &&
164
- customCsn.elements[columnName].type !==
165
- baseCsn.elements[columnName].type &&
151
+ customCsn.elements[columnName].type !== baseCsn.elements[columnName].type &&
166
152
  columnName === "status" &&
167
153
  customCsn.elements[columnName].type !== "cds.Integer"
168
154
  ) {
@@ -183,32 +169,18 @@ const mixConfigVarsWithEnv = (
183
169
  ) => {
184
170
  const configInstance = getConfigInstance();
185
171
 
186
- configInstance.configFilePath =
187
- configFilePath ?? cds.env.eventQueue?.configFilePath;
172
+ configInstance.configFilePath = configFilePath ?? cds.env.eventQueue?.configFilePath;
188
173
  configInstance.registerAsEventProcessor =
189
- registerAsEventProcessor ??
190
- cds.env.eventQueue?.registerAsEventProcessor ??
191
- true;
174
+ registerAsEventProcessor ?? cds.env.eventQueue?.registerAsEventProcessor ?? true;
192
175
  configInstance.processEventsAfterPublish =
193
- processEventsAfterPublish ??
194
- cds.env.eventQueue?.processEventsAfterPublish ??
195
- true;
196
- configInstance.runInterval =
197
- runInterval ?? cds.env.eventQueue?.runInterval ?? 5 * 60 * 1000;
176
+ processEventsAfterPublish ?? cds.env.eventQueue?.processEventsAfterPublish ?? true;
177
+ configInstance.runInterval = runInterval ?? cds.env.eventQueue?.runInterval ?? 5 * 60 * 1000;
198
178
  configInstance.parallelTenantProcessing =
199
- parallelTenantProcessing ??
200
- cds.env.eventQueue?.parallelTenantProcessing ??
201
- 5;
179
+ parallelTenantProcessing ?? cds.env.eventQueue?.parallelTenantProcessing ?? 5;
202
180
  configInstance.tableNameEventQueue =
203
- tableNameEventQueue ??
204
- cds.env.eventQueue?.tableNameEventQueue ??
205
- BASE_TABLES.EVENT;
206
- configInstance.tableNameEventLock =
207
- tableNameEventLock ??
208
- cds.env.eventQueue?.tableNameEventLock ??
209
- BASE_TABLES.LOCK;
210
- configInstance.skipCsnCheck =
211
- skipCsnCheck ?? cds.env.eventQueue?.skipCsnCheck ?? false;
181
+ tableNameEventQueue ?? cds.env.eventQueue?.tableNameEventQueue ?? BASE_TABLES.EVENT;
182
+ configInstance.tableNameEventLock = tableNameEventLock ?? cds.env.eventQueue?.tableNameEventLock ?? BASE_TABLES.LOCK;
183
+ configInstance.skipCsnCheck = skipCsnCheck ?? cds.env.eventQueue?.skipCsnCheck ?? false;
212
184
  };
213
185
 
214
186
  module.exports = {
@@ -5,13 +5,11 @@ const pathLib = require("path");
5
5
  const cds = require("@sap/cds");
6
6
 
7
7
  const { getConfigInstance } = require("./config");
8
+ const { TransactionMode } = require("./constants");
8
9
  const { limiter, Funnel } = require("./shared/common");
9
10
 
10
11
  const EventQueueBase = require("./EventQueueProcessorBase");
11
- const {
12
- executeInNewTransaction,
13
- TriggerRollback,
14
- } = require("./shared/cdsHelper");
12
+ const { executeInNewTransaction, TriggerRollback } = require("./shared/cdsHelper");
15
13
 
16
14
  const COMPONENT_NAME = "eventQueue/processEventQueue";
17
15
  const MAX_EXECUTION_TIME = 5 * 60 * 1000;
@@ -21,47 +19,24 @@ const eventQueueRunner = async (context, events) => {
21
19
  const funnel = new Funnel();
22
20
  await Promise.allSettled(
23
21
  events.map((event) =>
24
- funnel.run(event.load, async () =>
25
- processEventQueue(context, event.type, event.subType, startTime)
26
- )
22
+ funnel.run(event.load, async () => processEventQueue(context, event.type, event.subType, startTime))
27
23
  )
28
24
  );
29
25
  };
30
26
 
31
- const processEventQueue = async (
32
- context,
33
- eventType,
34
- eventSubType,
35
- startTime = new Date()
36
- ) => {
27
+ const processEventQueue = async (context, eventType, eventSubType, startTime = new Date()) => {
37
28
  let iterationCounter = 0;
38
29
  let shouldContinue = true;
39
30
  let baseInstance;
40
31
  try {
41
32
  let eventTypeInstance;
42
- const eventConfig = getConfigInstance().getEventConfig(
43
- eventType,
44
- eventSubType
45
- );
33
+ const eventConfig = getConfigInstance().getEventConfig(eventType, eventSubType);
46
34
  const [err, EventTypeClass] = resilientRequire(eventConfig?.impl);
47
- if (
48
- !eventConfig ||
49
- err ||
50
- !(typeof EventTypeClass.constructor === "function")
51
- ) {
52
- await EventQueueBase.handleMissingTypeImplementation(
53
- context,
54
- eventType,
55
- eventSubType
56
- );
35
+ if (!eventConfig || err || !(typeof EventTypeClass.constructor === "function")) {
36
+ await EventQueueBase.handleMissingTypeImplementation(context, eventType, eventSubType);
57
37
  return;
58
38
  }
59
- baseInstance = new EventTypeClass(
60
- context,
61
- eventType,
62
- eventSubType,
63
- eventConfig
64
- );
39
+ baseInstance = new EventTypeClass(context, eventType, eventSubType, eventConfig);
65
40
  const continueProcessing = await baseInstance.handleDistributedLock();
66
41
  if (!continueProcessing) {
67
42
  return;
@@ -69,118 +44,79 @@ const processEventQueue = async (
69
44
  eventConfig.startTime = startTime;
70
45
  while (shouldContinue) {
71
46
  iterationCounter++;
72
- await executeInNewTransaction(
73
- context,
74
- `eventQueue-pre-processing-${eventType}##${eventSubType}`,
75
- async (tx) => {
76
- eventTypeInstance = new EventTypeClass(
77
- tx.context,
78
- eventType,
79
- eventSubType,
80
- eventConfig
81
- );
82
- const queueEntries =
83
- await eventTypeInstance.getQueueEntriesAndSetToInProgress();
84
- eventTypeInstance.startPerformanceTracerPreprocessing();
85
- for (const queueEntry of queueEntries) {
86
- try {
87
- eventTypeInstance.modifyQueueEntry(queueEntry);
88
- const payload =
89
- await eventTypeInstance.checkEventAndGeneratePayload(
90
- queueEntry
91
- );
92
- if (payload === null) {
93
- eventTypeInstance.setStatusToDone(queueEntry);
94
- continue;
95
- }
96
- if (payload === undefined) {
97
- eventTypeInstance.handleInvalidPayloadReturned(queueEntry);
98
- continue;
99
- }
100
- eventTypeInstance.addEventWithPayloadForProcessing(
101
- queueEntry,
102
- payload
103
- );
104
- } catch (err) {
105
- eventTypeInstance.handleErrorDuringProcessing(err, queueEntry);
47
+ await executeInNewTransaction(context, `eventQueue-pre-processing-${eventType}##${eventSubType}`, async (tx) => {
48
+ eventTypeInstance = new EventTypeClass(tx.context, eventType, eventSubType, eventConfig);
49
+ const queueEntries = await eventTypeInstance.getQueueEntriesAndSetToInProgress();
50
+ eventTypeInstance.startPerformanceTracerPreprocessing();
51
+ for (const queueEntry of queueEntries) {
52
+ try {
53
+ eventTypeInstance.modifyQueueEntry(queueEntry);
54
+ const payload = await eventTypeInstance.checkEventAndGeneratePayload(queueEntry);
55
+ if (payload === null) {
56
+ eventTypeInstance.setStatusToDone(queueEntry);
57
+ continue;
106
58
  }
59
+ if (payload === undefined) {
60
+ eventTypeInstance.handleInvalidPayloadReturned(queueEntry);
61
+ continue;
62
+ }
63
+ eventTypeInstance.addEventWithPayloadForProcessing(queueEntry, payload);
64
+ } catch (err) {
65
+ eventTypeInstance.handleErrorDuringProcessing(err, queueEntry);
107
66
  }
108
- throw new TriggerRollback();
109
67
  }
110
- );
111
- eventTypeInstance.exceededEvents.length &&
112
- (await executeInNewTransaction(
113
- context,
114
- `eventQueue-handleExceededEvents-${eventType}##${eventSubType}`,
115
- async (tx) => {
116
- eventTypeInstance.processEventContext = tx.context;
117
- await eventTypeInstance.handleExceededEvents(
118
- eventTypeInstance.exceededEvents
119
- );
120
- }
121
- ));
68
+ throw new TriggerRollback();
69
+ });
70
+ await eventTypeInstance.handleExceededEvents();
122
71
  if (!eventTypeInstance) {
123
72
  return;
124
73
  }
125
74
  eventTypeInstance.endPerformanceTracerPreprocessing();
126
75
  if (Object.keys(eventTypeInstance.queueEntriesWithPayloadMap).length) {
76
+ await executeInNewTransaction(context, `eventQueue-processing-${eventType}##${eventSubType}`, async (tx) => {
77
+ eventTypeInstance.processEventContext = tx.context;
78
+ try {
79
+ eventTypeInstance.clusterQueueEntries();
80
+ await processEventMap(eventTypeInstance);
81
+ } catch (err) {
82
+ eventTypeInstance.handleErrorDuringClustering(err);
83
+ }
84
+ if (
85
+ eventTypeInstance.transactionMode !== TransactionMode.alwaysCommit ||
86
+ Object.entries(eventTypeInstance.eventProcessingMap).some(([key]) =>
87
+ eventTypeInstance.shouldRollbackTransaction(key)
88
+ )
89
+ ) {
90
+ throw new TriggerRollback();
91
+ }
92
+ });
93
+ }
94
+ await executeInNewTransaction(context, `eventQueue-persistStatus-${eventType}##${eventSubType}`, async (tx) => {
95
+ await eventTypeInstance.persistEventStatus(tx);
96
+ });
97
+ shouldContinue = reevaluateShouldContinue(eventTypeInstance, iterationCounter, startTime);
98
+ if (!shouldContinue) {
127
99
  await executeInNewTransaction(
128
100
  context,
129
- `eventQueue-processing-${eventType}##${eventSubType}`,
101
+ `eventQueue-deleteFinishedEvents-${eventType}##${eventSubType}`,
130
102
  async (tx) => {
131
- eventTypeInstance.processEventContext = tx.context;
132
- try {
133
- eventTypeInstance.clusterQueueEntries();
134
- await processEventMap(eventTypeInstance);
135
- } catch (err) {
136
- eventTypeInstance.handleErrorDuringClustering(err);
137
- }
138
- if (
139
- eventTypeInstance.shouldTriggerRollback ||
140
- Object.entries(eventTypeInstance.eventProcessingMap).some(
141
- ([key]) => eventTypeInstance.shouldRollbackTransaction(key)
142
- )
143
- ) {
144
- throw new TriggerRollback();
145
- }
103
+ await eventTypeInstance.deleteFinishedEvents(tx);
146
104
  }
147
105
  );
148
106
  }
149
- await executeInNewTransaction(
150
- context,
151
- `eventQueue-persistStatus-${eventType}##${eventSubType}`,
152
- async (tx) => {
153
- await eventTypeInstance.persistEventStatus(tx);
154
- }
155
- );
156
- shouldContinue = reevaluateShouldContinue(
157
- eventTypeInstance,
158
- iterationCounter,
159
- startTime
160
- );
161
107
  }
162
108
  } catch (err) {
163
- cds
164
- .log(COMPONENT_NAME)
165
- .error(
166
- "Processing event queue failed with unexpected error. Error:",
167
- err,
168
- {
169
- eventType,
170
- eventSubType,
171
- }
172
- );
109
+ cds.log(COMPONENT_NAME).error("Processing event queue failed with unexpected error. Error:", err, {
110
+ eventType,
111
+ eventSubType,
112
+ });
173
113
  } finally {
174
114
  await baseInstance?.handleReleaseLock();
175
115
  }
176
116
  };
177
117
 
178
- const reevaluateShouldContinue = (
179
- eventTypeInstance,
180
- iterationCounter,
181
- startTime
182
- ) => {
183
- if (!eventTypeInstance.getSelectNextChunk()) {
118
+ const reevaluateShouldContinue = (eventTypeInstance, iterationCounter, startTime) => {
119
+ if (!eventTypeInstance.selectNextChunk) {
184
120
  return false; // no select next chunk configured for this event
185
121
  }
186
122
  if (eventTypeInstance.emptyChunkSelected) {
@@ -209,13 +145,7 @@ const processEventMap = async (eventTypeInstance) => {
209
145
  eventTypeInstance.baseContext,
210
146
  `eventQueue-processEvent-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
211
147
  async (tx) => {
212
- statusMap = await _processEvent(
213
- eventTypeInstance,
214
- tx.context,
215
- key,
216
- queueEntries,
217
- payload
218
- );
148
+ statusMap = await _processEvent(eventTypeInstance, tx.context, key, queueEntries, payload);
219
149
  if (
220
150
  eventTypeInstance.statusMapContainsError(statusMap) ||
221
151
  eventTypeInstance.shouldRollbackTransaction(key)
@@ -236,13 +166,7 @@ const processEventMap = async (eventTypeInstance) => {
236
166
  }
237
167
  );
238
168
  } else {
239
- await _processEvent(
240
- eventTypeInstance,
241
- eventTypeInstance.context,
242
- key,
243
- queueEntries,
244
- payload
245
- );
169
+ await _processEvent(eventTypeInstance, eventTypeInstance.context, key, queueEntries, payload);
246
170
  }
247
171
  }
248
172
  ).finally(() => {
@@ -254,28 +178,15 @@ const processEventMap = async (eventTypeInstance) => {
254
178
  eventTypeInstance.endPerformanceTracerEvents();
255
179
  };
256
180
 
257
- const _processEvent = async (
258
- eventTypeInstance,
259
- processContext,
260
- key,
261
- queueEntries,
262
- payload
263
- ) => {
181
+ const _processEvent = async (eventTypeInstance, processContext, key, queueEntries, payload) => {
264
182
  try {
265
183
  eventTypeInstance.logStartMessage(queueEntries);
266
- const eventOutdated = await eventTypeInstance.isOutdatedAndKeepalive(
267
- queueEntries
268
- );
184
+ const eventOutdated = await eventTypeInstance.isOutdatedAndKeepalive(queueEntries);
269
185
  if (eventOutdated) {
270
186
  return;
271
187
  }
272
188
  eventTypeInstance.setTxForEventProcessing(key, cds.tx(processContext));
273
- const statusTuple = await eventTypeInstance.processEvent(
274
- processContext,
275
- key,
276
- queueEntries,
277
- payload
278
- );
189
+ const statusTuple = await eventTypeInstance.processEvent(processContext, key, queueEntries, payload);
279
190
  return eventTypeInstance.setEventStatus(queueEntries, statusTuple);
280
191
  } catch (err) {
281
192
  return eventTypeInstance.handleErrorDuringProcessing(err, queueEntries);
@@ -15,9 +15,7 @@ const publishEvent = async (tx, events) => {
15
15
  throw EventQueueError.unknownEventType(type, subType);
16
16
  }
17
17
  }
18
- await tx.run(
19
- INSERT.into(configInstance.tableNameEventQueue).entries(eventsForProcessing)
20
- );
18
+ return await tx.run(INSERT.into(configInstance.tableNameEventQueue).entries(eventsForProcessing));
21
19
  };
22
20
 
23
21
  module.exports = {
@@ -22,15 +22,11 @@ const initEventQueueRedisSubscribe = () => {
22
22
 
23
23
  const subscribeRedisClient = () => {
24
24
  const errorHandlerCreateClient = (err) => {
25
- cds
26
- .log(COMPONENT_NAME)
27
- .error("error from redis client for pub/sub failed", err);
25
+ cds.log(COMPONENT_NAME).error("error from redis client for pub/sub failed", err);
28
26
  subscriberClientPromise = null;
29
27
  setTimeout(subscribeRedisClient, 5 * 1000).unref();
30
28
  };
31
- subscriberClientPromise = redis.createClientAndConnect(
32
- errorHandlerCreateClient
33
- );
29
+ subscriberClientPromise = redis.createClientAndConnect(errorHandlerCreateClient);
34
30
  subscriberClientPromise
35
31
  .then((client) => {
36
32
  cds.log(COMPONENT_NAME).info("subscribe redis client connected");
@@ -39,10 +35,7 @@ const subscribeRedisClient = () => {
39
35
  .catch((err) => {
40
36
  cds
41
37
  .log(COMPONENT_NAME)
42
- .error(
43
- "error from redis client for pub/sub failed during startup - trying to reconnect",
44
- err
45
- );
38
+ .error("error from redis client for pub/sub failed during startup - trying to reconnect", err);
46
39
  });
47
40
  };
48
41
 
@@ -68,9 +61,7 @@ const messageHandlerProcessEvents = async (messageData) => {
68
61
  type,
69
62
  subType,
70
63
  });
71
- getWorkerPoolInstance().addToQueue(async () =>
72
- processEventQueue(context, type, subType)
73
- );
64
+ getWorkerPoolInstance().addToQueue(async () => processEventQueue(context, type, subType));
74
65
  };
75
66
 
76
67
  const publishEvent = async (tenantId, type, subType) => {
@@ -89,9 +80,7 @@ const publishEvent = async (tenantId, type, subType) => {
89
80
  };
90
81
  try {
91
82
  if (!publishClient) {
92
- publishClient = await redis.createClientAndConnect(
93
- errorHandlerCreateClient
94
- );
83
+ publishClient = await redis.createClientAndConnect(errorHandlerCreateClient);
95
84
  logger.info("publish redis client connected");
96
85
  }
97
86
 
@@ -108,10 +97,7 @@ const publishEvent = async (tenantId, type, subType) => {
108
97
  type,
109
98
  subType,
110
99
  });
111
- await publishClient.publish(
112
- MESSAGE_CHANNEL,
113
- JSON.stringify({ tenantId, type, subType })
114
- );
100
+ await publishClient.publish(MESSAGE_CHANNEL, JSON.stringify({ tenantId, type, subType }));
115
101
  } catch (err) {
116
102
  logger.error(`publish event failed with error: ${err.toString()}`, {
117
103
  tenantId,