@cap-js-community/event-queue 2.0.0-beta.7 → 2.0.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/package.json +1 -1
- package/src/EventQueueProcessorBase.js +26 -8
- package/src/config.js +19 -7
- package/src/outbox/EventQueueGenericOutboxHandler.js +20 -6
- package/src/processEventQueue.js +3 -1
- package/src/redis/redisSub.js +4 -4
- package/src/runner/openEvents.js +1 -1
- package/src/shared/common.js +9 -0
- package/srv/service/admin-service.cds +13 -0
- package/srv/service/admin-service.js +42 -7
- package/srv/service/publishEventHelper.js +52 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.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",
|
|
@@ -296,7 +296,7 @@ class EventQueueProcessorBase {
|
|
|
296
296
|
const statusMap = this.commitOnEventLevel || returnMap ? {} : this.__statusMap;
|
|
297
297
|
const errorHandler = (error) => {
|
|
298
298
|
queueEntries.forEach((queueEntry) =>
|
|
299
|
-
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Error, statusMap)
|
|
299
|
+
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Error, { statusMap })
|
|
300
300
|
);
|
|
301
301
|
this.logger.error(
|
|
302
302
|
"The supplied status tuple doesn't have the required structure. Setting all entries to error.",
|
|
@@ -317,7 +317,7 @@ class EventQueueProcessorBase {
|
|
|
317
317
|
|
|
318
318
|
try {
|
|
319
319
|
queueEntryProcessingStatusTuple.forEach(([id, processingStatus]) =>
|
|
320
|
-
this.#determineAndAddEventStatusToMap(id, processingStatus, statusMap)
|
|
320
|
+
this.#determineAndAddEventStatusToMap(id, processingStatus, { statusMap })
|
|
321
321
|
);
|
|
322
322
|
} catch (error) {
|
|
323
323
|
errorHandler(error);
|
|
@@ -344,11 +344,15 @@ class EventQueueProcessorBase {
|
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
346
|
|
|
347
|
-
#determineAndAddEventStatusToMap(id, statusOrUpdateData, statusMap = this.__statusMap) {
|
|
347
|
+
#determineAndAddEventStatusToMap(id, statusOrUpdateData, { statusMap = this.__statusMap, error } = {}) {
|
|
348
348
|
if (typeof statusOrUpdateData === "number") {
|
|
349
349
|
statusOrUpdateData = { status: statusOrUpdateData };
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
+
if (error) {
|
|
353
|
+
statusOrUpdateData.error = error;
|
|
354
|
+
}
|
|
355
|
+
|
|
352
356
|
if (!statusMap[id]) {
|
|
353
357
|
statusMap[id] = statusOrUpdateData;
|
|
354
358
|
return;
|
|
@@ -376,9 +380,17 @@ class EventQueueProcessorBase {
|
|
|
376
380
|
}
|
|
377
381
|
);
|
|
378
382
|
queueEntries.forEach((queueEntry) =>
|
|
379
|
-
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Error)
|
|
383
|
+
this.#determineAndAddEventStatusToMap(queueEntry.ID, EventProcessingStatus.Error, { error })
|
|
384
|
+
);
|
|
385
|
+
return Object.fromEntries(
|
|
386
|
+
queueEntries.map((queueEntry) => [
|
|
387
|
+
queueEntry.ID,
|
|
388
|
+
{
|
|
389
|
+
status: EventProcessingStatus.Error,
|
|
390
|
+
error,
|
|
391
|
+
},
|
|
392
|
+
])
|
|
380
393
|
);
|
|
381
|
-
return Object.fromEntries(queueEntries.map((queueEntry) => [queueEntry.ID, EventProcessingStatus.Error]));
|
|
382
394
|
}
|
|
383
395
|
|
|
384
396
|
handleErrorDuringPeriodicEventProcessing(error, queueEntry) {
|
|
@@ -389,11 +401,12 @@ class EventQueueProcessorBase {
|
|
|
389
401
|
});
|
|
390
402
|
}
|
|
391
403
|
|
|
392
|
-
async setPeriodicEventStatus(queueEntryIds, status) {
|
|
404
|
+
async setPeriodicEventStatus(queueEntryIds, status, error) {
|
|
393
405
|
await this.tx.run(
|
|
394
406
|
UPDATE.entity(this.#config.tableNameEventQueue)
|
|
395
407
|
.set({
|
|
396
408
|
status: status,
|
|
409
|
+
...(error && { error: this.#error2String(error) }),
|
|
397
410
|
})
|
|
398
411
|
.where({
|
|
399
412
|
ID: queueEntryIds,
|
|
@@ -409,7 +422,7 @@ class EventQueueProcessorBase {
|
|
|
409
422
|
if (typeof entry === "number") {
|
|
410
423
|
result.status = entry;
|
|
411
424
|
} else if (typeof entry === "object") {
|
|
412
|
-
for (const fieldName of
|
|
425
|
+
for (const fieldName of this.allowedFieldsEventHandler) {
|
|
413
426
|
if (fieldName in entry) {
|
|
414
427
|
result[fieldName] = entry[fieldName];
|
|
415
428
|
}
|
|
@@ -444,7 +457,8 @@ class EventQueueProcessorBase {
|
|
|
444
457
|
});
|
|
445
458
|
const ts = new Date().toISOString();
|
|
446
459
|
const updateData = Object.entries(statusMap).reduce((result, [id, data]) => {
|
|
447
|
-
const key =
|
|
460
|
+
const key = this.allowedFieldsEventHandler
|
|
461
|
+
.map((name) => [name, data[name]])
|
|
448
462
|
.flat()
|
|
449
463
|
.join("##");
|
|
450
464
|
|
|
@@ -1360,6 +1374,10 @@ class EventQueueProcessorBase {
|
|
|
1360
1374
|
get namespace() {
|
|
1361
1375
|
return this.#namespace;
|
|
1362
1376
|
}
|
|
1377
|
+
|
|
1378
|
+
get allowedFieldsEventHandler() {
|
|
1379
|
+
return ALLOWED_FIELDS_FOR_UPDATE;
|
|
1380
|
+
}
|
|
1363
1381
|
}
|
|
1364
1382
|
|
|
1365
1383
|
module.exports = EventQueueProcessorBase;
|
package/src/config.js
CHANGED
|
@@ -143,7 +143,7 @@ class Config {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
isCapOutboxEvent(type) {
|
|
146
|
-
return
|
|
146
|
+
return [CAP_EVENT_TYPE, [CAP_EVENT_TYPE, SUFFIX_PERIODIC].join("")].includes(type);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
hasEventAfterCommitFlag(type, subType, namespace = this.namespace) {
|
|
@@ -168,15 +168,27 @@ class Config {
|
|
|
168
168
|
return { type: "string", value: str };
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
171
|
+
normalizeSubType(type, rawSubType) {
|
|
172
|
+
if (![CAP_EVENT_TYPE, [CAP_EVENT_TYPE, SUFFIX_PERIODIC].join("")].includes(type)) {
|
|
173
|
+
return { subType: rawSubType };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const serviceParts = rawSubType.split(".");
|
|
177
|
+
let srvName = serviceParts.shift();
|
|
178
|
+
while (!cds.env.requires[srvName] && serviceParts.length) {
|
|
179
|
+
srvName = [srvName, serviceParts.shift()].join(".");
|
|
180
|
+
}
|
|
181
|
+
const actionName = serviceParts.shift();
|
|
182
|
+
const actionSpecificCall = this.getCdsOutboxEventSpecificConfig(srvName, actionName);
|
|
183
|
+
return {
|
|
184
|
+
subType: actionSpecificCall ? rawSubType : srvName,
|
|
185
|
+
actionName,
|
|
186
|
+
srvName,
|
|
187
|
+
};
|
|
175
188
|
}
|
|
176
189
|
|
|
177
190
|
shouldBeProcessedInThisApplication(type, rawSubType, namespace = this.namespace) {
|
|
178
|
-
const subType = this
|
|
179
|
-
|
|
191
|
+
const { subType } = this.normalizeSubType(type, rawSubType);
|
|
180
192
|
const config = this.#eventMap[this.generateKey(namespace, type, subType)];
|
|
181
193
|
const appNameConfig = config._appNameMap;
|
|
182
194
|
const appInstanceConfig = config._appInstancesMap;
|
|
@@ -22,8 +22,8 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
async getQueueEntriesAndSetToInProgress() {
|
|
25
|
-
const
|
|
26
|
-
this.__srv = await cds.connect.to(
|
|
25
|
+
const { srvName } = config.normalizeSubType(this.eventType, this.eventSubType);
|
|
26
|
+
this.__srv = await cds.connect.to(srvName);
|
|
27
27
|
this.__srvUnboxed = cds.unboxed(this.__srv);
|
|
28
28
|
const { handlers, clusterRelevant, specificClusterRelevant } = this.__srvUnboxed.handlers.on.reduce(
|
|
29
29
|
(result, handler) => {
|
|
@@ -309,8 +309,8 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
async processPeriodicEvent(processContext, key, queueEntry) {
|
|
312
|
-
const
|
|
313
|
-
const reg = new cds.Event({ event:
|
|
312
|
+
const { actionName } = config.normalizeSubType(this.eventType, this.eventSubType);
|
|
313
|
+
const reg = new cds.Event({ event: actionName, eventQueue: { processor: this, key, queueEntries: [queueEntry] } });
|
|
314
314
|
await this.#setContextUser(processContext, config.userId, reg);
|
|
315
315
|
await this.__srvUnboxed.tx(processContext).emit(reg);
|
|
316
316
|
}
|
|
@@ -366,7 +366,11 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
if (result instanceof Object && !Array.isArray(result)) {
|
|
369
|
-
|
|
369
|
+
const allAllowed = !Object.keys(result).some((name) => !this.allowedFieldsEventHandler.includes(name));
|
|
370
|
+
return queueEntries.map((queueEntry) => [
|
|
371
|
+
queueEntry.ID,
|
|
372
|
+
allAllowed ? result : { status: EventProcessingStatus.Done },
|
|
373
|
+
]);
|
|
370
374
|
}
|
|
371
375
|
|
|
372
376
|
if (!Array.isArray(result)) {
|
|
@@ -377,7 +381,11 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
377
381
|
if (Array.isArray(firstEntry)) {
|
|
378
382
|
const [, innerResult] = firstEntry;
|
|
379
383
|
if (innerResult instanceof Object) {
|
|
380
|
-
|
|
384
|
+
const allAllowed = !Object.keys(innerResult).some((name) => !this.allowedFieldsEventHandler.includes(name));
|
|
385
|
+
if (allAllowed) {
|
|
386
|
+
return result;
|
|
387
|
+
}
|
|
388
|
+
return queueEntries.map((queueEntry) => [queueEntry.ID, { status: EventProcessingStatus.Done }]);
|
|
381
389
|
} else {
|
|
382
390
|
return result.map(([id, status]) => {
|
|
383
391
|
return [id, { status }];
|
|
@@ -398,6 +406,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
398
406
|
}
|
|
399
407
|
|
|
400
408
|
delete entry.ID;
|
|
409
|
+
const allAllowed = !Object.keys(entry).some((name) => !this.allowedFieldsEventHandler.includes(name));
|
|
410
|
+
|
|
411
|
+
if (!allAllowed) {
|
|
412
|
+
result.push([ID, { status: EventProcessingStatus.Done }]);
|
|
413
|
+
}
|
|
414
|
+
|
|
401
415
|
if (!("status" in entry)) {
|
|
402
416
|
entry.status = EventProcessingStatus.Done;
|
|
403
417
|
}
|
package/src/processEventQueue.js
CHANGED
|
@@ -154,6 +154,7 @@ const processPeriodicEvent = async (context, eventTypeInstance) => {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
let status = EventProcessingStatus.Done;
|
|
157
|
+
let error;
|
|
157
158
|
await executeInNewTransaction(
|
|
158
159
|
eventTypeInstance.context,
|
|
159
160
|
`eventQueue-periodic-process-${eventTypeInstance.eventType}##${eventTypeInstance.eventSubType}`,
|
|
@@ -166,6 +167,7 @@ const processPeriodicEvent = async (context, eventTypeInstance) => {
|
|
|
166
167
|
eventTypeInstance.startPerformanceTracerPeriodicEvents();
|
|
167
168
|
await eventTypeInstance.processPeriodicEvent(tx.context, queueEntry.ID, queueEntry);
|
|
168
169
|
} catch (err) {
|
|
170
|
+
error = err;
|
|
169
171
|
status = EventProcessingStatus.Error;
|
|
170
172
|
eventTypeInstance.handleErrorDuringPeriodicEventProcessing(err, queueEntry);
|
|
171
173
|
await tx.rollback();
|
|
@@ -190,7 +192,7 @@ const processPeriodicEvent = async (context, eventTypeInstance) => {
|
|
|
190
192
|
async (tx) => {
|
|
191
193
|
await trace(eventTypeInstance.context, "periodic-event-set-status", async () => {
|
|
192
194
|
eventTypeInstance.processEventContext = tx.context;
|
|
193
|
-
await eventTypeInstance.setPeriodicEventStatus(queueEntry.ID, status);
|
|
195
|
+
await eventTypeInstance.setPeriodicEventStatus(queueEntry.ID, status, error);
|
|
194
196
|
});
|
|
195
197
|
}
|
|
196
198
|
);
|
package/src/redis/redisSub.js
CHANGED
|
@@ -43,16 +43,16 @@ const _messageHandlerProcessEvents = async (messageData) => {
|
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const
|
|
46
|
+
const { srvName, actionName } = config.normalizeSubType(type, subType);
|
|
47
47
|
if (!config.getEventConfig(type, subType, namespace)) {
|
|
48
48
|
if (config.isCapOutboxEvent(type)) {
|
|
49
49
|
try {
|
|
50
|
-
const service = await cds.connect.to(
|
|
50
|
+
const service = await cds.connect.to(srvName);
|
|
51
51
|
cds.outboxed(service);
|
|
52
52
|
if (actionName) {
|
|
53
|
-
const specificSettings = config.getCdsOutboxEventSpecificConfig(
|
|
53
|
+
const specificSettings = config.getCdsOutboxEventSpecificConfig(srvName, actionName);
|
|
54
54
|
if (specificSettings) {
|
|
55
|
-
config.addCAPOutboxEventSpecificAction(
|
|
55
|
+
config.addCAPOutboxEventSpecificAction(srvName, actionName);
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
} catch (err) {
|
package/src/runner/openEvents.js
CHANGED
|
@@ -39,7 +39,7 @@ const getOpenQueueEntries = async (tx, filterAppSpecificEvents = true) => {
|
|
|
39
39
|
const result = [];
|
|
40
40
|
for (const { type, subType, namespace } of entries) {
|
|
41
41
|
if (eventConfig.isCapOutboxEvent(type)) {
|
|
42
|
-
const
|
|
42
|
+
const { srvName, actionName } = config.normalizeSubType(type, subType);
|
|
43
43
|
try {
|
|
44
44
|
const service = await cds.connect.to(srvName);
|
|
45
45
|
if (filterAppSpecificEvents) {
|
package/src/shared/common.js
CHANGED
|
@@ -165,6 +165,14 @@ const isTenantIdValidCb = async (checkType, tenantId) => {
|
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
const cleanUndefined = (input) =>
|
|
169
|
+
Object.entries(input).reduce((acc, [key, value]) => {
|
|
170
|
+
if (value !== undefined) {
|
|
171
|
+
acc[key] = value;
|
|
172
|
+
}
|
|
173
|
+
return acc;
|
|
174
|
+
}, {});
|
|
175
|
+
|
|
168
176
|
module.exports = {
|
|
169
177
|
arrayToFlatMap,
|
|
170
178
|
limiter,
|
|
@@ -174,6 +182,7 @@ module.exports = {
|
|
|
174
182
|
getAuthContext,
|
|
175
183
|
isTenantIdValidCb,
|
|
176
184
|
promiseAllDone,
|
|
185
|
+
cleanUndefined,
|
|
177
186
|
__: {
|
|
178
187
|
clearAuthContextCache: () => getAuthContext._cache?.clear(),
|
|
179
188
|
},
|
|
@@ -42,4 +42,17 @@ service EventQueueAdminService {
|
|
|
42
42
|
@mandatory
|
|
43
43
|
subType: String) returns Boolean;
|
|
44
44
|
}
|
|
45
|
+
|
|
46
|
+
action publishEvent(
|
|
47
|
+
@mandatory
|
|
48
|
+
tenants: array of String,
|
|
49
|
+
@mandatory
|
|
50
|
+
type: String,
|
|
51
|
+
@mandatory
|
|
52
|
+
subType: String,
|
|
53
|
+
referenceEntity: String,
|
|
54
|
+
referenceEntityKey: String,
|
|
55
|
+
@open
|
|
56
|
+
payload: {},
|
|
57
|
+
startAfter: String);
|
|
45
58
|
}
|
|
@@ -1,26 +1,35 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
const cds = require("@sap/cds");
|
|
4
|
-
|
|
5
|
-
const
|
|
4
|
+
|
|
5
|
+
const eventQueue = require("../../src");
|
|
6
6
|
const distributedLock = require("../../src/shared/distributedLock");
|
|
7
7
|
const redisPub = require("../../src/redis/redisPub");
|
|
8
|
+
const publishEventHelper = require("./publishEventHelper");
|
|
9
|
+
const commonHelper = require("../../src/shared/common");
|
|
10
|
+
|
|
11
|
+
const COMPONENT_NAME = "/eventQueue/admin";
|
|
8
12
|
|
|
9
13
|
module.exports = class AdminService extends cds.ApplicationService {
|
|
10
14
|
async init() {
|
|
11
15
|
const { Event: EventService, Lock: LockService } = this.entities;
|
|
12
16
|
const { Event: EventDb } = cds.db.entities("sap.eventqueue");
|
|
17
|
+
const { publishEvent } = this.actions;
|
|
13
18
|
const { landscape, space } = this.getLandscapeAndSpace();
|
|
14
19
|
|
|
15
20
|
this.before("*", (req) => {
|
|
16
|
-
if (!config.enableAdminService) {
|
|
21
|
+
if (!eventQueue.config.enableAdminService) {
|
|
17
22
|
req.reject(403, "Admin service is disabled by configuration");
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
if (req.event === publishEvent.name.split(".")[1]) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
20
29
|
const headers = Object.assign({}, req.headers, req.req?.headers);
|
|
21
30
|
const tenant = headers["z-id"] ?? req.data.tenant;
|
|
22
31
|
|
|
23
|
-
if (config.isMultiTenancy && tenant == null) {
|
|
32
|
+
if (eventQueue.config.isMultiTenancy && tenant == null) {
|
|
24
33
|
req.reject(400, "Missing tenant ID in request header (z-id)");
|
|
25
34
|
}
|
|
26
35
|
req.headers ??= {};
|
|
@@ -46,7 +55,7 @@ module.exports = class AdminService extends cds.ApplicationService {
|
|
|
46
55
|
});
|
|
47
56
|
|
|
48
57
|
this.on("READ", LockService, async () => {
|
|
49
|
-
if (!config.redisEnabled) {
|
|
58
|
+
if (!eventQueue.config.redisEnabled) {
|
|
50
59
|
return [];
|
|
51
60
|
}
|
|
52
61
|
const locks = await distributedLock.getAllLocksRedis();
|
|
@@ -59,14 +68,14 @@ module.exports = class AdminService extends cds.ApplicationService {
|
|
|
59
68
|
|
|
60
69
|
this.on("setStatusAndAttempts", async (req) => {
|
|
61
70
|
const tenant = req.headers["z-id"];
|
|
62
|
-
cds.log(
|
|
71
|
+
cds.log(COMPONENT_NAME).info("Restarting processing for event queue");
|
|
63
72
|
const updateData = {};
|
|
64
73
|
|
|
65
74
|
if (Number.isInteger(req.data.attempts)) {
|
|
66
75
|
updateData.attempts = req.data.attempts;
|
|
67
76
|
}
|
|
68
77
|
|
|
69
|
-
if (Object.values(EventProcessingStatus).includes(req.data.status)) {
|
|
78
|
+
if (Object.values(eventQueue.EventProcessingStatus).includes(req.data.status)) {
|
|
70
79
|
updateData.status = req.data.status;
|
|
71
80
|
}
|
|
72
81
|
|
|
@@ -95,6 +104,32 @@ module.exports = class AdminService extends cds.ApplicationService {
|
|
|
95
104
|
});
|
|
96
105
|
});
|
|
97
106
|
|
|
107
|
+
this.on(publishEvent, async (req) => {
|
|
108
|
+
const logger = cds.log(COMPONENT_NAME);
|
|
109
|
+
try {
|
|
110
|
+
const { type, subType, referenceEntity, referenceEntityKey, payload, startAfter } = req.data;
|
|
111
|
+
const tenants = await publishEventHelper.resolveTenantInfos(req);
|
|
112
|
+
const eventOptions = commonHelper.cleanUndefined({
|
|
113
|
+
type,
|
|
114
|
+
subType,
|
|
115
|
+
referenceEntity,
|
|
116
|
+
referenceEntityKey,
|
|
117
|
+
payload,
|
|
118
|
+
startAfter,
|
|
119
|
+
});
|
|
120
|
+
const publishInfo = { count: tenants.length, type, subType, tenants: req.data.tenants };
|
|
121
|
+
logger.info("publishing event for tenant(s)", publishInfo);
|
|
122
|
+
for (const tenant of tenants) {
|
|
123
|
+
await cds.tx({ tenant }, async (tx) => {
|
|
124
|
+
await eventQueue.publishEvent(tx, { ...eventOptions });
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
logger.info("finished publishing event for tenant(s)", publishInfo);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
logger.error("error publishing event", err);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
98
133
|
await super.init();
|
|
99
134
|
}
|
|
100
135
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const cdsHelper = require("../../src/shared/cdsHelper");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef TenantInfo
|
|
7
|
+
* @type object
|
|
8
|
+
* @property {string} tenantId
|
|
9
|
+
* @property {string} subdomain
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* For service action interfaces, with a tenants = [...] data field this code
|
|
13
|
+
* resolves the inputs into a tenantInfos array.
|
|
14
|
+
*
|
|
15
|
+
* Inputs:
|
|
16
|
+
* - ["all"] give me all tenants
|
|
17
|
+
* - ["self"] give me just the context tenant
|
|
18
|
+
* - [] give me no tenant
|
|
19
|
+
* - ["<tenantId1>", "<tenantId2>", "<subdomain3>", "<subdomain4>",...]
|
|
20
|
+
* give me those tenants where either tenantId or subdomain corresponds to the given inputs
|
|
21
|
+
*
|
|
22
|
+
* @returns {Promise<Array<TenantInfo>>} returns an array of {@link TenantInfo} objects.
|
|
23
|
+
*/
|
|
24
|
+
const resolveTenantInfos = async (context, { sortByTenantId = true } = {}) => {
|
|
25
|
+
let result = await _resolveTenantInfos(context);
|
|
26
|
+
if (sortByTenantId) {
|
|
27
|
+
result = result.sort(_orderByTenantId);
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const _resolveTenantInfos = async (context) => {
|
|
33
|
+
if (!Array.isArray(context.data.tenants)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
for (const tenant of context.data.tenants) {
|
|
37
|
+
if (tenant === "self") {
|
|
38
|
+
return [context.tenant];
|
|
39
|
+
}
|
|
40
|
+
if (["all", "*"].includes(tenant)) {
|
|
41
|
+
return await cdsHelper.getAllTenantIds();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const contextTenantsMap = Object.fromEntries(context.data.tenants.map((tenant) => [tenant, true]));
|
|
45
|
+
return (await cdsHelper.getAllTenantIds()).filter((tenantId) => contextTenantsMap[tenantId]);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const _orderByTenantId = (a, b) => a.localeCompare(b);
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
resolveTenantInfos,
|
|
52
|
+
};
|