@cap-js-community/event-queue 1.11.0-beta.5 → 2.0.0-beta.0
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/db/Event.cds +1 -0
- package/package.json +18 -12
- package/src/EventQueueProcessorBase.js +18 -3
- package/src/config.js +68 -208
- package/src/dbHandler.js +7 -4
- package/src/index.d.ts +10 -9
- package/src/initialize.js +8 -23
- package/src/outbox/eventQueueAsOutbox.js +19 -9
- package/src/periodicEvents.js +13 -12
- package/src/processEventQueue.js +7 -11
- package/src/publishEvent.js +15 -6
- package/src/redis/redisPub.js +10 -8
- package/src/redis/redisSub.js +16 -5
- package/src/runner/openEvents.js +12 -10
- package/src/runner/runner.js +26 -15
- package/src/runner/runnerHelper.js +10 -4
- package/src/shared/cdsHelper.js +0 -31
- package/src/shared/common.js +7 -2
- package/src/shared/distributedLock.js +23 -13
- package/src/shared/eventScheduler.js +3 -3
- package/src/shared/redis/client.js +46 -0
- package/src/shared/redis/index.js +45 -0
- package/srv/service/admin-service.cds +0 -8
- package/srv/service/admin-service.js +1 -10
- package/src/shared/redis.js +0 -199
package/db/Event.cds
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.0",
|
|
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",
|
|
@@ -47,29 +47,29 @@
|
|
|
47
47
|
"node": ">=18"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@
|
|
51
|
-
"
|
|
52
|
-
"
|
|
50
|
+
"@cap-js-community/common": "0.3.2",
|
|
51
|
+
"@sap/xssec": "^4.11.0",
|
|
52
|
+
"cron-parser": "^5.4.0",
|
|
53
53
|
"verror": "^1.10.1",
|
|
54
54
|
"yaml": "^2.7.1"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
+
"@actions/core": "^1.11.1",
|
|
57
58
|
"@cap-js/cds-test": "^0.4.0",
|
|
58
|
-
"@cap-js/hana": "^2.
|
|
59
|
-
"@cap-js/sqlite": "^2.0.
|
|
60
|
-
"@
|
|
61
|
-
"@sap/cds
|
|
59
|
+
"@cap-js/hana": "^2.3.4",
|
|
60
|
+
"@cap-js/sqlite": "^2.0.4",
|
|
61
|
+
"@opentelemetry/api": "^1.9.0",
|
|
62
|
+
"@sap/cds": "^9.4.4",
|
|
63
|
+
"@sap/cds-dk": "^9.4.2",
|
|
62
64
|
"eslint": "^8.57.0",
|
|
63
65
|
"eslint-config-prettier": "^9.1.0",
|
|
64
66
|
"eslint-plugin-jest": "^28.6.0",
|
|
65
67
|
"eslint-plugin-node": "^11.1.0",
|
|
66
68
|
"express": "^4.21.2",
|
|
67
|
-
"hdb": "^2.
|
|
69
|
+
"hdb": "^2.26.1",
|
|
68
70
|
"jest": "^29.7.0",
|
|
69
71
|
"prettier": "^2.8.8",
|
|
70
|
-
"sqlite3": "^5.1.7"
|
|
71
|
-
"@opentelemetry/api": "^1.9.0",
|
|
72
|
-
"@actions/core": "^1.11.1"
|
|
72
|
+
"sqlite3": "^5.1.7"
|
|
73
73
|
},
|
|
74
74
|
"homepage": "https://cap-js-community.github.io/event-queue/",
|
|
75
75
|
"repository": {
|
|
@@ -100,6 +100,12 @@
|
|
|
100
100
|
}
|
|
101
101
|
},
|
|
102
102
|
"requires": {
|
|
103
|
+
"xsuaa-eventQueue": {
|
|
104
|
+
"vcap": {
|
|
105
|
+
"label": "xsuaa",
|
|
106
|
+
"plan": "application"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
103
109
|
"redis-eventQueue": {
|
|
104
110
|
"options": {},
|
|
105
111
|
"vcap": {
|
|
@@ -42,15 +42,17 @@ class EventQueueProcessorBase {
|
|
|
42
42
|
#keepAliveRunner;
|
|
43
43
|
#currentKeepAlivePromise = Promise.resolve();
|
|
44
44
|
#etagMap;
|
|
45
|
+
#namespace;
|
|
45
46
|
|
|
46
47
|
constructor(context, eventType, eventSubType, config) {
|
|
47
48
|
this.__context = context;
|
|
48
49
|
this.__baseContext = context;
|
|
49
50
|
this.__tx = cds.tx(context);
|
|
50
51
|
this.__baseLogger = cds.log(COMPONENT_NAME);
|
|
52
|
+
this.#namespace = config.namespace;
|
|
51
53
|
this.#eventSchedulerInstance = eventScheduler.getInstance();
|
|
52
54
|
this.#config = eventConfig;
|
|
53
|
-
this.#isPeriodic = this.#config.isPeriodicEvent(eventType, eventSubType);
|
|
55
|
+
this.#isPeriodic = this.#config.isPeriodicEvent(eventType, eventSubType, this.#namespace);
|
|
54
56
|
this.__logger = null;
|
|
55
57
|
this.__eventProcessingMap = {};
|
|
56
58
|
this.__statusMap = {};
|
|
@@ -178,6 +180,7 @@ class EventQueueProcessorBase {
|
|
|
178
180
|
this.__context.tenant,
|
|
179
181
|
this.#eventType,
|
|
180
182
|
this.#eventSubType,
|
|
183
|
+
this.#namespace,
|
|
181
184
|
new Date(Date.now() + 5 * 1000) // add some offset to make sure all locks are released
|
|
182
185
|
);
|
|
183
186
|
}
|
|
@@ -477,6 +480,7 @@ class EventQueueProcessorBase {
|
|
|
477
480
|
this.__context.tenant,
|
|
478
481
|
this.#eventType,
|
|
479
482
|
this.#eventSubType,
|
|
483
|
+
this.#namespace,
|
|
480
484
|
startAfter
|
|
481
485
|
);
|
|
482
486
|
}
|
|
@@ -606,6 +610,8 @@ class EventQueueProcessorBase {
|
|
|
606
610
|
this.#eventType,
|
|
607
611
|
"AND subType=",
|
|
608
612
|
this.#eventSubType,
|
|
613
|
+
"AND namespace =",
|
|
614
|
+
this.#namespace,
|
|
609
615
|
"AND ( startAfter IS NULL OR startAfter <=",
|
|
610
616
|
refDateStartAfter.toISOString(),
|
|
611
617
|
" ) AND ( status =",
|
|
@@ -743,6 +749,7 @@ class EventQueueProcessorBase {
|
|
|
743
749
|
this.__context.tenant,
|
|
744
750
|
this.#eventType,
|
|
745
751
|
this.#eventSubType,
|
|
752
|
+
delayedEvent.namespace,
|
|
746
753
|
delayedEvent.startAfter
|
|
747
754
|
);
|
|
748
755
|
}
|
|
@@ -960,7 +967,7 @@ class EventQueueProcessorBase {
|
|
|
960
967
|
return await trace(this.baseContext, "acquire-lock", async () => {
|
|
961
968
|
const lockAcquired = await distributedLock.acquireLock(
|
|
962
969
|
this.__context,
|
|
963
|
-
[this.#eventType, this.#eventSubType].join("##"),
|
|
970
|
+
[this.#namespace, this.#eventType, this.#eventSubType].join("##"),
|
|
964
971
|
{ keepTrackOfLock: true, expiryTime: this.#eventConfig.keepAliveMaxInProgressTime * 1000 }
|
|
965
972
|
);
|
|
966
973
|
if (!lockAcquired) {
|
|
@@ -1002,7 +1009,10 @@ class EventQueueProcessorBase {
|
|
|
1002
1009
|
}
|
|
1003
1010
|
try {
|
|
1004
1011
|
await trace(this.baseContext, "release-lock", async () => {
|
|
1005
|
-
await distributedLock.releaseLock(
|
|
1012
|
+
await distributedLock.releaseLock(
|
|
1013
|
+
this.context,
|
|
1014
|
+
[this.#namespace, this.#eventType, this.#eventSubType].join("##")
|
|
1015
|
+
);
|
|
1006
1016
|
});
|
|
1007
1017
|
} catch (err) {
|
|
1008
1018
|
this.logger.error("Releasing distributed lock failed.", err);
|
|
@@ -1035,6 +1045,7 @@ class EventQueueProcessorBase {
|
|
|
1035
1045
|
const newEvent = {
|
|
1036
1046
|
type: this.#eventType,
|
|
1037
1047
|
subType: this.#eventSubType,
|
|
1048
|
+
namespace: this.#eventConfig.namespace,
|
|
1038
1049
|
startAfter: new Date(newStartAfter),
|
|
1039
1050
|
};
|
|
1040
1051
|
const { relative } = this.#eventSchedulerInstance.calculateOffset(
|
|
@@ -1293,6 +1304,10 @@ class EventQueueProcessorBase {
|
|
|
1293
1304
|
get inheritTraceContext() {
|
|
1294
1305
|
return this.#eventConfig.inheritTraceContext;
|
|
1295
1306
|
}
|
|
1307
|
+
|
|
1308
|
+
get namespace() {
|
|
1309
|
+
return this.#namespace;
|
|
1310
|
+
}
|
|
1296
1311
|
}
|
|
1297
1312
|
|
|
1298
1313
|
module.exports = EventQueueProcessorBase;
|
package/src/config.js
CHANGED
|
@@ -4,18 +4,12 @@ const cds = require("@sap/cds");
|
|
|
4
4
|
const { CronExpressionParser } = require("cron-parser");
|
|
5
5
|
|
|
6
6
|
const { getEnvInstance } = require("./shared/env");
|
|
7
|
-
const redis = require("./shared/redis");
|
|
8
7
|
const EventQueueError = require("./EventQueueError");
|
|
9
8
|
const { Priorities } = require("./constants");
|
|
10
9
|
|
|
11
10
|
const FOR_UPDATE_TIMEOUT = 10;
|
|
12
11
|
const GLOBAL_TX_TIMEOUT = 30 * 60 * 1000;
|
|
13
12
|
const REDIS_PREFIX = "EVENT_QUEUE";
|
|
14
|
-
const REDIS_CONFIG_CHANNEL = "CONFIG_CHANNEL";
|
|
15
|
-
const REDIS_OFFBOARD_TENANT_CHANNEL = "REDIS_OFFBOARD_TENANT_CHANNEL";
|
|
16
|
-
const REDIS_CONFIG_BLOCKLIST_CHANNEL = "REDIS_CONFIG_BLOCKLIST_CHANNEL";
|
|
17
|
-
const COMMAND_BLOCK = "EVENT_BLOCK";
|
|
18
|
-
const COMMAND_UNBLOCK = "EVENT_UNBLOCK";
|
|
19
13
|
const COMPONENT_NAME = "/eventQueue/config";
|
|
20
14
|
const MIN_INTERVAL_SEC = 10;
|
|
21
15
|
const DEFAULT_LOAD = 1;
|
|
@@ -53,6 +47,7 @@ const ALLOWED_EVENT_OPTIONS_BASE = [
|
|
|
53
47
|
"keepAliveMaxInProgressTime",
|
|
54
48
|
"appNames",
|
|
55
49
|
"appInstances",
|
|
50
|
+
"namespace",
|
|
56
51
|
"internalEvent",
|
|
57
52
|
];
|
|
58
53
|
|
|
@@ -98,7 +93,6 @@ class Config {
|
|
|
98
93
|
#env;
|
|
99
94
|
#eventMap;
|
|
100
95
|
#updatePeriodicEvents;
|
|
101
|
-
#blockedEvents;
|
|
102
96
|
#isEventBlockedCb;
|
|
103
97
|
#thresholdLoggingEventProcessing;
|
|
104
98
|
#useAsCAPOutbox;
|
|
@@ -112,7 +106,6 @@ class Config {
|
|
|
112
106
|
#cronTimezone;
|
|
113
107
|
#randomOffsetPeriodicEvents;
|
|
114
108
|
#redisNamespace;
|
|
115
|
-
#publishEventBlockList;
|
|
116
109
|
#crashOnRedisUnavailable;
|
|
117
110
|
#tenantIdFilterAuthContextCb;
|
|
118
111
|
#tenantIdFilterEventProcessingCb;
|
|
@@ -120,6 +113,8 @@ class Config {
|
|
|
120
113
|
#configPeriodicEvents;
|
|
121
114
|
#enableAdminService;
|
|
122
115
|
#disableProcessingOfSuspendedTenants;
|
|
116
|
+
#namespace;
|
|
117
|
+
#processingNamespaces;
|
|
123
118
|
static #instance;
|
|
124
119
|
constructor() {
|
|
125
120
|
this.#logger = cds.log(COMPONENT_NAME);
|
|
@@ -137,12 +132,11 @@ class Config {
|
|
|
137
132
|
this.#processEventsAfterPublish = null;
|
|
138
133
|
this.#disableRedis = null;
|
|
139
134
|
this.#env = getEnvInstance();
|
|
140
|
-
this.#blockedEvents = {};
|
|
141
135
|
}
|
|
142
136
|
|
|
143
|
-
getEventConfig(type, subType) {
|
|
144
|
-
return this.#eventMap[this.generateKey(type, subType)]
|
|
145
|
-
? { ...this.#eventMap[this.generateKey(type, subType)] }
|
|
137
|
+
getEventConfig(type, subType, namespace = this.namespace) {
|
|
138
|
+
return this.#eventMap[this.generateKey(namespace, type, subType)]
|
|
139
|
+
? { ...this.#eventMap[this.generateKey(namespace, type, subType)] }
|
|
146
140
|
: undefined;
|
|
147
141
|
}
|
|
148
142
|
|
|
@@ -150,8 +144,8 @@ class Config {
|
|
|
150
144
|
return type === CAP_EVENT_TYPE;
|
|
151
145
|
}
|
|
152
146
|
|
|
153
|
-
hasEventAfterCommitFlag(type, subType) {
|
|
154
|
-
return this.#eventMap[this.generateKey(type, subType)]?.processAfterCommit ?? true;
|
|
147
|
+
hasEventAfterCommitFlag(type, subType, namespace = this.namespace) {
|
|
148
|
+
return this.#eventMap[this.generateKey(namespace, type, subType)]?.processAfterCommit ?? true;
|
|
155
149
|
}
|
|
156
150
|
|
|
157
151
|
_checkRedisIsBound() {
|
|
@@ -178,10 +172,10 @@ class Config {
|
|
|
178
172
|
return actionSpecificCall ? rawSubType : serviceName;
|
|
179
173
|
}
|
|
180
174
|
|
|
181
|
-
shouldBeProcessedInThisApplication(type, rawSubType) {
|
|
175
|
+
shouldBeProcessedInThisApplication(type, rawSubType, namespace = this.namespace) {
|
|
182
176
|
const subType = this.#normalizeSubType(rawSubType);
|
|
183
177
|
|
|
184
|
-
const config = this.#eventMap[this.generateKey(type, subType)];
|
|
178
|
+
const config = this.#eventMap[this.generateKey(namespace, type, subType)];
|
|
185
179
|
const appNameConfig = config._appNameMap;
|
|
186
180
|
const appInstanceConfig = config._appInstancesMap;
|
|
187
181
|
let result = true;
|
|
@@ -226,38 +220,6 @@ class Config {
|
|
|
226
220
|
return this.#redisEnabled;
|
|
227
221
|
}
|
|
228
222
|
|
|
229
|
-
attachConfigChangeHandler() {
|
|
230
|
-
this.#attachBlockListChangeHandler();
|
|
231
|
-
redis.subscribeRedisChannel(this.redisOptions, REDIS_CONFIG_CHANNEL, (messageData) => {
|
|
232
|
-
try {
|
|
233
|
-
const { key, value } = JSON.parse(messageData);
|
|
234
|
-
if (this[key] !== value) {
|
|
235
|
-
this.#logger.info("received config change", { key, value });
|
|
236
|
-
this[key] = value;
|
|
237
|
-
}
|
|
238
|
-
} catch (err) {
|
|
239
|
-
this.#logger.error("could not parse event config change", err, {
|
|
240
|
-
messageData,
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
attachRedisUnsubscribeHandler() {
|
|
247
|
-
this.#logger.info("attached redis handle for unsubscribe events");
|
|
248
|
-
redis.subscribeRedisChannel(this.redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, (messageData) => {
|
|
249
|
-
try {
|
|
250
|
-
const { tenantId } = JSON.parse(messageData);
|
|
251
|
-
this.#logger.info("received unsubscribe broadcast event", { tenantId });
|
|
252
|
-
this.executeUnsubscribeHandlers(tenantId);
|
|
253
|
-
} catch (err) {
|
|
254
|
-
this.#logger.error("could not parse unsubscribe broadcast event", err, {
|
|
255
|
-
messageData,
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
223
|
executeUnsubscribeHandlers(tenantId) {
|
|
262
224
|
this.#unsubscribedTenants[tenantId] = true;
|
|
263
225
|
setTimeout(() => delete this.#unsubscribedTenants[tenantId], DELETE_TENANT_BLOCK_AFTER_MS);
|
|
@@ -272,122 +234,24 @@ class Config {
|
|
|
272
234
|
}
|
|
273
235
|
}
|
|
274
236
|
|
|
275
|
-
handleUnsubscribe(tenantId) {
|
|
276
|
-
if (this.redisEnabled) {
|
|
277
|
-
redis
|
|
278
|
-
.publishMessage(this.redisOptions, REDIS_OFFBOARD_TENANT_CHANNEL, JSON.stringify({ tenantId }))
|
|
279
|
-
.catch((error) => {
|
|
280
|
-
this.#logger.error(`publishing tenant unsubscribe failed. tenantId: ${tenantId}`, error);
|
|
281
|
-
});
|
|
282
|
-
} else {
|
|
283
|
-
this.executeUnsubscribeHandlers(tenantId);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
237
|
attachUnsubscribeHandler(cb) {
|
|
288
238
|
this.#unsubscribeHandlers.push(cb);
|
|
289
239
|
}
|
|
290
240
|
|
|
291
|
-
publishConfigChange(key, value) {
|
|
292
|
-
if (!this.redisEnabled) {
|
|
293
|
-
this.#logger.info("redis not connected, config change won't be published", { key, value });
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
redis.publishMessage(this.redisOptions, REDIS_CONFIG_CHANNEL, JSON.stringify({ key, value })).catch((error) => {
|
|
297
|
-
this.#logger.error(`publishing config change failed key: ${key}, value: ${value}`, error);
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
#attachBlockListChangeHandler() {
|
|
302
|
-
redis.subscribeRedisChannel(this.redisOptions, REDIS_CONFIG_BLOCKLIST_CHANNEL, (messageData) => {
|
|
303
|
-
try {
|
|
304
|
-
const { command, key, tenant } = JSON.parse(messageData);
|
|
305
|
-
if (command === COMMAND_BLOCK) {
|
|
306
|
-
this.#blockEventLocalState(key, tenant);
|
|
307
|
-
} else {
|
|
308
|
-
this.#unblockEventLocalState(key, tenant);
|
|
309
|
-
}
|
|
310
|
-
} catch (err) {
|
|
311
|
-
this.#logger.error("could not parse event blocklist change", err, {
|
|
312
|
-
messageData,
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
blockEvent(type, subType, isPeriodic, tenant = "*") {
|
|
319
|
-
const typeWithSuffix = `${type}${isPeriodic ? SUFFIX_PERIODIC : ""}`;
|
|
320
|
-
const config = this.getEventConfig(typeWithSuffix, subType);
|
|
321
|
-
if (!config) {
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
const key = this.generateKey(typeWithSuffix, subType);
|
|
325
|
-
this.#blockEventLocalState(key, tenant);
|
|
326
|
-
if (!this.redisEnabled || !this.publishEventBlockList) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
redis
|
|
331
|
-
.publishMessage(
|
|
332
|
-
this.redisOptions,
|
|
333
|
-
REDIS_CONFIG_BLOCKLIST_CHANNEL,
|
|
334
|
-
JSON.stringify({ command: COMMAND_BLOCK, key, tenant })
|
|
335
|
-
)
|
|
336
|
-
.catch((error) => {
|
|
337
|
-
this.#logger.error(`publishing config block failed key: ${key}`, error);
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
#blockEventLocalState(key, tenant) {
|
|
342
|
-
this.#blockedEvents[key] ??= {};
|
|
343
|
-
this.#blockedEvents[key][tenant] = true;
|
|
344
|
-
return key;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
clearPeriodicEventBlockList() {
|
|
348
|
-
this.#blockedEvents = {};
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
unblockEvent(type, subType, isPeriodic, tenant = "*") {
|
|
352
|
-
const typeWithSuffix = `${type}${isPeriodic ? SUFFIX_PERIODIC : ""}`;
|
|
353
|
-
const key = this.generateKey(typeWithSuffix, subType);
|
|
354
|
-
const config = this.getEventConfig(typeWithSuffix, subType);
|
|
355
|
-
if (!config) {
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
this.#unblockEventLocalState(key, tenant);
|
|
359
|
-
if (!this.redisEnabled) {
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
redis
|
|
364
|
-
.publishMessage(
|
|
365
|
-
this.redisOptions,
|
|
366
|
-
REDIS_CONFIG_BLOCKLIST_CHANNEL,
|
|
367
|
-
JSON.stringify({ command: COMMAND_UNBLOCK, key, tenant })
|
|
368
|
-
)
|
|
369
|
-
.catch((error) => {
|
|
370
|
-
this.#logger.error(`publishing config block failed key: ${key}`, error);
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
241
|
addCAPOutboxEventBase(serviceName, config) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
(event) => event.type === CAP_EVENT_TYPE && event.subType === serviceName
|
|
378
|
-
);
|
|
379
|
-
this.#config.events.splice(index, 1);
|
|
380
|
-
}
|
|
242
|
+
const namespace = config.namespace ?? this.#namespace;
|
|
243
|
+
delete this.#eventMap[this.generateKey(namespace, CAP_EVENT_TYPE, serviceName)];
|
|
381
244
|
|
|
382
|
-
// NOTE: CAP outbox defaults are injected by cds.requires.outbox
|
|
245
|
+
// NOTE: CAP outbox defaults are injected by cds.requires.outbox // cds.requires.queue
|
|
383
246
|
const eventConfig = this.#sanitizeParamsAdHocEvent({
|
|
384
247
|
type: CAP_EVENT_TYPE,
|
|
385
248
|
subType: serviceName,
|
|
386
249
|
impl: "./outbox/EventQueueGenericOutboxHandler",
|
|
387
|
-
kind: config.kind ?? "persistent-
|
|
250
|
+
kind: config.kind ?? "persistent-queue",
|
|
388
251
|
selectMaxChunkSize: config.selectMaxChunkSize ?? config.chunkSize,
|
|
389
252
|
parallelEventProcessing: config.parallelEventProcessing ?? (config.parallel && CAP_PARALLEL_DEFAULT),
|
|
390
253
|
retryAttempts: config.retryAttempts ?? config.maxAttempts ?? CAP_MAX_ATTEMPTS_DEFAULT,
|
|
254
|
+
namespace,
|
|
391
255
|
...config,
|
|
392
256
|
});
|
|
393
257
|
eventConfig.internalEvent = true;
|
|
@@ -395,21 +259,20 @@ class Config {
|
|
|
395
259
|
this.#basicEventTransformation(eventConfig);
|
|
396
260
|
this.#validateAdHocEvents(this.#eventMap, eventConfig, false);
|
|
397
261
|
this.#basicEventTransformationAfterValidate(eventConfig);
|
|
398
|
-
this.#
|
|
399
|
-
this.#eventMap[this.generateKey(CAP_EVENT_TYPE, serviceName)] = eventConfig;
|
|
262
|
+
this.#eventMap[this.generateKey(namespace, CAP_EVENT_TYPE, serviceName)] = eventConfig;
|
|
400
263
|
}
|
|
401
264
|
|
|
402
265
|
addCAPOutboxEventSpecificAction(serviceName, actionName) {
|
|
403
266
|
const subType = [serviceName, actionName].join(".");
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
);
|
|
408
|
-
this.#config.events.splice(index, 1);
|
|
267
|
+
|
|
268
|
+
const specific = this.#findBaseCAPServiceWithoutNamespace(subType);
|
|
269
|
+
if (specific) {
|
|
270
|
+
delete this.#eventMap[this.generateKey(specific.namespace, CAP_EVENT_TYPE, subType)];
|
|
409
271
|
}
|
|
410
272
|
|
|
273
|
+
const base = this.#findBaseCAPServiceWithoutNamespace(serviceName);
|
|
411
274
|
const eventConfig = this.#sanitizeParamsAdHocEvent({
|
|
412
|
-
...this.getEventConfig(CAP_EVENT_TYPE, serviceName),
|
|
275
|
+
...this.getEventConfig(CAP_EVENT_TYPE, serviceName, base.namespace),
|
|
413
276
|
...this.getCdsOutboxEventSpecificConfig(serviceName, actionName),
|
|
414
277
|
subType,
|
|
415
278
|
});
|
|
@@ -418,28 +281,14 @@ class Config {
|
|
|
418
281
|
this.#basicEventTransformation(eventConfig);
|
|
419
282
|
this.#validateAdHocEvents(this.#eventMap, eventConfig, false);
|
|
420
283
|
this.#basicEventTransformationAfterValidate(eventConfig);
|
|
421
|
-
this.#
|
|
422
|
-
this.#eventMap[this.generateKey(CAP_EVENT_TYPE, subType)] = eventConfig;
|
|
284
|
+
this.#eventMap[this.generateKey(eventConfig.namespace, CAP_EVENT_TYPE, subType)] = eventConfig;
|
|
423
285
|
return eventConfig;
|
|
424
286
|
}
|
|
425
287
|
|
|
426
|
-
#
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
431
|
-
this.#blockedEvents[key][tenant] = false;
|
|
432
|
-
return key;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
isEventBlocked(type, subType, isPeriodicEvent, tenant) {
|
|
436
|
-
const map = this.#blockedEvents[this.generateKey(`${type}${isPeriodicEvent ? SUFFIX_PERIODIC : ""}`, subType)];
|
|
437
|
-
if (!map) {
|
|
438
|
-
return false;
|
|
439
|
-
}
|
|
440
|
-
const tenantSpecific = map[tenant];
|
|
441
|
-
const allTenants = map["*"];
|
|
442
|
-
return tenantSpecific ?? allTenants;
|
|
288
|
+
#findBaseCAPServiceWithoutNamespace(serviceName) {
|
|
289
|
+
return Object.values(this.#eventMap).find(
|
|
290
|
+
(event) => event.type === CAP_EVENT_TYPE && event.subType === serviceName
|
|
291
|
+
);
|
|
443
292
|
}
|
|
444
293
|
|
|
445
294
|
get isEventQueueActive() {
|
|
@@ -469,10 +318,11 @@ class Config {
|
|
|
469
318
|
|
|
470
319
|
#cdsPeriodicOutboxServicesFromEnv() {
|
|
471
320
|
return Object.entries(cds.env.requires).reduce((result, [name, value]) => {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
const
|
|
321
|
+
const config = value.outbox ?? value.queued;
|
|
322
|
+
if (config?.events) {
|
|
323
|
+
for (const fnName in config.events) {
|
|
324
|
+
const base = { ...config };
|
|
325
|
+
const fnConfig = config.events[fnName];
|
|
476
326
|
if (fnConfig.interval || fnConfig.cron) {
|
|
477
327
|
if ("interval" in base || "cron" in base) {
|
|
478
328
|
this.#logger.error(
|
|
@@ -502,8 +352,10 @@ class Config {
|
|
|
502
352
|
}
|
|
503
353
|
|
|
504
354
|
getCdsOutboxEventSpecificConfig(serviceName, action) {
|
|
505
|
-
|
|
506
|
-
|
|
355
|
+
const srv = cds.env.requires[serviceName];
|
|
356
|
+
const config = srv?.outbox ?? srv?.queued;
|
|
357
|
+
if (config?.events?.[action]) {
|
|
358
|
+
return config.events[action];
|
|
507
359
|
} else {
|
|
508
360
|
return null;
|
|
509
361
|
}
|
|
@@ -531,7 +383,7 @@ class Config {
|
|
|
531
383
|
this.#basicEventTransformation(eventSanitized);
|
|
532
384
|
this.#validateAdHocEvents(result, eventSanitized);
|
|
533
385
|
this.#basicEventTransformationAfterValidate(eventSanitized);
|
|
534
|
-
result[this.generateKey(eventSanitized.type, eventSanitized.subType)] = eventSanitized;
|
|
386
|
+
result[this.generateKey(eventSanitized.namespace, eventSanitized.type, eventSanitized.subType)] = eventSanitized;
|
|
535
387
|
return result;
|
|
536
388
|
}, {});
|
|
537
389
|
this.#eventMap = config.periodicEvents.reduce((result, event) => {
|
|
@@ -541,7 +393,7 @@ class Config {
|
|
|
541
393
|
this.#basicEventTransformation(eventSanitized);
|
|
542
394
|
this.#validatePeriodicConfig(result, eventSanitized);
|
|
543
395
|
this.#basicEventTransformationAfterValidate(eventSanitized);
|
|
544
|
-
result[this.generateKey(eventSanitized.type, eventSanitized.subType)] = eventSanitized;
|
|
396
|
+
result[this.generateKey(eventSanitized.namespace, eventSanitized.type, eventSanitized.subType)] = eventSanitized;
|
|
545
397
|
return result;
|
|
546
398
|
}, this.#eventMap);
|
|
547
399
|
this.#config = config;
|
|
@@ -554,6 +406,7 @@ class Config {
|
|
|
554
406
|
event.keepAliveInterval = event.keepAliveInterval ?? DEFAULT_KEEP_ALIVE_INTERVAL;
|
|
555
407
|
event.keepAliveMaxInProgressTime = event.keepAliveInterval * DEFAULT_MAX_FACTOR_STUCK_2_KEEP_ALIVE_INTERVAL;
|
|
556
408
|
event.checkForNextChunk = event.checkForNextChunk ?? DEFAULT_CHECK_FOR_NEXT_CHUNK;
|
|
409
|
+
event.namespace = event.namespace === undefined ? this.#namespace : event.namespace;
|
|
557
410
|
}
|
|
558
411
|
|
|
559
412
|
#sanitizeParamsBase(config, allowList) {
|
|
@@ -623,7 +476,7 @@ class Config {
|
|
|
623
476
|
}
|
|
624
477
|
|
|
625
478
|
#validatePeriodicConfig(eventMap, event) {
|
|
626
|
-
const key = this.generateKey(event.type, event.subType);
|
|
479
|
+
const key = this.generateKey(event.namespace, event.type, event.subType);
|
|
627
480
|
if (eventMap[key] && eventMap[key].isPeriodic) {
|
|
628
481
|
throw EventQueueError.duplicateEventRegistration(event.type, event.subType);
|
|
629
482
|
}
|
|
@@ -674,7 +527,7 @@ class Config {
|
|
|
674
527
|
}
|
|
675
528
|
|
|
676
529
|
#validateAdHocEvents(eventMap, event, checkForDuplication = true) {
|
|
677
|
-
const key = this.generateKey(event.type, event.subType);
|
|
530
|
+
const key = this.generateKey(event.namespace, event.type, event.subType);
|
|
678
531
|
if (eventMap[key] && !eventMap[key].isPeriodic && checkForDuplication) {
|
|
679
532
|
throw EventQueueError.duplicateEventRegistration(event.type, event.subType);
|
|
680
533
|
}
|
|
@@ -695,22 +548,22 @@ class Config {
|
|
|
695
548
|
this.#basicEventValidation(event);
|
|
696
549
|
}
|
|
697
550
|
|
|
698
|
-
generateKey(type, subType) {
|
|
699
|
-
return [type, subType].join("##");
|
|
551
|
+
generateKey(namespace, type, subType) {
|
|
552
|
+
return [namespace, type, subType].join("##");
|
|
700
553
|
}
|
|
701
554
|
|
|
702
|
-
removeEvent(type, subType) {
|
|
703
|
-
|
|
704
|
-
if (index >= 0) {
|
|
705
|
-
this.#config.events.splice(index, 1);
|
|
706
|
-
}
|
|
707
|
-
delete this.#eventMap[this.generateKey(type, subType)];
|
|
555
|
+
removeEvent(type, subType, namespace = this.namespace) {
|
|
556
|
+
delete this.#eventMap[this.generateKey(namespace, type, subType)];
|
|
708
557
|
}
|
|
709
558
|
|
|
710
559
|
isTenantUnsubscribed(tenantId) {
|
|
711
560
|
return this.#unsubscribedTenants[tenantId];
|
|
712
561
|
}
|
|
713
562
|
|
|
563
|
+
shouldProcessNamespace(namespace) {
|
|
564
|
+
return this.#processingNamespaces.includes(namespace);
|
|
565
|
+
}
|
|
566
|
+
|
|
714
567
|
get fileContent() {
|
|
715
568
|
return this.#config;
|
|
716
569
|
}
|
|
@@ -735,8 +588,8 @@ class Config {
|
|
|
735
588
|
return Object.values(this.#eventMap).filter((e) => e.isPeriodic);
|
|
736
589
|
}
|
|
737
590
|
|
|
738
|
-
isPeriodicEvent(type, subType) {
|
|
739
|
-
return this.#eventMap[this.generateKey(type, subType)]?.isPeriodic;
|
|
591
|
+
isPeriodicEvent(type, subType, namespace = this.namespace) {
|
|
592
|
+
return this.#eventMap[this.generateKey(namespace, type, subType)]?.isPeriodic;
|
|
740
593
|
}
|
|
741
594
|
|
|
742
595
|
get allEvents() {
|
|
@@ -755,14 +608,6 @@ class Config {
|
|
|
755
608
|
this.#forUpdateTimeout = value;
|
|
756
609
|
}
|
|
757
610
|
|
|
758
|
-
get publishEventBlockList() {
|
|
759
|
-
return this.#publishEventBlockList;
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
set publishEventBlockList(value) {
|
|
763
|
-
this.#publishEventBlockList = value;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
611
|
get crashOnRedisUnavailable() {
|
|
767
612
|
return this.#crashOnRedisUnavailable;
|
|
768
613
|
}
|
|
@@ -937,7 +782,6 @@ class Config {
|
|
|
937
782
|
get redisOptions() {
|
|
938
783
|
return {
|
|
939
784
|
...this.#redisOptions,
|
|
940
|
-
redisNamespace: `${[REDIS_PREFIX, this.redisNamespace].filter((a) => a).join("_")}`,
|
|
941
785
|
};
|
|
942
786
|
}
|
|
943
787
|
|
|
@@ -946,7 +790,7 @@ class Config {
|
|
|
946
790
|
}
|
|
947
791
|
|
|
948
792
|
get redisNamespace() {
|
|
949
|
-
return this.#redisNamespace
|
|
793
|
+
return `${[REDIS_PREFIX, this.#redisNamespace].filter((a) => a).join("##")}`;
|
|
950
794
|
}
|
|
951
795
|
|
|
952
796
|
set insertEventsBeforeCommit(value) {
|
|
@@ -993,6 +837,22 @@ class Config {
|
|
|
993
837
|
this.#disableProcessingOfSuspendedTenants = value;
|
|
994
838
|
}
|
|
995
839
|
|
|
840
|
+
get namespace() {
|
|
841
|
+
return this.#namespace;
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
set namespace(value) {
|
|
845
|
+
this.#namespace = value;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
get processingNamespaces() {
|
|
849
|
+
return this.#processingNamespaces;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
set processingNamespaces(value) {
|
|
853
|
+
this.#processingNamespaces = value;
|
|
854
|
+
}
|
|
855
|
+
|
|
996
856
|
/**
|
|
997
857
|
@return { Config }
|
|
998
858
|
**/
|
package/src/dbHandler.js
CHANGED
|
@@ -28,8 +28,11 @@ const registerEventQueueDbHandler = (dbService) => {
|
|
|
28
28
|
const data = Array.isArray(req.query.INSERT.entries) ? req.query.INSERT.entries : [req.query.INSERT.entries];
|
|
29
29
|
const eventCombinations = Object.keys(
|
|
30
30
|
data.reduce((result, event) => {
|
|
31
|
-
const key = [event.type, event.subType].join("##");
|
|
32
|
-
if (
|
|
31
|
+
const key = [event.type, event.subType, event.namespace].join("##");
|
|
32
|
+
if (
|
|
33
|
+
!config.hasEventAfterCommitFlag(event.type, event.subType, event.namespace) ||
|
|
34
|
+
eventQueuePublishEvents[key]
|
|
35
|
+
) {
|
|
33
36
|
return result;
|
|
34
37
|
}
|
|
35
38
|
eventQueuePublishEvents[key] = true;
|
|
@@ -41,8 +44,8 @@ const registerEventQueueDbHandler = (dbService) => {
|
|
|
41
44
|
eventCombinations.length &&
|
|
42
45
|
req.on("succeeded", () => {
|
|
43
46
|
const events = eventCombinations.map((eventCombination) => {
|
|
44
|
-
const [type, subType] = eventCombination.split("##");
|
|
45
|
-
return { type, subType };
|
|
47
|
+
const [type, subType, namespace] = eventCombination.split("##");
|
|
48
|
+
return { type, subType, namespace };
|
|
46
49
|
});
|
|
47
50
|
|
|
48
51
|
redisPub.broadcastEvent(req.tenant, events).catch((err) => {
|