@cap-js-community/event-queue 2.0.0-beta.4 → 2.0.0-beta.5
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": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.5",
|
|
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",
|
|
@@ -24,7 +24,8 @@ const SELECT_LIMIT_EVENTS_PER_TICK = 100;
|
|
|
24
24
|
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
29
|
|
|
29
30
|
class EventQueueProcessorBase {
|
|
30
31
|
#eventsWithExceededTries = [];
|
|
@@ -38,7 +39,6 @@ class EventQueueProcessorBase {
|
|
|
38
39
|
#eventConfig;
|
|
39
40
|
#isPeriodic;
|
|
40
41
|
#lastSuccessfulRunTimestamp;
|
|
41
|
-
#retryFailedAfter;
|
|
42
42
|
#keepAliveRunner;
|
|
43
43
|
#currentKeepAlivePromise = Promise.resolve();
|
|
44
44
|
#etagMap;
|
|
@@ -66,7 +66,6 @@ class EventQueueProcessorBase {
|
|
|
66
66
|
if (this.__parallelEventProcessing > LIMIT_PARALLEL_EVENT_PROCESSING) {
|
|
67
67
|
this.__parallelEventProcessing = LIMIT_PARALLEL_EVENT_PROCESSING;
|
|
68
68
|
}
|
|
69
|
-
this.#retryFailedAfter = this.#eventConfig.retryFailedAfter ?? DEFAULT_RETRY_AFTER;
|
|
70
69
|
this.__concurrentEventProcessing = this.#eventConfig.multiInstanceProcessing;
|
|
71
70
|
this.__retryAttempts = this.#isPeriodic ? 1 : this.#eventConfig.retryAttempts ?? DEFAULT_RETRY_ATTEMPTS;
|
|
72
71
|
this.__selectMaxChunkSize = this.#eventConfig.selectMaxChunkSize ?? SELECT_LIMIT_EVENTS_PER_TICK;
|
|
@@ -345,17 +344,21 @@ class EventQueueProcessorBase {
|
|
|
345
344
|
}
|
|
346
345
|
}
|
|
347
346
|
|
|
348
|
-
#determineAndAddEventStatusToMap(id,
|
|
347
|
+
#determineAndAddEventStatusToMap(id, statusOrUpdateData, statusMap = this.__statusMap) {
|
|
348
|
+
if (typeof statusOrUpdateData === "number") {
|
|
349
|
+
statusOrUpdateData = { status: statusOrUpdateData };
|
|
350
|
+
}
|
|
351
|
+
|
|
349
352
|
if (!statusMap[id]) {
|
|
350
|
-
statusMap[id] =
|
|
353
|
+
statusMap[id] = statusOrUpdateData;
|
|
351
354
|
return;
|
|
352
355
|
}
|
|
353
|
-
if ([EventProcessingStatus.Error, EventProcessingStatus.Exceeded].includes(statusMap[id])) {
|
|
356
|
+
if ([EventProcessingStatus.Error, EventProcessingStatus.Exceeded].includes(statusMap[id].status)) {
|
|
354
357
|
// NOTE: worst aggregation --> if already error|exceeded keep this state
|
|
355
358
|
return;
|
|
356
359
|
}
|
|
357
|
-
if (statusMap[id] >= 0) {
|
|
358
|
-
statusMap[id] =
|
|
360
|
+
if (statusMap[id].status >= 0) {
|
|
361
|
+
statusMap[id] = { status: statusOrUpdateData };
|
|
359
362
|
}
|
|
360
363
|
}
|
|
361
364
|
|
|
@@ -396,6 +399,25 @@ class EventQueueProcessorBase {
|
|
|
396
399
|
);
|
|
397
400
|
}
|
|
398
401
|
|
|
402
|
+
#normalizeStatusMap(statusMap) {
|
|
403
|
+
const originalMap = {};
|
|
404
|
+
for (const [id, entry] of Object.entries(statusMap)) {
|
|
405
|
+
originalMap[id] = entry;
|
|
406
|
+
const result = {};
|
|
407
|
+
if (typeof entry === "number") {
|
|
408
|
+
result.status = entry;
|
|
409
|
+
} else if (typeof entry === "object") {
|
|
410
|
+
for (const fieldName of ALLOWED_FIELDS_FOR_UPDATE) {
|
|
411
|
+
if (fieldName in entry) {
|
|
412
|
+
result[fieldName] = entry[fieldName];
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
statusMap[id] = result;
|
|
417
|
+
}
|
|
418
|
+
return originalMap;
|
|
419
|
+
}
|
|
420
|
+
|
|
399
421
|
/**
|
|
400
422
|
* This function validates for all selected events one status has been submitted. It's also validated that only for
|
|
401
423
|
* selected events a status has been submitted. Persisting the status of events is done in a dedicated database tx.
|
|
@@ -406,98 +428,123 @@ class EventQueueProcessorBase {
|
|
|
406
428
|
eventType: this.#eventType,
|
|
407
429
|
eventSubType: this.#eventSubType,
|
|
408
430
|
});
|
|
431
|
+
const originalStatusMap = this.#normalizeStatusMap(statusMap);
|
|
409
432
|
this.#ensureOnlySelectedQueueEntries(statusMap);
|
|
410
433
|
if (!skipChecks) {
|
|
411
434
|
this.#ensureEveryQueueEntryHasStatus();
|
|
412
435
|
}
|
|
413
436
|
this.#ensureEveryStatusIsAllowed(statusMap);
|
|
414
|
-
|
|
415
|
-
const { success, failed, exceeded, invalidAttempts } = Object.entries(statusMap).reduce(
|
|
416
|
-
(result, [queueEntryId, processingStatus]) => {
|
|
417
|
-
this.__commitedStatusMap[queueEntryId] = processingStatus;
|
|
418
|
-
delete this.__notCommitedStatusMap[queueEntryId];
|
|
419
|
-
if (processingStatus === EventProcessingStatus.Open) {
|
|
420
|
-
result.invalidAttempts.push(queueEntryId);
|
|
421
|
-
} else if (processingStatus === EventProcessingStatus.Done) {
|
|
422
|
-
result.success.push(queueEntryId);
|
|
423
|
-
} else if (processingStatus === EventProcessingStatus.Error) {
|
|
424
|
-
result.failed.push(queueEntryId);
|
|
425
|
-
} else if (processingStatus === EventProcessingStatus.Exceeded) {
|
|
426
|
-
result.exceeded.push(queueEntryId);
|
|
427
|
-
}
|
|
428
|
-
return result;
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
success: [],
|
|
432
|
-
failed: [],
|
|
433
|
-
exceeded: [],
|
|
434
|
-
invalidAttempts: [],
|
|
435
|
-
}
|
|
436
|
-
);
|
|
437
|
-
if (![success, failed, exceeded, invalidAttempts].some((statusArray) => statusArray.length)) {
|
|
438
|
-
this.logger.debug("exiting persistEventStatus", {
|
|
439
|
-
eventType: this.#eventType,
|
|
440
|
-
eventSubType: this.#eventSubType,
|
|
441
|
-
});
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
|
|
445
437
|
return await trace(this.baseContext, "persist-event-status", async () => {
|
|
446
438
|
this.logger.debug("persistEventStatus for entries", {
|
|
447
439
|
eventType: this.#eventType,
|
|
448
440
|
eventSubType: this.#eventSubType,
|
|
449
|
-
|
|
450
|
-
failed,
|
|
451
|
-
exceeded,
|
|
452
|
-
success,
|
|
441
|
+
statusMap,
|
|
453
442
|
});
|
|
454
|
-
if (invalidAttempts.length) {
|
|
455
|
-
await tx.run(
|
|
456
|
-
UPDATE.entity(this.#config.tableNameEventQueue)
|
|
457
|
-
.set({
|
|
458
|
-
status: EventProcessingStatus.Open,
|
|
459
|
-
lastAttemptTimestamp: new Date().toISOString(),
|
|
460
|
-
attempts: { "-=": 1 },
|
|
461
|
-
})
|
|
462
|
-
.where("ID IN", invalidAttempts)
|
|
463
|
-
);
|
|
464
|
-
}
|
|
465
443
|
const ts = new Date().toISOString();
|
|
466
|
-
const
|
|
467
|
-
[
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
444
|
+
const updateData = Object.entries(statusMap).reduce((result, [id, data]) => {
|
|
445
|
+
const key = ALLOWED_FIELDS_FOR_UPDATE.map((name) => [name, data[name]])
|
|
446
|
+
.flat()
|
|
447
|
+
.join("##");
|
|
448
|
+
|
|
449
|
+
result[key] ??= { data, ids: [] };
|
|
450
|
+
result[key].ids.push(id);
|
|
451
|
+
return result;
|
|
452
|
+
}, {});
|
|
453
|
+
|
|
454
|
+
for (const { ids, data } of Object.values(updateData)) {
|
|
455
|
+
if (!("status" in data)) {
|
|
456
|
+
// TODO: Can this still happen?
|
|
457
|
+
this.logger.error("can't find status value in return value of event-processing. Setting event to done", {
|
|
458
|
+
ids,
|
|
459
|
+
// NOTE: use first id as same return values are clustered
|
|
460
|
+
eventReturnValue: originalStatusMap[ids[0]],
|
|
461
|
+
});
|
|
474
462
|
continue;
|
|
475
463
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
464
|
+
|
|
465
|
+
for (const id of ids) {
|
|
466
|
+
this.__commitedStatusMap[id] = data.status;
|
|
467
|
+
delete this.__notCommitedStatusMap[id];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (![EventProcessingStatus.Open, EventProcessingStatus.Error].includes(data.status)) {
|
|
471
|
+
delete data.startAfter;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (data.startAfter) {
|
|
475
|
+
data.startAfter = this.#normalizeDate(data.startAfter);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (data.error) {
|
|
479
|
+
data.error = this.#error2String(data.error);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (!data.startAfter && [EventProcessingStatus.Error, EventProcessingStatus.Open].includes(data.status)) {
|
|
483
|
+
data.startAfter = new Date(
|
|
484
|
+
Date.now() +
|
|
485
|
+
(EventProcessingStatus.Error ? this.#eventConfig.retryFailedAfter : this.#eventConfig.retryOpenAfter)
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (data.startAfter) {
|
|
479
490
|
this.#eventSchedulerInstance.scheduleEvent(
|
|
480
491
|
this.__context.tenant,
|
|
481
492
|
this.#eventType,
|
|
482
493
|
this.#eventSubType,
|
|
483
494
|
this.#namespace,
|
|
484
|
-
startAfter
|
|
495
|
+
data.startAfter
|
|
485
496
|
);
|
|
486
497
|
}
|
|
487
498
|
|
|
488
499
|
await tx.run(
|
|
489
500
|
UPDATE.entity(this.#config.tableNameEventQueue)
|
|
490
501
|
.set({
|
|
491
|
-
|
|
502
|
+
...data,
|
|
492
503
|
lastAttemptTimestamp: ts,
|
|
493
|
-
...(status === EventProcessingStatus.Error ? { startAfter: startAfter.toISOString() } : {}),
|
|
494
504
|
})
|
|
495
|
-
.where("ID IN",
|
|
505
|
+
.where("ID IN", ids)
|
|
496
506
|
);
|
|
497
507
|
}
|
|
498
508
|
});
|
|
499
509
|
}
|
|
500
510
|
|
|
511
|
+
#error2String(error) {
|
|
512
|
+
return JSON.stringify(error, (_, value) => this.#errorReplacer(value));
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
#errorReplacer(value) {
|
|
516
|
+
if (!(value instanceof Error)) {
|
|
517
|
+
return value;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const plain = {
|
|
521
|
+
name: value.name,
|
|
522
|
+
message: value.message,
|
|
523
|
+
stack: value.stack,
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
for (const key in value) {
|
|
527
|
+
plain[key] = value[key];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return plain;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
#normalizeDate(value) {
|
|
534
|
+
if (value instanceof Date) {
|
|
535
|
+
return value;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
539
|
+
const date = new Date(value);
|
|
540
|
+
if (!isNaN(date)) {
|
|
541
|
+
return date;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return null;
|
|
546
|
+
}
|
|
547
|
+
|
|
501
548
|
#ensureEveryQueueEntryHasStatus() {
|
|
502
549
|
this.__queueEntries.forEach((queueEntry) => {
|
|
503
550
|
if (
|
|
@@ -507,17 +554,17 @@ class EventQueueProcessorBase {
|
|
|
507
554
|
) {
|
|
508
555
|
return;
|
|
509
556
|
}
|
|
510
|
-
this.logger.error("Missing status for selected event entry. Setting status to
|
|
557
|
+
this.logger.error("Missing status for selected event entry. Setting status to done", {
|
|
511
558
|
eventType: this.#eventType,
|
|
512
559
|
eventSubType: this.#eventSubType,
|
|
513
560
|
queueEntry,
|
|
514
561
|
});
|
|
515
|
-
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.
|
|
562
|
+
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Done);
|
|
516
563
|
});
|
|
517
564
|
}
|
|
518
565
|
|
|
519
566
|
#ensureEveryStatusIsAllowed(statusMap) {
|
|
520
|
-
Object.entries(statusMap).forEach(([queueEntryId, status]) => {
|
|
567
|
+
Object.entries(statusMap).forEach(([queueEntryId, { status }]) => {
|
|
521
568
|
if (
|
|
522
569
|
[
|
|
523
570
|
EventProcessingStatus.Open,
|
|
@@ -533,7 +580,7 @@ class EventQueueProcessorBase {
|
|
|
533
580
|
eventType: this.#eventType,
|
|
534
581
|
eventSubType: this.#eventSubType,
|
|
535
582
|
queueEntryId,
|
|
536
|
-
status: statusMap[queueEntryId],
|
|
583
|
+
status: statusMap[queueEntryId].status,
|
|
537
584
|
});
|
|
538
585
|
delete statusMap[queueEntryId];
|
|
539
586
|
});
|
package/src/config.js
CHANGED
|
@@ -24,6 +24,7 @@ const CAP_EVENT_TYPE = "CAP_OUTBOX";
|
|
|
24
24
|
const CAP_PARALLEL_DEFAULT = 5;
|
|
25
25
|
const CAP_MAX_ATTEMPTS_DEFAULT = 5;
|
|
26
26
|
const DELETE_TENANT_BLOCK_AFTER_MS = 5 * 60 * 1000;
|
|
27
|
+
const DEFAULT_RETRY_AFTER = 5 * 60 * 1000;
|
|
27
28
|
const PRIORITIES = Object.values(Priorities);
|
|
28
29
|
const UTC_DEFAULT = false;
|
|
29
30
|
const USE_CRON_TZ_DEFAULT = true;
|
|
@@ -60,6 +61,8 @@ const ALLOWED_EVENT_OPTIONS_AD_HOC = [
|
|
|
60
61
|
"processAfterCommit",
|
|
61
62
|
"checkForNextChunk",
|
|
62
63
|
"retryFailedAfter",
|
|
64
|
+
"propagateHeaders",
|
|
65
|
+
"retryOpenAfter",
|
|
63
66
|
"multiInstanceProcessing",
|
|
64
67
|
"kind",
|
|
65
68
|
"timeBucket",
|
|
@@ -435,6 +438,9 @@ class Config {
|
|
|
435
438
|
event._appInstancesMap = event.appInstances
|
|
436
439
|
? Object.fromEntries(new Map(event.appInstances.map((a) => [a, true])))
|
|
437
440
|
: null;
|
|
441
|
+
event.propagateHeaders = event.propagateHeaders ?? [];
|
|
442
|
+
event.retryFailedAfter = event.retryFailedAfter ?? DEFAULT_RETRY_AFTER;
|
|
443
|
+
event.retryOpenAfter = event.retryOpenAfter ?? DEFAULT_RETRY_AFTER;
|
|
438
444
|
}
|
|
439
445
|
|
|
440
446
|
#basicEventValidation(event) {
|
|
@@ -751,6 +757,9 @@ class Config {
|
|
|
751
757
|
}
|
|
752
758
|
|
|
753
759
|
set useAsCAPOutbox(value) {
|
|
760
|
+
if (this.#useAsCAPOutbox) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
754
763
|
this.#useAsCAPOutbox = value;
|
|
755
764
|
}
|
|
756
765
|
|
|
@@ -758,6 +767,14 @@ class Config {
|
|
|
758
767
|
return this.#useAsCAPOutbox;
|
|
759
768
|
}
|
|
760
769
|
|
|
770
|
+
set useAsCAPQueue(value) {
|
|
771
|
+
this.useAsCAPOutbox = value;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
get useAsCAPQueue() {
|
|
775
|
+
return this.useAsCAPOutbox;
|
|
776
|
+
}
|
|
777
|
+
|
|
761
778
|
set userId(value) {
|
|
762
779
|
this.#userId = value;
|
|
763
780
|
}
|
package/src/initialize.js
CHANGED
|
@@ -35,6 +35,7 @@ const CONFIG_VARS = [
|
|
|
35
35
|
["updatePeriodicEvents", true],
|
|
36
36
|
["thresholdLoggingEventProcessing", 50],
|
|
37
37
|
["useAsCAPOutbox", false],
|
|
38
|
+
["useAsCAPQueue", false],
|
|
38
39
|
["userId", null],
|
|
39
40
|
["cleanupLocksAndEventsForDev", false],
|
|
40
41
|
["redisOptions", {}],
|
|
@@ -65,6 +66,7 @@ const CONFIG_VARS = [
|
|
|
65
66
|
* @param {boolean} [options.updatePeriodicEvents=true] - Automatically update periodic events.
|
|
66
67
|
* @param {number} [options.thresholdLoggingEventProcessing=50] - Threshold for logging event processing time (in milliseconds).
|
|
67
68
|
* @param {boolean} [options.useAsCAPOutbox=false] - Use the event queue as a CAP Outbox.
|
|
69
|
+
* @param {boolean} [options.useAsCAPQueue=false] - Use the event queue as a CAP Outbox.
|
|
68
70
|
* @param {string} [options.userId=null] - ID of the user initiating the process.
|
|
69
71
|
* @param {boolean} [options.cleanupLocksAndEventsForDev=false] - Cleanup locks and events for development environments.
|
|
70
72
|
* @param {Object} [options.redisOptions={}] - Configuration options for Redis.
|
|
@@ -348,7 +348,13 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
348
348
|
this.logger.error("error processing outboxed service call", err, {
|
|
349
349
|
serviceName: this.eventSubType,
|
|
350
350
|
});
|
|
351
|
-
return queueEntries.map((queueEntry) => [
|
|
351
|
+
return queueEntries.map((queueEntry) => [
|
|
352
|
+
queueEntry.ID,
|
|
353
|
+
{
|
|
354
|
+
status: EventProcessingStatus.Error,
|
|
355
|
+
error: err,
|
|
356
|
+
},
|
|
357
|
+
]);
|
|
352
358
|
}
|
|
353
359
|
}
|
|
354
360
|
|
|
@@ -359,10 +365,48 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
359
365
|
return queueEntries.map((queueEntry) => [queueEntry.ID, result]);
|
|
360
366
|
}
|
|
361
367
|
|
|
368
|
+
if (result instanceof Object && !Array.isArray(result)) {
|
|
369
|
+
return queueEntries.map((queueEntry) => [queueEntry.ID, result]);
|
|
370
|
+
}
|
|
371
|
+
|
|
362
372
|
if (!Array.isArray(result)) {
|
|
363
373
|
return queueEntries.map((queueEntry) => [queueEntry.ID, EventProcessingStatus.Done]);
|
|
364
374
|
}
|
|
365
375
|
|
|
376
|
+
const [firstEntry] = result;
|
|
377
|
+
if (Array.isArray(firstEntry)) {
|
|
378
|
+
const [, innerResult] = firstEntry;
|
|
379
|
+
if (innerResult instanceof Object) {
|
|
380
|
+
return result;
|
|
381
|
+
} else {
|
|
382
|
+
return result.map(([id, status]) => {
|
|
383
|
+
return [id, { status }];
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
} else if (firstEntry instanceof Object) {
|
|
387
|
+
return result.reduce((result, entry) => {
|
|
388
|
+
let { ID } = entry;
|
|
389
|
+
|
|
390
|
+
if (!ID) {
|
|
391
|
+
if (queueEntries.length > 1) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
"The CAP handler return value does not match the event-queue specification. Please check the documentation"
|
|
394
|
+
);
|
|
395
|
+
} else {
|
|
396
|
+
ID = queueEntries[0].ID;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
delete entry.ID;
|
|
401
|
+
if (!("status" in entry)) {
|
|
402
|
+
entry.status = EventProcessingStatus.Done;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
result.push([ID, entry]);
|
|
406
|
+
return result;
|
|
407
|
+
}, []);
|
|
408
|
+
}
|
|
409
|
+
|
|
366
410
|
const valid = !result.some((entry) => {
|
|
367
411
|
const [, status] = entry;
|
|
368
412
|
return !validStatusValues.includes(status);
|
|
@@ -51,14 +51,16 @@ function outboxed(srv, customOpts) {
|
|
|
51
51
|
customOpts || {}
|
|
52
52
|
);
|
|
53
53
|
config.addCAPOutboxEventBase(srv.name, outboxOpts);
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
54
|
+
const hasSpecificSettings = !!config.getCdsOutboxEventSpecificConfig(srv.name, req.event);
|
|
55
|
+
if (hasSpecificSettings) {
|
|
56
56
|
outboxOpts = config.addCAPOutboxEventSpecificAction(srv.name, req.event);
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
const namespace =
|
|
58
|
+
const subType = hasSpecificSettings ? [srv.name, req.event].join(".") : srv.name;
|
|
59
|
+
const namespace = outboxOpts.namespace ?? config.namespace;
|
|
60
|
+
outboxOpts = config.getEventConfig(CDS_EVENT_TYPE, subType, namespace);
|
|
61
|
+
const eventHeaders = getPropagatedHeaders(outboxOpts, req);
|
|
60
62
|
if (["persistent-outbox", "persistent-queue"].includes(outboxOpts.kind)) {
|
|
61
|
-
await _mapToEventAndPublish(context,
|
|
63
|
+
await _mapToEventAndPublish(context, subType, req, eventHeaders, namespace);
|
|
62
64
|
return;
|
|
63
65
|
}
|
|
64
66
|
context.on("succeeded", async () => {
|
|
@@ -84,24 +86,35 @@ function unboxed(srv) {
|
|
|
84
86
|
return srv[UNBOXED] || srv;
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
const
|
|
89
|
+
const getPropagatedHeaders = (config, req) => {
|
|
90
|
+
const propagateHeaders = config.propagateHeaders.reduce((headers, headerName) => {
|
|
91
|
+
if (headerName in req.tx.context.headers) {
|
|
92
|
+
headers[headerName] = req.tx.context.headers[headerName];
|
|
93
|
+
}
|
|
94
|
+
return headers;
|
|
95
|
+
}, {});
|
|
96
|
+
return Object.assign(propagateHeaders, req.headers);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const _mapToEventAndPublish = async (context, subType, req, eventHeaders, namespace) => {
|
|
88
100
|
const eventQueueSpecificValues = {};
|
|
89
101
|
for (const header in req.headers ?? {}) {
|
|
90
102
|
for (const field of EVENT_QUEUE_SPECIFIC_FIELDS) {
|
|
91
103
|
if (header.toLocaleLowerCase() === `x-eventqueue-${field.toLocaleLowerCase()}`) {
|
|
92
104
|
eventQueueSpecificValues[field] = req.headers[header];
|
|
93
|
-
delete
|
|
105
|
+
delete eventHeaders[header];
|
|
94
106
|
break;
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
109
|
}
|
|
110
|
+
|
|
98
111
|
const event = {
|
|
99
112
|
contextUser: context.user.id,
|
|
100
113
|
...(req._fromSend || (req.reply && { _fromSend: true })), // send or emit
|
|
101
114
|
...(req.inbound && { inbound: req.inbound }),
|
|
102
115
|
...(req.event && { event: req.event }),
|
|
103
116
|
...(req.data && { data: req.data }),
|
|
104
|
-
...(
|
|
117
|
+
...(eventHeaders && { headers: eventHeaders }),
|
|
105
118
|
...(req.query && { query: req.query }),
|
|
106
119
|
};
|
|
107
120
|
|
|
@@ -109,7 +122,7 @@ const _mapToEventAndPublish = async (context, name, req, actionSpecific, namespa
|
|
|
109
122
|
cds.tx(context),
|
|
110
123
|
{
|
|
111
124
|
type: CDS_EVENT_TYPE,
|
|
112
|
-
subType
|
|
125
|
+
subType,
|
|
113
126
|
payload: JSON.stringify(event),
|
|
114
127
|
namespace: eventQueueSpecificValues.namespace ?? namespace,
|
|
115
128
|
...eventQueueSpecificValues,
|