@cap-js-community/event-queue 1.10.0-beta.1 → 1.10.0-beta.2

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.10.0-beta.1",
3
+ "version": "1.10.0-beta.2",
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
  "types": "src/index.d.ts",
package/src/config.js CHANGED
@@ -10,9 +10,12 @@ const { Priorities } = require("./constants");
10
10
 
11
11
  const FOR_UPDATE_TIMEOUT = 10;
12
12
  const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
13
- const REDIS_CONFIG_CHANNEL = "EVENT_QUEUE_CONFIG_CHANNEL";
13
+ const REDIS_PREFIX = "EVENT_QUEUE";
14
+ const REDIS_CONFIG_CHANNEL = "CONFIG_CHANNEL";
14
15
  const REDIS_OFFBOARD_TENANT_CHANNEL = "REDIS_OFFBOARD_TENANT_CHANNEL";
15
- const REDIS_CONFIG_BLOCKLIST_CHANNEL = "EVENT_QUEUE_REDIS_CONFIG_BLOCKLIST_CHANNEL";
16
+ const REDIS_CONFIG_BLOCKLIST_CHANNEL = "REDIS_CONFIG_BLOCKLIST_CHANNEL";
17
+ const COMMAND_BLOCK = "EVENT_BLOCK";
18
+ const COMMAND_UNBLOCK = "EVENT_UNBLOCK";
16
19
  const COMPONENT_NAME = "/eventQueue/config";
17
20
  const MIN_INTERVAL_SEC = 10;
18
21
  const DEFAULT_LOAD = 1;
@@ -22,8 +25,6 @@ const DEFAULT_KEEP_ALIVE_INTERVAL = 60;
22
25
  const DEFAULT_MAX_FACTOR_STUCK_2_KEEP_ALIVE_INTERVAL = 3.5;
23
26
  const DEFAULT_INHERIT_TRACE_CONTEXT = true;
24
27
  const SUFFIX_PERIODIC = "_PERIODIC";
25
- const COMMAND_BLOCK = "EVENT_QUEUE_EVENT_BLOCK";
26
- const COMMAND_UNBLOCK = "EVENT_QUEUE_EVENT_UNBLOCK";
27
28
  const CAP_EVENT_TYPE = "CAP_OUTBOX";
28
29
  const CAP_PARALLEL_DEFAULT = 5;
29
30
  const DELETE_TENANT_BLOCK_AFTER_MS = 5 * 60 * 1000;
