@cap-js-community/event-queue 1.2.5 → 1.2.6

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": "1.2.5",
3
+ "version": "1.2.6",
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": [
@@ -18,6 +18,7 @@ const ERROR_CODES = {
18
18
  NO_MANUEL_INSERT_OF_PERIODIC: "NO_MANUEL_INSERT_OF_PERIODIC",
19
19
  LOAD_HIGHER_THAN_LIMIT: "LOAD_HIGHER_THAN_LIMIT",
20
20
  SCHEMA_TENANT_MISMATCH: "SCHEMA_TENANT_MISMATCH",
21
+ GLOBAL_CDS_CONTEXT_MISSMATCH: "GLOBAL_CDS_CONTEXT_MISSMATCH",
21
22
  };
22
23
 
23
24
  const ERROR_CODES_META = {
@@ -67,6 +68,9 @@ const ERROR_CODES_META = {
67
68
  [ERROR_CODES.SCHEMA_TENANT_MISMATCH]: {
68
69
  message: "The db client associated to the tenant context does not match! Processing will be skipped.",
69
70
  },
71
+ [ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH]: {
72
+ message: "The global cds context does not match the local cds context.",
73
+ },
70
74
  };
71
75
 
72
76
  class EventQueueError extends VError {
@@ -235,6 +239,17 @@ class EventQueueError extends VError {
235
239
  message
236
240
  );
237
241
  }
242
+
243
+ static globalCdsContextNotMatchingLocal(globalProperties, localProperties) {
244
+ const { message } = ERROR_CODES_META[ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH];
245
+ return new EventQueueError(
246
+ {
247
+ name: ERROR_CODES.GLOBAL_CDS_CONTEXT_MISSMATCH,
248
+ info: { globalProperties, localProperties },
249
+ },
250
+ message
251
+ );
252
+ }
238
253
  }
239
254
 
240
255
  module.exports = EventQueueError;
@@ -70,6 +70,8 @@ class EventQueueProcessorBase {
70
70
  this.__txMap = {};
71
71
  this.__txRollback = {};
72
72
  this.__queueEntries = [];
73
+
74
+ this.#checkGlobalContextToLocalContext();
73
75
  }
74
76
 
