@cap-js-community/event-queue 2.0.4 → 2.1.0-beta.1
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 +7 -11
- package/src/EventQueueProcessorBase.js +18 -4
- package/src/config.js +34 -3
- package/src/initialize.js +1 -0
- package/src/outbox/EventQueueGenericOutboxHandler.js +81 -13
- package/src/outbox/eventQueueAsOutbox.js +14 -2
- package/src/publishEvent.js +8 -5
- package/src/redis/redisPub.js +5 -3
- package/src/shared/cdsHelper.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.1.0-beta.1",
|
|
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",
|
|
@@ -22,8 +22,6 @@
|
|
|
22
22
|
"multi-tenancy"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
-
"start": "PORT=4005 cds-serve",
|
|
26
|
-
"watch": "PORT=4005 cds watch",
|
|
27
25
|
"test:unit": "jest --selectProjects unit",
|
|
28
26
|
"test:integration": "jest --selectProjects integration --runInBand",
|
|
29
27
|
"voter:test:integration": "jest --selectProjects integration",
|
|
@@ -38,13 +36,12 @@
|
|
|
38
36
|
"eslint:ci": "eslint .",
|
|
39
37
|
"prettier": "prettier --write --loglevel error .",
|
|
40
38
|
"prettier:ci": "prettier --check .",
|
|
41
|
-
"prepareRelease": "npm prune --production",
|
|
42
39
|
"docs": "cd docs && bundle exec jekyll serve",
|
|
43
40
|
"docs:install": "cd docs && npx shx rm -rf vendor Gemfile.lock && bundle install",
|
|
44
41
|
"upgrade-lock": "npx shx rm -rf package-lock.json node_modules && npm i --package-lock"
|
|
45
42
|
},
|
|
46
43
|
"engines": {
|
|
47
|
-
"node": ">=
|
|
44
|
+
"node": ">=20"
|
|
48
45
|
},
|
|
49
46
|
"dependencies": {
|
|
50
47
|
"@cap-js-community/common": "^0.3.4",
|
|
@@ -56,17 +53,16 @@
|
|
|
56
53
|
"devDependencies": {
|
|
57
54
|
"@actions/core": "^2.0.2",
|
|
58
55
|
"@cap-js/cds-test": "^0.4.1",
|
|
59
|
-
"@cap-js/db-service": "^2.8.
|
|
60
|
-
"@cap-js/hana": "^2.
|
|
61
|
-
"@cap-js/sqlite": "^2.1.
|
|
56
|
+
"@cap-js/db-service": "^2.8.2",
|
|
57
|
+
"@cap-js/hana": "^2.6.0",
|
|
58
|
+
"@cap-js/sqlite": "^2.1.3",
|
|
62
59
|
"@opentelemetry/api": "^1.9.0",
|
|
63
|
-
"@sap/cds": "^9.
|
|
64
|
-
"@sap/cds-dk": "^9.
|
|
60
|
+
"@sap/cds": "^9.7.0",
|
|
61
|
+
"@sap/cds-dk": "^9.7.0",
|
|
65
62
|
"eslint": "^8.57.1",
|
|
66
63
|
"eslint-config-prettier": "^10.1.8",
|
|
67
64
|
"eslint-plugin-jest": "^29.12.1",
|
|
68
65
|
"eslint-plugin-node": "^11.1.0",
|
|
69
|
-
"express": "^4.22.1",
|
|
70
66
|
"jest": "^29.7.0",
|
|
71
67
|
"prettier": "^2.8.8"
|
|
72
68
|
},
|
|
@@ -25,7 +25,7 @@ const TRIES_FOR_EXCEEDED_EVENTS = 3;
|
|
|
25
25
|
const EVENT_START_AFTER_HEADROOM = 3 * 1000;
|
|
26
26
|
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
27
27
|
|
|
28
|
-
const ALLOWED_FIELDS_FOR_UPDATE = ["status", "startAfter", "error"];
|
|
28
|
+
const ALLOWED_FIELDS_FOR_UPDATE = ["status", "startAfter", "error", "nextData"];
|
|
29
29
|
|
|
30
30
|
class EventQueueProcessorBase {
|
|
31
31
|
#eventsWithExceededTries = [];
|
|
@@ -43,6 +43,7 @@ class EventQueueProcessorBase {
|
|
|
43
43
|
#currentKeepAlivePromise = Promise.resolve();
|
|
44
44
|
#etagMap;
|
|
45
45
|
#namespace;
|
|
46
|
+
#nextSagaEvents;
|
|
46
47
|
|
|
47
48
|
constructor(context, eventType, eventSubType, config) {
|
|
48
49
|
this.__context = context;
|
|
@@ -407,7 +408,7 @@ class EventQueueProcessorBase {
|
|
|
407
408
|
UPDATE.entity(this.#config.tableNameEventQueue)
|
|
408
409
|
.set({
|
|
409
410
|
status: status,
|
|
410
|
-
...(error && { error: this
|
|
411
|
+
...(error && { error: this._error2String(error) }),
|
|
411
412
|
})
|
|
412
413
|
.where({
|
|
413
414
|
ID: queueEntryIds,
|
|
@@ -468,6 +469,10 @@ class EventQueueProcessorBase {
|
|
|
468
469
|
return result;
|
|
469
470
|
}, {});
|
|
470
471
|
|
|
472
|
+
if (!Object.values(updateData).length) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
471
476
|
for (const { ids, data } of Object.values(updateData)) {
|
|
472
477
|
if (!("status" in data)) {
|
|
473
478
|
// TODO: Can this still happen?
|
|
@@ -493,7 +498,7 @@ class EventQueueProcessorBase {
|
|
|
493
498
|
}
|
|
494
499
|
|
|
495
500
|
if (data.error) {
|
|
496
|
-
data.error = this
|
|
501
|
+
data.error = this._error2String(data.error);
|
|
497
502
|
}
|
|
498
503
|
|
|
499
504
|
if (!data.startAfter && [EventProcessingStatus.Error, EventProcessingStatus.Open].includes(data.status)) {
|
|
@@ -522,10 +527,15 @@ class EventQueueProcessorBase {
|
|
|
522
527
|
.where("ID IN", ids)
|
|
523
528
|
);
|
|
524
529
|
}
|
|
530
|
+
|
|
531
|
+
if (this.#nextSagaEvents?.length) {
|
|
532
|
+
await tx.run(INSERT.into(this.#config.tableNameEventQueue).entries(this.#nextSagaEvents));
|
|
533
|
+
this.#nextSagaEvents = [];
|
|
534
|
+
}
|
|
525
535
|
});
|
|
526
536
|
}
|
|
527
537
|
|
|
528
|
-
|
|
538
|
+
_error2String(error) {
|
|
529
539
|
return JSON.stringify(error, (_, value) => this.#errorReplacer(value));
|
|
530
540
|
}
|
|
531
541
|
|
|
@@ -1390,6 +1400,10 @@ class EventQueueProcessorBase {
|
|
|
1390
1400
|
get allowedFieldsEventHandler() {
|
|
1391
1401
|
return ALLOWED_FIELDS_FOR_UPDATE;
|
|
1392
1402
|
}
|
|
1403
|
+
|
|
1404
|
+
set nextSagaEvents(value) {
|
|
1405
|
+
this.#nextSagaEvents = value;
|
|
1406
|
+
}
|
|
1393
1407
|
}
|
|
1394
1408
|
|
|
1395
1409
|
module.exports = EventQueueProcessorBase;
|
package/src/config.js
CHANGED
|
@@ -28,6 +28,8 @@ const DEFAULT_RETRY_AFTER = 5 * 60 * 1000;
|
|
|
28
28
|
const PRIORITIES = Object.values(Priorities);
|
|
29
29
|
const UTC_DEFAULT = false;
|
|
30
30
|
const USE_CRON_TZ_DEFAULT = true;
|
|
31
|
+
const SAGA_SUCCESS = "#succeeded";
|
|
32
|
+
const SAGA_FAILED = "#failed";
|
|
31
33
|
|
|
32
34
|
const BASE_TABLES = {
|
|
33
35
|
EVENT: "sap.eventqueue.Event",
|
|
@@ -62,6 +64,7 @@ const ALLOWED_EVENT_OPTIONS_AD_HOC = [
|
|
|
62
64
|
"checkForNextChunk",
|
|
63
65
|
"retryFailedAfter",
|
|
64
66
|
"propagateHeaders",
|
|
67
|
+
"propagatedContextProperties",
|
|
65
68
|
"retryOpenAfter",
|
|
66
69
|
"multiInstanceProcessing",
|
|
67
70
|
"kind",
|
|
@@ -236,7 +239,7 @@ class Config {
|
|
|
236
239
|
|
|
237
240
|
executeUnsubscribeHandlers(tenantId) {
|
|
238
241
|
this.#unsubscribedTenants[tenantId] = true;
|
|
239
|
-
setTimeout(() => delete this.#unsubscribedTenants[tenantId], DELETE_TENANT_BLOCK_AFTER_MS);
|
|
242
|
+
setTimeout(() => delete this.#unsubscribedTenants[tenantId], DELETE_TENANT_BLOCK_AFTER_MS).unref();
|
|
240
243
|
for (const unsubscribeHandler of this.#unsubscribeHandlers) {
|
|
241
244
|
try {
|
|
242
245
|
unsubscribeHandler(tenantId);
|
|
@@ -383,6 +386,20 @@ class Config {
|
|
|
383
386
|
result.adHoc
|
|
384
387
|
);
|
|
385
388
|
result.adHoc[key] = specificEventConfig;
|
|
389
|
+
const sagaSuccessKey = [fnName, SAGA_SUCCESS].join("/");
|
|
390
|
+
if (config.events[sagaSuccessKey]) {
|
|
391
|
+
const [sagaKey, sagaSpecificEventConfig] = this.addCAPOutboxEventSpecificAction(
|
|
392
|
+
srvConfig,
|
|
393
|
+
name,
|
|
394
|
+
fnName,
|
|
395
|
+
result.adHoc
|
|
396
|
+
);
|
|
397
|
+
result.adHoc[sagaKey] = sagaSpecificEventConfig;
|
|
398
|
+
} else {
|
|
399
|
+
const sagaConfig = { ...specificEventConfig };
|
|
400
|
+
sagaConfig.subType = [sagaConfig.subType, SAGA_SUCCESS].join("/");
|
|
401
|
+
result.adHoc[[key, SAGA_SUCCESS].join("/")] = sagaConfig;
|
|
402
|
+
}
|
|
386
403
|
}
|
|
387
404
|
}
|
|
388
405
|
return result;
|
|
@@ -405,11 +422,24 @@ class Config {
|
|
|
405
422
|
getCdsOutboxEventSpecificConfig(serviceName, action) {
|
|
406
423
|
const srv = cds.env.requires[serviceName];
|
|
407
424
|
const config = srv?.outbox ?? srv?.queued;
|
|
408
|
-
|
|
425
|
+
const specificConfig = config?.events?.[action];
|
|
426
|
+
|
|
427
|
+
if (specificConfig) {
|
|
409
428
|
return this.#mixCAPPropertyNamesWithEventQueueNames(config.events[action]);
|
|
410
|
-
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!action) {
|
|
411
432
|
return null;
|
|
412
433
|
}
|
|
434
|
+
|
|
435
|
+
const [withoutSaga, sagaSuffix] = action.split("/");
|
|
436
|
+
if ([SAGA_FAILED, SAGA_SUCCESS].includes(sagaSuffix)) {
|
|
437
|
+
if (config?.events?.[withoutSaga]) {
|
|
438
|
+
return this.#mixCAPPropertyNamesWithEventQueueNames(config.events[withoutSaga]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return null;
|
|
413
443
|
}
|
|
414
444
|
|
|
415
445
|
#mapEnvEvents(events) {
|
|
@@ -488,6 +518,7 @@ class Config {
|
|
|
488
518
|
? Object.fromEntries(new Map(event.appInstances.map((a) => [a, true])))
|
|
489
519
|
: null;
|
|
490
520
|
event.propagateHeaders = event.propagateHeaders ?? [];
|
|
521
|
+
event.propagatedContextProperties = event.propagatedContextProperties ?? [];
|
|
491
522
|
event.retryFailedAfter = event.retryFailedAfter ?? DEFAULT_RETRY_AFTER;
|
|
492
523
|
event.retryOpenAfter = event.retryOpenAfter ?? DEFAULT_RETRY_AFTER;
|
|
493
524
|
}
|
package/src/initialize.js
CHANGED
|
@@ -14,6 +14,8 @@ const EVENT_QUEUE_ACTIONS = {
|
|
|
14
14
|
EXCEEDED: "eventQueueRetriesExceeded",
|
|
15
15
|
CLUSTER: "eventQueueCluster",
|
|
16
16
|
CHECK_AND_ADJUST: "eventQueueCheckAndAdjustPayload",
|
|
17
|
+
SAGA_SUCCESS: "#succeeded",
|
|
18
|
+
SAGA_FAILED: "#failed",
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
@@ -25,7 +27,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
25
27
|
async getQueueEntriesAndSetToInProgress() {
|
|
26
28
|
const { srvName } = config.normalizeSubType(this.eventType, this.eventSubType);
|
|
27
29
|
this.__srv = await cds.connect.to(srvName);
|
|
28
|
-
this.__srvUnboxed = cds.
|
|
30
|
+
this.__srvUnboxed = cds.unqueued(this.__srv);
|
|
29
31
|
const { handlers, clusterRelevant, specificClusterRelevant } = this.__srvUnboxed.handlers.on.reduce(
|
|
30
32
|
(result, handler) => {
|
|
31
33
|
if (handler.on.startsWith(EVENT_QUEUE_ACTIONS.CLUSTER)) {
|
|
@@ -260,7 +262,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
260
262
|
async checkEventAndGeneratePayload(queueEntry) {
|
|
261
263
|
const payload = await super.checkEventAndGeneratePayload(queueEntry);
|
|
262
264
|
const { event } = payload;
|
|
263
|
-
const handlerName = this.#checkHandlerExists(EVENT_QUEUE_ACTIONS.CHECK_AND_ADJUST, event);
|
|
265
|
+
const handlerName = this.#checkHandlerExists({ eventQueueFn: EVENT_QUEUE_ACTIONS.CHECK_AND_ADJUST, event });
|
|
264
266
|
if (!handlerName) {
|
|
265
267
|
return payload;
|
|
266
268
|
}
|
|
@@ -281,7 +283,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
281
283
|
|
|
282
284
|
async hookForExceededEvents(exceededEvent) {
|
|
283
285
|
const { event } = exceededEvent.payload;
|
|
284
|
-
const handlerName = this.#checkHandlerExists(EVENT_QUEUE_ACTIONS.EXCEEDED, event);
|
|
286
|
+
const handlerName = this.#checkHandlerExists({ eventQueueFn: EVENT_QUEUE_ACTIONS.EXCEEDED, event });
|
|
285
287
|
if (!handlerName) {
|
|
286
288
|
return await super.hookForExceededEvents(exceededEvent);
|
|
287
289
|
}
|
|
@@ -299,14 +301,27 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
299
301
|
return await super.beforeProcessingEvents();
|
|
300
302
|
}
|
|
301
303
|
|
|
302
|
-
#checkHandlerExists(eventQueueFn, event) {
|
|
303
|
-
|
|
304
|
+
#checkHandlerExists({ eventQueueFn, event, saga } = {}) {
|
|
305
|
+
if (eventQueueFn) {
|
|
306
|
+
const specificHandler = this.__onHandlers[[eventQueueFn, event].join(".")];
|
|
307
|
+
if (specificHandler) {
|
|
308
|
+
return specificHandler;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const genericHandler = this.__onHandlers[eventQueueFn];
|
|
312
|
+
return genericHandler ?? null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (event.endsWith(EVENT_QUEUE_ACTIONS.SAGA_SUCCESS)) {
|
|
316
|
+
[event] = event.split("/");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const specificHandler = this.__onHandlers[[event, saga].join("/")];
|
|
304
320
|
if (specificHandler) {
|
|
305
321
|
return specificHandler;
|
|
306
322
|
}
|
|
307
323
|
|
|
308
|
-
|
|
309
|
-
return genericHandler ?? null;
|
|
324
|
+
return this.__onHandlers[saga];
|
|
310
325
|
}
|
|
311
326
|
|
|
312
327
|
async processPeriodicEvent(processContext, key, queueEntry) {
|
|
@@ -340,16 +355,17 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
340
355
|
}
|
|
341
356
|
|
|
342
357
|
async processEvent(processContext, key, queueEntries, payload) {
|
|
358
|
+
let statusTuple;
|
|
359
|
+
const { userId, invocationFn, reg } = this.#buildDispatchData(processContext, payload, { key, queueEntries });
|
|
343
360
|
try {
|
|
344
|
-
const { userId, invocationFn, reg } = this.#buildDispatchData(processContext, payload, { key, queueEntries });
|
|
345
361
|
await this.#setContextUser(processContext, userId, reg);
|
|
346
362
|
const result = await this.__srvUnboxed.tx(processContext)[invocationFn](reg);
|
|
347
|
-
|
|
363
|
+
statusTuple = this.#determineResultStatus(result, queueEntries);
|
|
348
364
|
} catch (err) {
|
|
349
365
|
this.logger.error("error processing outboxed service call", err, {
|
|
350
366
|
serviceName: this.eventSubType,
|
|
351
367
|
});
|
|
352
|
-
|
|
368
|
+
statusTuple = queueEntries.map((queueEntry) => [
|
|
353
369
|
queueEntry.ID,
|
|
354
370
|
{
|
|
355
371
|
status: EventProcessingStatus.Error,
|
|
@@ -357,13 +373,65 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
357
373
|
},
|
|
358
374
|
]);
|
|
359
375
|
}
|
|
376
|
+
|
|
377
|
+
await this.#publishFollowupEvents(processContext, reg, statusTuple);
|
|
378
|
+
return statusTuple;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async #publishFollowupEvents(processContext, req, statusTuple) {
|
|
382
|
+
const succeeded = this.#checkHandlerExists({ event: req.event, saga: EVENT_QUEUE_ACTIONS.SAGA_SUCCESS });
|
|
383
|
+
const failed = this.#checkHandlerExists({ event: req.event, saga: EVENT_QUEUE_ACTIONS.SAGA_FAILED });
|
|
384
|
+
|
|
385
|
+
if (!succeeded && !failed) {
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (req.event.endsWith(EVENT_QUEUE_ACTIONS.SAGA_FAILED)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// NOTE: required for #failed because tx is rolledback and new events would not be commmited!
|
|
394
|
+
const tx = cds.tx(processContext);
|
|
395
|
+
const nextEvents = tx._eventQueue?.events;
|
|
396
|
+
|
|
397
|
+
if (nextEvents?.length) {
|
|
398
|
+
tx._eventQueue.events = [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
for (const [, result] of statusTuple) {
|
|
402
|
+
const data = result.nextData ?? req.data;
|
|
403
|
+
if (
|
|
404
|
+
succeeded &&
|
|
405
|
+
result.status === EventProcessingStatus.Done &&
|
|
406
|
+
!req.event.endsWith(EVENT_QUEUE_ACTIONS.SAGA_SUCCESS)
|
|
407
|
+
) {
|
|
408
|
+
await this.__srv.tx(processContext).send(succeeded, data);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
if (failed && result.status === EventProcessingStatus.Error) {
|
|
412
|
+
result.error && (data.error = this._error2String(result.error));
|
|
413
|
+
await this.__srv.tx(processContext).send(failed, data);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
delete result.nextData;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (config.insertEventsBeforeCommit) {
|
|
420
|
+
this.nextSagaEvents = tx._eventQueue?.events;
|
|
421
|
+
} else {
|
|
422
|
+
this.nextSagaEvents = tx._eventQueue?.events.filter((event) => JSON.parse(event.payload).event === failed);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (tx._eventQueue) {
|
|
426
|
+
tx._eventQueue.events = nextEvents ?? [];
|
|
427
|
+
}
|
|
360
428
|
}
|
|
361
429
|
|
|
362
430
|
#determineResultStatus(result, queueEntries) {
|
|
363
431
|
const validStatusValues = Object.values(EventProcessingStatus);
|
|
364
432
|
const validStatus = validStatusValues.includes(result);
|
|
365
433
|
if (validStatus) {
|
|
366
|
-
return queueEntries.map((queueEntry) => [queueEntry.ID, result]);
|
|
434
|
+
return queueEntries.map((queueEntry) => [queueEntry.ID, { status: result }]);
|
|
367
435
|
}
|
|
368
436
|
|
|
369
437
|
if (result instanceof Object && !Array.isArray(result)) {
|
|
@@ -375,7 +443,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
375
443
|
}
|
|
376
444
|
|
|
377
445
|
if (!Array.isArray(result)) {
|
|
378
|
-
return queueEntries.map((queueEntry) => [queueEntry.ID, EventProcessingStatus.Done]);
|
|
446
|
+
return queueEntries.map((queueEntry) => [queueEntry.ID, { status: EventProcessingStatus.Done }]);
|
|
379
447
|
}
|
|
380
448
|
|
|
381
449
|
const [firstEntry] = result;
|
|
@@ -430,7 +498,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
430
498
|
if (valid) {
|
|
431
499
|
return result;
|
|
432
500
|
} else {
|
|
433
|
-
return queueEntries.map((queueEntry) => [queueEntry.ID, EventProcessingStatus.Done]);
|
|
501
|
+
return queueEntries.map((queueEntry) => [queueEntry.ID, { status: EventProcessingStatus.Done }]);
|
|
434
502
|
}
|
|
435
503
|
}
|
|
436
504
|
}
|
|
@@ -43,8 +43,9 @@ function outboxed(srv, customOpts) {
|
|
|
43
43
|
|
|
44
44
|
const outboxOpts = config.getEventConfig(CDS_EVENT_TYPE, subType, srvConfig.namespace);
|
|
45
45
|
const eventHeaders = getPropagatedHeaders(outboxOpts, req);
|
|
46
|
+
const contextProperties = getPropagatedContextProperties(outboxOpts, req);
|
|
46
47
|
if (["persistent-outbox", "persistent-queue"].includes(outboxOpts.kind)) {
|
|
47
|
-
await _mapToEventAndPublish(
|
|
48
|
+
await _mapToEventAndPublish(req, srvConfig.namespace, subType, eventHeaders, contextProperties);
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
51
|
context.on("succeeded", async () => {
|
|
@@ -80,7 +81,17 @@ const getPropagatedHeaders = (config, req) => {
|
|
|
80
81
|
return Object.assign(propagateHeaders, req.headers);
|
|
81
82
|
};
|
|
82
83
|
|
|
83
|
-
const
|
|
84
|
+
const getPropagatedContextProperties = (config, req) => {
|
|
85
|
+
return config.propagatedContextProperties.reduce((properties, name) => {
|
|
86
|
+
if (name in req.tx.context) {
|
|
87
|
+
properties[name] = req.tx.context[name];
|
|
88
|
+
}
|
|
89
|
+
return properties;
|
|
90
|
+
}, {});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const _mapToEventAndPublish = async (req, namespace, subType, eventHeaders, contextProperties) => {
|
|
94
|
+
const context = req.context || cds.context;
|
|
84
95
|
const eventQueueSpecificValues = {};
|
|
85
96
|
for (const header in req.headers ?? {}) {
|
|
86
97
|
for (const field of EVENT_QUEUE_SPECIFIC_FIELDS) {
|
|
@@ -100,6 +111,7 @@ const _mapToEventAndPublish = async (context, subType, req, eventHeaders, namesp
|
|
|
100
111
|
...(req.data && { data: req.data }),
|
|
101
112
|
...(eventHeaders && { headers: eventHeaders }),
|
|
102
113
|
...(req.query && { query: req.query }),
|
|
114
|
+
...(Object.keys(contextProperties).length && { ...contextProperties }),
|
|
103
115
|
};
|
|
104
116
|
|
|
105
117
|
await publishEvent(
|
package/src/publishEvent.js
CHANGED
|
@@ -76,8 +76,9 @@ const publishEvent = async (
|
|
|
76
76
|
event.namespace = config.namespace;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
+
_addEventsToContext(tx, events);
|
|
79
80
|
if (config.insertEventsBeforeCommit && !skipInsertEventsBeforeCommit) {
|
|
80
|
-
|
|
81
|
+
_registerHandler(tx, skipBroadcast);
|
|
81
82
|
} else {
|
|
82
83
|
let result;
|
|
83
84
|
tx._skipEventQueueBroadcast = skipBroadcast;
|
|
@@ -87,10 +88,7 @@ const publishEvent = async (
|
|
|
87
88
|
}
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
const
|
|
91
|
-
tx._eventQueue ??= { events: [], handlerRegistered: false };
|
|
92
|
-
tx._eventQueue.events = tx._eventQueue.events.concat(events);
|
|
93
|
-
|
|
91
|
+
const _registerHandler = (tx, skipBroadcast) => {
|
|
94
92
|
if (tx._eventQueue.handlerRegistered) {
|
|
95
93
|
return;
|
|
96
94
|
}
|
|
@@ -106,6 +104,11 @@ const _registerHandlerAndAddEvents = (tx, events, skipBroadcast) => {
|
|
|
106
104
|
});
|
|
107
105
|
};
|
|
108
106
|
|
|
107
|
+
const _addEventsToContext = (tx, events) => {
|
|
108
|
+
tx._eventQueue ??= { events: [], handlerRegistered: false };
|
|
109
|
+
tx._eventQueue.events = tx._eventQueue.events.concat(events);
|
|
110
|
+
};
|
|
111
|
+
|
|
109
112
|
module.exports = {
|
|
110
113
|
publishEvent,
|
|
111
114
|
};
|
package/src/redis/redisPub.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
const { promisify } = require("util");
|
|
4
|
-
|
|
5
3
|
const cds = require("@sap/cds");
|
|
6
4
|
|
|
7
5
|
const redis = require("../shared/redis");
|
|
@@ -17,7 +15,11 @@ const COMPONENT_NAME = "/eventQueue/redisPub";
|
|
|
17
15
|
const TRIES_FOR_PUBLISH_PERIODIC_EVENT = 10;
|
|
18
16
|
const SLEEP_TIME_FOR_PUBLISH_PERIODIC_EVENT = 30 * 1000;
|
|
19
17
|
|
|
20
|
-
const wait =
|
|
18
|
+
const wait = async (ms) => {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
setTimeout(resolve, ms).unref();
|
|
21
|
+
});
|
|
22
|
+
};
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
25
|
* Broadcasts events to the event queue, either locally or through Redis.
|
package/src/shared/cdsHelper.js
CHANGED
|
@@ -130,7 +130,7 @@ const _getAllTenantBase = async () => {
|
|
|
130
130
|
if (cds.services["cds.xt.SaasProvisioningService"] || cds.services["saas-registry"]) {
|
|
131
131
|
break;
|
|
132
132
|
}
|
|
133
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
133
|
+
await new Promise((resolve) => setTimeout(resolve, 1000).unref());
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
const ssp = await cds.connect.to("cds.xt.SaasProvisioningService");
|