@@ -106,6 +107,7 @@ class Config {
106
107
  #unsubscribeHandlers = [];
107
108
  #unsubscribedTenants = {};
108
109
  #cronTimezone;
110
+ #redisNamespace;
109
111
  #publishEventBlockList;
110
112
  #crashOnRedisUnavailable;
111
113
  #tenantIdFilterTokenInfoCb;
@@ -182,7 +184,7 @@ class Config {
182
184
 
183
185
  attachConfigChangeHandler() {
184
186
  this.#attachBlockListChangeHandler();
185
- redis.subscribeRedisChannel(this.#redisOptions, REDIS_CONFIG_CHANNEL, (messageData) => {
187
+ redis.subscribeRedisChannel(this.redisOptions, REDIS_CONFIG_CHANNEL, (messageData) => {
186
188
  try {
187
189
  const { key, value } = JSON.parse(messageData);
188
190
  if (this[key] !== value) {
@@ -199,7 +201,7 @@ class Config {
199
201
 
200
202
  attachRedisUnsubscribeHandler() {
201
203
  this.#logger.info("attached redis handle for unsubscribe events");
202
- redis.subscribeRedisChannel(this.#redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, (messageData) => {
204
+ redis.subscribeRedisChannel(this.redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, (messageData) => {
203
205
  try {
204
206
  const { tenantId } = JSON.parse(messageData);
205
207
  this.#logger.info("received unsubscribe broadcast event", { tenantId });
@@ -229,7 +231,7 @@ class Config {
229
231
  handleUnsubscribe(tenantId) {
230
232
  if (this.redisEnabled) {
231
233
  redis
232
- .publishMessage(this.#redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, JSON.stringify({ tenantId }))
234
+ .publishMessage(this.redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, JSON.stringify({ tenantId }))
233
235
  .catch((error) => {
234
236
  this.#logger.error(`publishing tenant unsubscribe failed. tenantId: ${tenantId}`, error);
235
237
  });
@@ -247,13 +249,13 @@ class Config {
247
249
  this.#logger.info("redis not connected, config change won't be published", { key, value });
248
250
  return;
249
251
  }
250
- redis.publishMessage(this.#redisOptions, REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
252
+ redis.publishMessage(this.redisOptions, REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
251
253
  this.#logger.error(`publishing config change failed key: ${key}, value: ${value}`, error);
252
254
  });
253
255
  }
254
256
 
255
257
  #attachBlockListChangeHandler() {
256
- redis.subscribeRedisChannel(this.#redisOptions, REDIS_CONFIG_BLOCKLIST_CHANNEL, (messageData) => {
258
+ redis.subscribeRedisChannel(this.redisOptions, REDIS_CONFIG_BLOCKLIST_CHANNEL, (messageData) => {
257
259
  try {
258
260
  const { command, key, tenant } = JSON.parse(messageData);
259
261
  if (command === COMMAND_BLOCK) {
@@ -283,7 +285,7 @@ class Config {
283
285
 
284
286
  redis
285
287
  .publishMessage(
286
- this.#redisOptions,
288
+ this.redisOptions,
287
289
  REDIS_CONFIG_BLOCKLIST_CHANNEL,
288
290
  JSON.stringify({ command: COMMAND_BLOCK, key, tenant })
289
291
  )
@@ -316,7 +318,7 @@ class Config {
316
318
 
317
319
  redis
318
320
  .publishMessage(
319
- this.#redisOptions,
321
+ this.redisOptions,
320
322
  REDIS_CONFIG_BLOCKLIST_CHANNEL,
321
323
  JSON.stringify({ command: COMMAND_UNBLOCK, key, tenant })
322
324
  )
@@ -874,7 +876,18 @@ class Config {
874
876
  }
875
877
 
876
878
  get redisOptions() {
877
- return this.#redisOptions;
879
+ return {
880
+ ...this.#redisOptions,
881
+ redisNamespace: `${[REDIS_PREFIX, this.redisNamespace].filter((a) => a).join("_")}`,
882
+ };
883
+ }
884
+
885
+ set redisNamespace(value) {
886
+ this.#redisNamespace = value;
887
+ }
888
+
889
+ get redisNamespace() {
890
+ return this.#redisNamespace;
878
891
  }
879
892
 
880
893
  set insertEventsBeforeCommit(value) {
package/src/initialize.js CHANGED
@@ -42,6 +42,7 @@ const CONFIG_VARS = [
42
42
  ["insertEventsBeforeCommit", true],
43
43
  ["enableTelemetry", true],
44
44
  ["cronTimezone", null],
45
+ ["redisNamespace", null],
45
46
  ["publishEventBlockList", true],
46
47
  ["crashOnRedisUnavailable", false],
47
48
  ];
@@ -63,13 +63,11 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
63
63
  } else {
64
64
  const msg = new cds.Request({
65
65
  event: "clusterQueueEntries",
66
- data: { queueEntriesWithPayloadMap: genericClusterEvents },
67
66
  eventQueue: {
68
67
  processor: this,
69
- clusterByPayloadProperty: (propertyName) =>
70
- EventQueueGenericOutboxHandler.clusterByPayloadProperty(genericClusterEvents, propertyName),
71
- clusterByEventProperty: (propertyName) =>
72
- EventQueueGenericOutboxHandler.clusterByEventProperty(genericClusterEvents, propertyName),
68
+ clusterByPayloadProperty: (propertyName, cb) =>
69
+ this.clusterByPayloadProperty(genericClusterEvents, propertyName, cb),
70
+ clusterByEventProperty: (propertyName) => this.clusterByEventProperty(genericClusterEvents, propertyName),
73
71
  },
74
72
  });
75
73
  const handlerCluster = await this.__srvUnboxed.tx(this.context).send(msg);
@@ -80,13 +78,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
80
78
  for (const actionName in specificClusterEvents) {
81
79
  const msg = new cds.Request({
82
80
  event: `clusterQueueEntries.${actionName}`,
83
- data: { queueEntriesWithPayloadMap: specificClusterEvents[actionName] },
84
81
  eventQueue: {
85
82
  processor: this,
86
- clusterByPayloadProperty: (propertyName) =>
87
- EventQueueGenericOutboxHandler.clusterByPayloadProperty(specificClusterEvents[actionName], propertyName),
88
- clusterByEventProperty: (propertyName) =>
89
- EventQueueGenericOutboxHandler.clusterByEventProperty(specificClusterEvents[actionName], propertyName),
83
+ clusterByPayloadProperty: (propertyName, cb) =>
84
+ this.clusterByPayloadProperty(specificClusterEvents[actionName], propertyName, cb),
85
+ clusterByEventProperty: (propertyName, cb) =>
86
+ this.clusterByEventProperty(specificClusterEvents[actionName], propertyName, cb),
90
87
  },
91
88
  });
92
89
  const handlerCluster = await this.__srvUnboxed.tx(this.context).send(msg);
@@ -94,26 +91,58 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
94
91
  }
95
92
  }
96
93
 
97
- static clusterByPayloadProperty(queueEntriesWithPayloadMap, propertyName) {
98
- return Object.entries(queueEntriesWithPayloadMap).reduce((result, [, { queueEntry, payload }]) => {
99
- result[payload[propertyName]] ??= {
100
- queueEntries: [],
101
- payload,
102
- };
103
- result[payload[propertyName]].queueEntries.push(queueEntry);
94
+ clusterBase(queueEntriesWithPayloadMap, propertyName, refCb, cb) {
95
+ return Object.entries(queueEntriesWithPayloadMap).reduce((result, [, { queueEntry, payload }], index) => {
96
+ const ref = refCb(result, payload, queueEntry);
97
+ ref.queueEntries.push(queueEntry);
98
+ if (cb) {
99
+ const clusterResult = cb(ref.payload.data, queueEntry.payload.data, index);
100
+ ref.payload.data ??= clusterResult;
101
+ }
104
102
  return result;
105
103
  }, {});
106
104
  }
107
105
 
108
- static clusterByEventProperty(queueEntriesWithPayloadMap, propertyName) {
109
- return Object.entries(queueEntriesWithPayloadMap).reduce((result, [, { queueEntry, payload }]) => {
110
- result[queueEntry[propertyName]] ??= {
111
- queueEntries: [],
112
- payload,
113
- };
114
- result[queueEntry[propertyName]].queueEntries.push(queueEntry);
115
- return result;
116
- }, {});
106
+ clusterByPayloadProperty(queueEntriesWithPayloadMap, propertyName, cb) {
107
+ return this.clusterBase(
108
+ queueEntriesWithPayloadMap,
109
+ propertyName,
110
+ (result, payload) => {
111
+ const parts = propertyName.split(".");
112
+ const data = JSON.parse(JSON.stringify(payload.data));
113
+ let ref = payload;
114
+ for (const part of parts) {
115
+ ref = ref[part];
116
+ }
117
+ result[ref[propertyName]] ??= {
118
+ queueEntries: [],
119
+ payload: { ...payload, data },
120
+ };
121
+ return result[ref[propertyName]];
122
+ },
123
+ cb
124
+ );
125
+ }
126
+
127
+ clusterByEventProperty(queueEntriesWithPayloadMap, propertyName, cb) {
128
+ return this.clusterBase(
129
+ queueEntriesWithPayloadMap,
130
+ propertyName,
131
+ (result, payload, queueEntry) => {
132
+ const parts = propertyName.split(".");
133
+ const payloadCopy = JSON.parse(JSON.stringify(payload));
134
+ let ref = queueEntry;
135
+ for (const part of parts) {
136
+ ref = ref[part];
137
+ }
138
+ result[queueEntry[propertyName]] ??= {
139
+ queueEntries: [],
140
+ payload: payloadCopy,
141
+ };
142
+ return result[queueEntry[propertyName]];
143
+ },
144
+ cb
145
+ );
117
146
  }
118
147
 
119
148
  #clusterByAction(queueEntriesWithPayloadMap) {
@@ -47,7 +47,8 @@ function outboxed(srv, customOpts) {
47
47
  customOpts || {}
48
48
  );
49
49
  config.addCAPOutboxEventBase(srv.name, outboxOpts);
50
- const specificSettings = config.getCdsOutboxEventSpecificConfig(srv.name, req.method);
50
+ // TODO: check req.event ?? req.method
51
+ const specificSettings = config.getCdsOutboxEventSpecificConfig(srv.name, req.event);
51
52
  if (specificSettings) {
52
53
  outboxOpts = config.addCAPOutboxEventSpecificAction(srv.name, req.event);
53
54
  }
@@ -18,10 +18,10 @@ const { runEventCombinationForTenant } = require("./runnerHelper");
18
18
  const { trace } = require("../shared/openTelemetry");
19
19
 
20
20
  const COMPONENT_NAME = "/eventQueue/runner";
21
- const EVENT_QUEUE_RUN_ID = "EVENT_QUEUE_RUN_ID";
22
- const EVENT_QUEUE_RUN_TS = "EVENT_QUEUE_RUN_TS";
23
- const EVENT_QUEUE_RUN_REDIS_CHECK = "EVENT_QUEUE_RUN_REDIS_CHECK";
24
- const EVENT_QUEUE_UPDATE_PERIODIC_EVENTS = "EVENT_QUEUE_UPDATE_PERIODIC_EVENTS";
21
+ const EVENT_QUEUE_RUN_ID = "RUN_ID";
22
+ const EVENT_QUEUE_RUN_TS = "RUN_TS";
23
+ const EVENT_QUEUE_RUN_REDIS_CHECK = "RUN_REDIS_CHECK";
24
+ const EVENT_QUEUE_UPDATE_PERIODIC_EVENTS = "UPDATE_PERIODIC_EVENTS";
25
25
  let OFFSET_FIRST_RUN = 10 * 1000;
26
26
 
27
27
  let tenantIdHash;
@@ -4,7 +4,6 @@ const redis = require("./redis");
4
4
  const config = require("../config");
5
5
  const cdsHelper = require("./cdsHelper");
6
6
 
7
- const KEY_PREFIX = "EVENT_QUEUE";
8
7
  const existingLocks = {};
9
8
  const REDIS_COMMAND_OK = "OK";
10
9
  const COMPONENT_NAME = "/eventQueue/distributedLock";
@@ -176,10 +175,10 @@ const _acquireLockDB = async (context, fullKey, expiryTime, { value = "true", ov
176
175
  };
177
176
 
178
177
  const _generateKey = (context, tenantScoped, key) => {
179
- const keyParts = [];
178
+ const keyParts = [config.redisOptions.redisNamespace];
180
179
  tenantScoped && keyParts.push(context.tenant);
181
180
  keyParts.push(key);
182
- return `${KEY_PREFIX}_${keyParts.join("##")}`;
181
+ return `${keyParts.join("##")}`;
183
182
  };
184
183
 
185
184
  const shutdownHandler = async () => {
@@ -46,6 +46,7 @@ const _createClientBase = (redisOptions = {}) => {
46
46
  password: redisOptions.password ?? options.password ?? credentials.password,
47
47
  socket,
48
48
  });
49
+ delete socketOptions.redisNamespace;
49
50
  if (credentials.cluster_mode) {
50
51
  return redis.createCluster({
51
52
  rootNodes: [socketOptions],
@@ -106,9 +107,10 @@ const _subscribeChannels = (options, subscribedChannels, errorHandlerCreateClien
106
107
  if (client._subscribedChannels[channel]) {
107
108
  continue;
108
109
  }
109
- cds.log(COMPONENT_NAME).info("subscribe redis client connected channel", { channel });
110
+ const prefixedChannelName = [options.redisNamespace, channel].join("_");
111
+ cds.log(COMPONENT_NAME).info("subscribe redis client connected channel", { channel: prefixedChannelName });
110
112
  client
111
- .subscribe(channel, fn)
113
+ .subscribe(prefixedChannelName, fn)
112
114
  .then(() => {
113
115
  client._subscribedChannels ??= {};
114
116
  client._subscribedChannels[channel] = 1;
@@ -133,7 +135,7 @@ const _subscribeChannels = (options, subscribedChannels, errorHandlerCreateClien
133
135
 
134
136
  const publishMessage = async (options, channel, message) => {
135
137
  const client = await createMainClientAndConnect(options);
136
- return await client.publish(channel, message);
138
+ return await client.publish([options.redisNamespace, channel].join("_"), message);
137
139
  };
138
140
 
139
141
  const closeMainClient = async () => {