75
77
  /**
@@ -533,7 +535,9 @@ class EventQueueProcessorBase {
533
535
  async getQueueEntriesAndSetToInProgress() {
534
536
  let result = [];
535
537
  const refDateStartAfter = new Date(Date.now() + this.#config.runInterval * 1.2);
538
+ this.#checkGlobalContextToLocalContext();
536
539
  await executeInNewTransaction(this.__baseContext, "eventQueue-getQueueEntriesAndSetToInProgress", async (tx) => {
540
+ this.#checkGlobalContextToLocalContext();
537
541
  await this.checkTxConsistency(tx);
538
542
  const entries = await tx.run(
539
543
  SELECT.from(this.#config.tableNameEventQueue)
@@ -692,7 +696,7 @@ class EventQueueProcessorBase {
692
696
  } catch (err) {
693
697
  errorHandler(err);
694
698
  }
695
- if (txSchema !== serviceManagerSchema) {
699
+ if (serviceManagerSchema && txSchema !== serviceManagerSchema) {
696
700
  const err = EventQueueError.dbClientSchemaMismatch(tx.context.tenant, txSchema, serviceManagerSchema);
697
701
  errorHandler(err);
698
702
  throw err;
@@ -724,6 +728,48 @@ class EventQueueProcessorBase {
724
728
  return entry.lastAttemptsTs;
725
729
  }
726
730
 
731
+ #checkGlobalContextToLocalContext() {
732
+ if (!this.#config.enableTxConsistencyCheck) {
733
+ return;
734
+ }
735
+ if (this.__context.tenant !== cds.context.tenant) {
736
+ throw EventQueueError.globalCdsContextNotMatchingLocal(
737
+ JSON.stringify(
738
+ {
739
+ correlationId: cds.context.id,
740
+ tenantId: cds.context.tenant,
741
+ timestamp: cds.context.timestamp,
742
+ base: JSON.stringify(
743
+ {
744
+ correlationId: cds.context.context?.id,
745
+ tenantId: cds.context.context?.tenant,
746
+ timestamp: cds.context.context?.timestamp,
747
+ },
748
+ null,
749
+ 2
750
+ ),
751
+ },
752
+ null,
753
+ 2
754
+ ),
755
+ JSON.stringify(
756
+ {
757
+ correlationId: this.__context.id,
758
+ tenantId: this.__context.tenant,
759
+ timestamp: this.__context.timestamp,
760
+ base: JSON.stringify({
761
+ correlationId: this.__context.context?.id,
762
+ tenantId: this.__context.context?.tenant,
763
+ timestamp: this.__context.context?.timestamp,
764
+ }),
765
+ },
766
+ null,
767
+ 2
768
+ )
769
+ );
770
+ }
771
+ }
772
+
727
773
  #handleDelayedEvents(delayedEvents) {
728
774
  for (const delayedEvent of delayedEvents) {
729
775
  this.#eventSchedulerInstance.scheduleEvent(
package/src/runner.js CHANGED
@@ -26,9 +26,9 @@ let singleRunDone;
26
26
 
27
27
  const singleTenant = () => _scheduleFunction(_checkPeriodicEventsSingleTenant, _singleTenantDb);
28
28
 
29
- const multiTenancyDb = () => _scheduleFunction(_multiTenancyPeriodicEvents, _multiTenancyDb);
29
+ const multiTenancyDb = () => _scheduleFunction(async () => {}, _multiTenancyDb);
30
30
 
31
- const multiTenancyRedis = () => _scheduleFunction(_multiTenancyPeriodicEvents, _multiTenancyRedis);
31
+ const multiTenancyRedis = () => _scheduleFunction(async () => {}, _multiTenancyRedis);
32
32
 
33
33
  const _scheduleFunction = async (singleRunFn, periodicFn) => {
34
34
  const logger = cds.log(COMPONENT_NAME);
@@ -69,7 +69,7 @@ const _multiTenancyRedis = async () => {
69
69
  const emptyContext = new cds.EventContext({});
70
70
  logger.info("executing event queue run for multi instance and tenant");
71
71
  const tenantIds = await cdsHelper.getAllTenantIds();
72
- _checkAndTriggerPeriodicEventUpdate(tenantIds);
72
+ await _checkPeriodicEventUpdate(tenantIds);
73
73
 
74
74
  const runId = await _acquireRunId(emptyContext);
75
75
 
@@ -78,22 +78,21 @@ const _multiTenancyRedis = async () => {
78
78
  return;
79
79
  }
80
80
 
81
- return _executeEventsAllTenants(tenantIds, runId);
81
+ return await _executeEventsAllTenants(tenantIds, runId);
82
82
  };
83
83
 
84
- const _checkAndTriggerPeriodicEventUpdate = (tenantIds) => {
84
+ const _checkPeriodicEventUpdate = async (tenantIds) => {
85
85
  const hash = hashStringTo32Bit(JSON.stringify(tenantIds));
86
86
  if (!tenantIdHash) {
87
87
  tenantIdHash = hash;
88
- _multiTenancyPeriodicEvents().catch((err) => {
88
+ return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
89
89
  cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events!", err);
90
90
  });
91
- return;
92
91
  }
93
92
  if (tenantIdHash && tenantIdHash !== hash) {
94
93
  tenantIdHash = hash;
95
94
  cds.log(COMPONENT_NAME).info("tenant id hash changed, triggering updating periodic events!");
96
- _multiTenancyPeriodicEvents().catch((err) => {
95
+ return await _multiTenancyPeriodicEvents(tenantIds).catch((err) => {
97
96
  cds.log(COMPONENT_NAME).error("Error during triggering updating periodic events!", err);
98
97
  });
99
98
  }
@@ -108,91 +107,103 @@ const _executeEventsAllTenants = (tenantIds, runId) => {
108
107
  return result;
109
108
  }, []);
110
109
 
111
- return product.map(async ([tenantId, event]) => {
112
- const subdomain = await getSubdomainForTenantId(tenantId);
113
- const user = new cds.User.Privileged(config.userId);
114
- const tenantContext = {
115
- tenant: tenantId,
116
- user,
117
- // NOTE: we need this because of logging otherwise logs would not contain the subdomain
118
- http: { req: { authInfo: { getSubdomain: () => subdomain } } },
119
- };
120
- return await cds.tx(tenantContext, async ({ context }) => {
110
+ return Promise.allSettled(
111
+ product.map(async ([tenantId, event]) => {
112
+ const subdomain = await getSubdomainForTenantId(tenantId);
113
+ const user = new cds.User.Privileged(config.userId);
114
+ const tenantContext = {
115
+ tenant: tenantId,
116
+ user,
117
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
118
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
119
+ };
121
120
  const label = `${event.type}_${event.subType}`;
122
121
  return await WorkerQueue.instance.addToQueue(event.load, label, async () => {
122
+ return await cds.tx(tenantContext, async ({ context }) => {
123
+ try {
124
+ const lockId = `${runId}_${label}`;
125
+ const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
126
+ expiryTime: eventQueueConfig.runInterval * 0.95,
127
+ });
128
+ if (!couldAcquireLock) {
129
+ return;
130
+ }
131
+ await runEventCombinationForTenant(context, event.type, event.subType, true);
132
+ } catch (err) {
133
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
134
+ tenantId,
135
+ });
136
+ }
137
+ });
138
+ });
139
+ })
140
+ );
141
+ };
142
+
143
+ const _executePeriodicEventsAllTenants = async (tenantIds, runId) => {
144
+ return await Promise.allSettled(
145
+ tenantIds.map(async (tenantId) => {
146
+ const label = `UPDATE_PERIODIC_EVENTS_${tenantId}`;
147
+ return await WorkerQueue.instance.addToQueue(1, label, async () => {
123
148
  try {
124
- const lockId = `${runId}_${label}`;
125
- const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
126
- expiryTime: eventQueueConfig.runInterval * 0.95,
149
+ const subdomain = await getSubdomainForTenantId(tenantId);
150
+ const user = new cds.User.Privileged(config.userId);
151
+ const tenantContext = {
152
+ tenant: tenantId,
153
+ user,
154
+ // NOTE: we need this because of logging otherwise logs would not contain the subdomain
155
+ http: { req: { authInfo: { getSubdomain: () => subdomain } } },
156
+ };
157
+
158
+ return await cds.tx(tenantContext, async ({ context }) => {
159
+ const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
160
+ expiryTime: eventQueueConfig.runInterval * 0.95,
161
+ });
162
+ if (!couldAcquireLock) {
163
+ return;
164
+ }
165
+ await _checkPeriodicEventsSingleTenant(context);
127
166
  });
128
- if (!couldAcquireLock) {
129
- return;
130
- }
131
- await runEventCombinationForTenant(context, event.type, event.subType, true);
132
167
  } catch (err) {
133
168
  cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
134
169
  tenantId,
135
170
  });
136
171
  }
137
172
  });
138
- });
139
- });
173
+ })
174
+ );
140
175
  };
141
176
 
142
- const _executePeriodicEventsAllTenants = (tenantIds, runId) => {
143
- tenantIds.forEach((tenantId) => {
144
- const label = `UPDATE_PERIODIC_EVENTS_${tenantId}`;
145
- WorkerQueue.instance.addToQueue(1, label, async () => {
146
- try {
147
- const subdomain = await getSubdomainForTenantId(tenantId);
148
- const user = new cds.User.Privileged(config.userId);
149
- const tenantContext = {
150
- tenant: tenantId,
151
- user,
152
- // NOTE: we need this because of logging otherwise logs would not contain the subdomain
153
- http: { req: { authInfo: { getSubdomain: () => subdomain } } },
154
- };
155
-
177
+ const _singleTenantDb = async (tenantId) => {
178
+ return Promise.allSettled(
179
+ eventQueueConfig.allEvents.map(async (event) => {
180
+ const label = `${event.type}_${event.subType}`;
181
+ const user = new cds.User.Privileged(config.userId);
182
+ const tenantContext = {
183
+ tenant: tenantId,
184
+ user,
185
+ };
186
+ return await WorkerQueue.instance.addToQueue(event.load, label, async () => {
156
187
  return await cds.tx(tenantContext, async ({ context }) => {
157
- const couldAcquireLock = await distributedLock.acquireLock(context, runId, {
158
- expiryTime: eventQueueConfig.runInterval * 0.95,
159
- });
160
- if (!couldAcquireLock) {
161
- return;
188
+ try {
189
+ const lockId = `${EVENT_QUEUE_RUN_ID}_${label}`;
190
+ const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
191
+ expiryTime: eventQueueConfig.runInterval * 0.95,
192
+ });
193
+ if (!couldAcquireLock) {
194
+ return;
195
+ }
196
+ await runEventCombinationForTenant(context, event.type, event.subType, true);
197
+ } catch (err) {
198
+ cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
199
+ tenantId,
200
+ redisEnabled: eventQueueConfig.redisEnabled,
201
+ });
162
202
  }
163
- await _checkPeriodicEventsSingleTenant(context);
164
203
  });
165
- } catch (err) {
166
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
167
- tenantId,
168
- });
169
- }
170
- });
171
- });
172
- };
173
-
174
- const _singleTenantDb = async (tenantId) => {
175
- return eventQueueConfig.allEvents.map((event) => {
176
- const label = `${event.type}_${event.subType}`;
177
- return WorkerQueue.instance.addToQueue(event.load, label, async () => {
178
- try {
179
- const context = new cds.EventContext({ tenant: tenantId });
180
- const lockId = `${EVENT_QUEUE_RUN_ID}_${label}`;
181
- const couldAcquireLock = await distributedLock.acquireLock(context, lockId, {
182
- expiryTime: eventQueueConfig.runInterval * 0.95,
183
- });
184
- if (!couldAcquireLock) {
185
- return;
186
- }
187
- await runEventCombinationForTenant(context, event.type, event.subType, true);
188
- } catch (err) {
189
- cds.log(COMPONENT_NAME).error("executing event-queue run for tenant failed", {
190
- tenantId,
191
- redisEnabled: eventQueueConfig.redisEnabled,
192
- });
193
- }
194
- });
195
- });
204
+ });
205
+ })
206
+ );
196
207
  };
197
208
 
198
209
  const _acquireRunId = async (context) => {
@@ -280,19 +291,19 @@ const _multiTenancyDb = async () => {
280
291
  try {
281
292
  logger.info("executing event queue run for single instance and multi tenant");
282
293
  const tenantIds = await cdsHelper.getAllTenantIds();
283
- _checkAndTriggerPeriodicEventUpdate(tenantIds);
284
- return _executeEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
294
+ await _checkPeriodicEventUpdate(tenantIds);
295
+ return await _executeEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_ID);
285
296
  } catch (err) {
286
297
  logger.error("Couldn't fetch tenant ids for event queue processing! Next try after defined interval.", err);
287
298
  }
288
299
  };
289
300
 
290
- const _multiTenancyPeriodicEvents = async () => {
301
+ const _multiTenancyPeriodicEvents = async (tenantIds) => {
291
302
  const logger = cds.log(COMPONENT_NAME);
292
303
  try {
293
304
  logger.info("executing event queue update periodic events");
294
- const tenantIds = await cdsHelper.getAllTenantIds();
295
- _executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
305
+ tenantIds = tenantIds ?? (await cdsHelper.getAllTenantIds());
306
+ return await _executePeriodicEventsAllTenants(tenantIds, EVENT_QUEUE_RUN_PERIODIC_EVENT);
296
307
  } catch (err) {
297
308
  logger.error("Couldn't fetch tenant ids for updating periodic event processing!", err);
298
309
  }