@cap-js-community/event-queue 1.11.0-beta.1 → 1.11.0-beta.3
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 +6 -6
- package/src/config.js +3 -2
- package/src/initialize.js +0 -3
- package/src/outbox/EventQueueGenericOutboxHandler.js +28 -28
- package/src/shared/distributedLock.js +63 -7
- package/src/shared/redis.js +10 -0
- package/srv/service/admin-service.cds +12 -0
- package/srv/service/admin-service.js +14 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/event-queue",
|
|
3
|
-
"version": "1.11.0-beta.
|
|
3
|
+
"version": "1.11.0-beta.3",
|
|
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",
|
|
@@ -76,7 +76,7 @@ class EventQueueProcessorBase {
|
|
|
76
76
|
this.__txMap = {};
|
|
77
77
|
this.__txRollback = {};
|
|
78
78
|
this.__queueEntries = [];
|
|
79
|
-
this.#keepAliveRunner = new SetIntervalDriftSafe(this.#eventConfig.keepAliveInterval);
|
|
79
|
+
this.#keepAliveRunner = new SetIntervalDriftSafe(this.#eventConfig.keepAliveInterval * 1000);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -617,7 +617,7 @@ class EventQueueProcessorBase {
|
|
|
617
617
|
"OR lastAttemptTimestamp IS NULL ) OR ( status =",
|
|
618
618
|
EventProcessingStatus.InProgress,
|
|
619
619
|
"AND lastAttemptTimestamp <=",
|
|
620
|
-
new Date(baseDate - this.#eventConfig.keepAliveMaxInProgressTime).toISOString(),
|
|
620
|
+
new Date(baseDate - this.#eventConfig.keepAliveMaxInProgressTime * 1000).toISOString(),
|
|
621
621
|
") )",
|
|
622
622
|
]
|
|
623
623
|
: [
|
|
@@ -628,7 +628,7 @@ class EventQueueProcessorBase {
|
|
|
628
628
|
") OR ( status =",
|
|
629
629
|
EventProcessingStatus.InProgress,
|
|
630
630
|
"AND lastAttemptTimestamp <=",
|
|
631
|
-
new Date(baseDate - this.#eventConfig.keepAliveMaxInProgressTime).toISOString(),
|
|
631
|
+
new Date(baseDate - this.#eventConfig.keepAliveMaxInProgressTime * 1000).toISOString(),
|
|
632
632
|
") )",
|
|
633
633
|
])
|
|
634
634
|
)
|
|
@@ -868,7 +868,7 @@ class EventQueueProcessorBase {
|
|
|
868
868
|
}
|
|
869
869
|
|
|
870
870
|
continuesKeepAlive() {
|
|
871
|
-
if (Date.now() - this.lockAcquiredTime.getTime() >= this.#eventConfig.keepAliveInterval) {
|
|
871
|
+
if (Date.now() - this.lockAcquiredTime.getTime() >= this.#eventConfig.keepAliveInterval * 1000) {
|
|
872
872
|
trace(this.baseContext, "keepAlive-between-iterations", async () => {
|
|
873
873
|
await this.#renewDistributedLock();
|
|
874
874
|
}).catch((err) => this.logger.error("renewing lock between intervals failed!", err));
|
|
@@ -961,7 +961,7 @@ class EventQueueProcessorBase {
|
|
|
961
961
|
const lockAcquired = await distributedLock.acquireLock(
|
|
962
962
|
this.__context,
|
|
963
963
|
[this.#eventType, this.#eventSubType].join("##"),
|
|
964
|
-
{ keepTrackOfLock: true, expiryTime: this.#eventConfig.keepAliveMaxInProgressTime }
|
|
964
|
+
{ keepTrackOfLock: true, expiryTime: this.#eventConfig.keepAliveMaxInProgressTime * 1000 }
|
|
965
965
|
);
|
|
966
966
|
if (!lockAcquired) {
|
|
967
967
|
this.logger.debug("no lock available, exit processing", {
|
|
@@ -983,7 +983,7 @@ class EventQueueProcessorBase {
|
|
|
983
983
|
const lockAcquired = await distributedLock.renewLock(
|
|
984
984
|
this.__context,
|
|
985
985
|
[this.#eventType, this.#eventSubType].join("##"),
|
|
986
|
-
{ expiryTime: this.#eventConfig.keepAliveMaxInProgressTime }
|
|
986
|
+
{ expiryTime: this.#eventConfig.keepAliveMaxInProgressTime * 1000 }
|
|
987
987
|
);
|
|
988
988
|
if (!lockAcquired) {
|
|
989
989
|
this.logger.error("renewing distributed lock failed!", {
|
package/src/config.js
CHANGED
|
@@ -28,6 +28,7 @@ const DEFAULT_CHECK_FOR_NEXT_CHUNK = true;
|
|
|
28
28
|
const SUFFIX_PERIODIC = "_PERIODIC";
|
|
29
29
|
const CAP_EVENT_TYPE = "CAP_OUTBOX";
|
|
30
30
|
const CAP_PARALLEL_DEFAULT = 5;
|
|
31
|
+
const CAP_MAX_ATTEMPTS_DEFAULT = 5;
|
|
31
32
|
const DELETE_TENANT_BLOCK_AFTER_MS = 5 * 60 * 1000;
|
|
32
33
|
const PRIORITIES = Object.values(Priorities);
|
|
33
34
|
const UTC_DEFAULT = false;
|
|
@@ -387,7 +388,7 @@ class Config {
|
|
|
387
388
|
kind: config.kind ?? "persistent-outbox",
|
|
388
389
|
selectMaxChunkSize: config.selectMaxChunkSize ?? config.chunkSize,
|
|
389
390
|
parallelEventProcessing: config.parallelEventProcessing ?? (config.parallel && CAP_PARALLEL_DEFAULT),
|
|
390
|
-
retryAttempts: config.retryAttempts ?? config.maxAttempts,
|
|
391
|
+
retryAttempts: config.retryAttempts ?? config.maxAttempts ?? CAP_MAX_ATTEMPTS_DEFAULT,
|
|
391
392
|
...config,
|
|
392
393
|
});
|
|
393
394
|
eventConfig.internalEvent = true;
|
|
@@ -550,7 +551,7 @@ class Config {
|
|
|
550
551
|
event.load = event.load ?? DEFAULT_LOAD;
|
|
551
552
|
event.priority = event.priority ?? DEFAULT_PRIORITY;
|
|
552
553
|
event.increasePriorityOverTime = event.increasePriorityOverTime ?? DEFAULT_INCREASE_PRIORITY;
|
|
553
|
-
event.keepAliveInterval =
|
|
554
|
+
event.keepAliveInterval = event.keepAliveInterval ?? DEFAULT_KEEP_ALIVE_INTERVAL;
|
|
554
555
|
event.keepAliveMaxInProgressTime = event.keepAliveInterval * DEFAULT_MAX_FACTOR_STUCK_2_KEEP_ALIVE_INTERVAL;
|
|
555
556
|
event.checkForNextChunk = event.checkForNextChunk ?? DEFAULT_CHECK_FOR_NEXT_CHUNK;
|
|
556
557
|
}
|
package/src/initialize.js
CHANGED
|
@@ -61,7 +61,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
61
61
|
}
|
|
62
62
|
} else {
|
|
63
63
|
for (const actionName in genericClusterEvents) {
|
|
64
|
-
const
|
|
64
|
+
const reg = new cds.Request({
|
|
65
65
|
event: EVENT_QUEUE_ACTIONS.CLUSTER,
|
|
66
66
|
user: this.context.user,
|
|
67
67
|
eventQueue: {
|
|
@@ -74,14 +74,14 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
74
74
|
this.#clusterByDataProperty(actionName, genericClusterEvents[actionName], propertyName, cb),
|
|
75
75
|
},
|
|
76
76
|
});
|
|
77
|
-
const clusterResult = await this.__srvUnboxed.tx(this.context).send(
|
|
77
|
+
const clusterResult = await this.__srvUnboxed.tx(this.context).send(reg);
|
|
78
78
|
if (this.#validateCluster(clusterResult)) {
|
|
79
79
|
Object.assign(clusterMap, clusterResult);
|
|
80
80
|
} else {
|
|
81
81
|
this.logger.error(
|
|
82
82
|
"cluster result of handler is not valid. Check the documentation for the expected structure. Continuing without clustering!",
|
|
83
83
|
{
|
|
84
|
-
handler:
|
|
84
|
+
handler: reg.event,
|
|
85
85
|
clusterResult: JSON.stringify(clusterResult),
|
|
86
86
|
}
|
|
87
87
|
);
|
|
@@ -92,7 +92,7 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
for (const actionName in specificClusterEvents) {
|
|
95
|
-
const
|
|
95
|
+
const reg = new cds.Request({
|
|
96
96
|
event: `${EVENT_QUEUE_ACTIONS.CLUSTER}.${actionName}`,
|
|
97
97
|
user: this.context.user,
|
|
98
98
|
eventQueue: {
|
|
@@ -105,14 +105,14 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
105
105
|
this.#clusterByDataProperty(actionName, specificClusterEvents[actionName], propertyName, cb),
|
|
106
106
|
},
|
|
107
107
|
});
|
|
108
|
-
const clusterResult = await this.__srvUnboxed.tx(this.context).send(
|
|
108
|
+
const clusterResult = await this.__srvUnboxed.tx(this.context).send(reg);
|
|
109
109
|
if (this.#validateCluster(clusterResult)) {
|
|
110
110
|
Object.assign(clusterMap, clusterResult);
|
|
111
111
|
} else {
|
|
112
112
|
this.logger.error(
|
|
113
113
|
"cluster result of handler is not valid. Check the documentation for the expected structure. Continuing without clustering!",
|
|
114
114
|
{
|
|
115
|
-
handler:
|
|
115
|
+
handler: reg.event,
|
|
116
116
|
clusterResult: JSON.stringify(clusterResult),
|
|
117
117
|
}
|
|
118
118
|
);
|
|
@@ -264,12 +264,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
264
264
|
return payload;
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
-
const {
|
|
267
|
+
const { reg, userId } = this.#buildDispatchData(this.context, payload, {
|
|
268
268
|
queueEntries: [queueEntry],
|
|
269
269
|
});
|
|
270
|
-
|
|
271
|
-
await this.#setContextUser(this.context, userId,
|
|
272
|
-
const data = await this.__srvUnboxed.tx(this.context).send(
|
|
270
|
+
reg.event = handlerName;
|
|
271
|
+
await this.#setContextUser(this.context, userId, reg);
|
|
272
|
+
const data = await this.__srvUnboxed.tx(this.context).send(reg);
|
|
273
273
|
if (data) {
|
|
274
274
|
payload.data = data;
|
|
275
275
|
return payload;
|
|
@@ -285,12 +285,12 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
285
285
|
return await super.hookForExceededEvents(exceededEvent);
|
|
286
286
|
}
|
|
287
287
|
|
|
288
|
-
const {
|
|
288
|
+
const { reg, userId } = this.#buildDispatchData(this.context, exceededEvent.payload, {
|
|
289
289
|
queueEntries: [exceededEvent],
|
|
290
290
|
});
|
|
291
|
-
await this.#setContextUser(this.context, userId,
|
|
292
|
-
|
|
293
|
-
await this.__srvUnboxed.tx(this.context).send(
|
|
291
|
+
await this.#setContextUser(this.context, userId, reg);
|
|
292
|
+
reg.event = handlerName;
|
|
293
|
+
await this.__srvUnboxed.tx(this.context).send(reg);
|
|
294
294
|
}
|
|
295
295
|
|
|
296
296
|
// NOTE: Currently not exposed to CAP service; we wait for a valid use case
|
|
@@ -310,37 +310,37 @@ class EventQueueGenericOutboxHandler extends EventQueueBaseClass {
|
|
|
310
310
|
|
|
311
311
|
async processPeriodicEvent(processContext, key, queueEntry) {
|
|
312
312
|
const [, action] = this.eventSubType.split(".");
|
|
313
|
-
const
|
|
314
|
-
await this.#setContextUser(processContext, config.userId,
|
|
315
|
-
await this.__srvUnboxed.tx(processContext).emit(
|
|
313
|
+
const reg = new cds.Event({ event: action, eventQueue: { processor: this, key, queueEntries: [queueEntry] } });
|
|
314
|
+
await this.#setContextUser(processContext, config.userId, reg);
|
|
315
|
+
await this.__srvUnboxed.tx(processContext).emit(reg);
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
#buildDispatchData(context, payload, { key, queueEntries } = {}) {
|
|
319
319
|
const { useEventQueueUser } = this.eventConfig;
|
|
320
320
|
const userId = useEventQueueUser ? config.userId : payload.contextUser;
|
|
321
|
-
const
|
|
321
|
+
const reg = payload._fromSend ? new cds.Request(payload) : new cds.Event(payload);
|
|
322
322
|
const invocationFn = payload._fromSend ? "send" : "emit";
|
|
323
|
-
delete
|
|
324
|
-
delete
|
|
325
|
-
|
|
326
|
-
return {
|
|
323
|
+
delete reg._fromSend;
|
|
324
|
+
delete reg.contextUser;
|
|
325
|
+
reg.eventQueue = { processor: this, key, queueEntries, payload };
|
|
326
|
+
return { reg, userId, invocationFn };
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
-
async #setContextUser(context, userId,
|
|
329
|
+
async #setContextUser(context, userId, reg) {
|
|
330
330
|
context.user = new cds.User.Privileged({
|
|
331
331
|
id: userId,
|
|
332
332
|
tokenInfo: await common.getTokenInfo(this.baseContext.tenant),
|
|
333
333
|
});
|
|
334
|
-
if (
|
|
335
|
-
|
|
334
|
+
if (reg) {
|
|
335
|
+
reg.user = context.user;
|
|
336
336
|
}
|
|
337
337
|
}
|
|
338
338
|
|
|
339
339
|
async processEvent(processContext, key, queueEntries, payload) {
|
|
340
340
|
try {
|
|
341
|
-
const { userId, invocationFn,
|
|
342
|
-
await this.#setContextUser(processContext, userId,
|
|
343
|
-
const result = await this.__srvUnboxed.tx(processContext)[invocationFn](
|
|
341
|
+
const { userId, invocationFn, reg } = this.#buildDispatchData(processContext, payload, { key, queueEntries });
|
|
342
|
+
await this.#setContextUser(processContext, userId, reg);
|
|
343
|
+
const result = await this.__srvUnboxed.tx(processContext)[invocationFn](reg);
|
|
344
344
|
return this.#determineResultStatus(result, queueEntries);
|
|
345
345
|
} catch (err) {
|
|
346
346
|
this.logger.error("error processing outboxed service call", err, {
|
|
@@ -17,7 +17,7 @@ const acquireLock = async (
|
|
|
17
17
|
if (config.redisEnabled) {
|
|
18
18
|
return await _acquireLockRedis(context, fullKey, expiryTime, { keepTrackOfLock });
|
|
19
19
|
} else {
|
|
20
|
-
return await _acquireLockDB(context, fullKey, expiryTime);
|
|
20
|
+
return await _acquireLockDB(context, fullKey, expiryTime, { keepTrackOfLock });
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
@@ -73,7 +73,7 @@ const _acquireLockRedis = async (
|
|
|
73
73
|
context,
|
|
74
74
|
fullKey,
|
|
75
75
|
expiryTime,
|
|
76
|
-
{ value =
|
|
76
|
+
{ value = Date.now(), overrideValue = false, keepTrackOfLock } = {}
|
|
77
77
|
) => {
|
|
78
78
|
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
79
79
|
const result = await client.set(fullKey, value, {
|
|
@@ -82,7 +82,7 @@ const _acquireLockRedis = async (
|
|
|
82
82
|
});
|
|
83
83
|
const isOk = result === REDIS_COMMAND_OK;
|
|
84
84
|
if (isOk && keepTrackOfLock) {
|
|
85
|
-
existingLocks[fullKey] =
|
|
85
|
+
existingLocks[fullKey] = context.tenant;
|
|
86
86
|
}
|
|
87
87
|
return isOk;
|
|
88
88
|
};
|
|
@@ -108,7 +108,7 @@ const _renewLockRedis = async (context, fullKey, expiryTime, { value = "true" }
|
|
|
108
108
|
|
|
109
109
|
const _checkLockExistsRedis = async (context, fullKey) => {
|
|
110
110
|
const client = await redis.createMainClientAndConnect(config.redisOptions);
|
|
111
|
-
return await client.
|
|
111
|
+
return await client.exists(fullKey);
|
|
112
112
|
};
|
|
113
113
|
|
|
114
114
|
const _checkLockExistsDb = async (context, fullKey) => {
|
|
@@ -129,9 +129,15 @@ const _releaseLockDb = async (context, fullKey) => {
|
|
|
129
129
|
await cdsHelper.executeInNewTransaction(context, "distributedLock-release", async (tx) => {
|
|
130
130
|
await tx.run(DELETE.from(config.tableNameEventLock).where("code =", fullKey));
|
|
131
131
|
});
|
|
132
|
+
delete existingLocks[fullKey];
|
|
132
133
|
};
|
|
133
134
|
|
|
134
|
-
const _acquireLockDB = async (
|
|
135
|
+
const _acquireLockDB = async (
|
|
136
|
+
context,
|
|
137
|
+
fullKey,
|
|
138
|
+
expiryTime,
|
|
139
|
+
{ value = "true", overrideValue = false, keepTrackOfLock } = {}
|
|
140
|
+
) => {
|
|
135
141
|
let result;
|
|
136
142
|
await cdsHelper.executeInNewTransaction(context, "distributedLock-acquire", async (tx) => {
|
|
137
143
|
try {
|
|
@@ -171,6 +177,9 @@ const _acquireLockDB = async (context, fullKey, expiryTime, { value = "true", ov
|
|
|
171
177
|
}
|
|
172
178
|
}
|
|
173
179
|
});
|
|
180
|
+
if (result && keepTrackOfLock) {
|
|
181
|
+
existingLocks[fullKey] = context.tenant;
|
|
182
|
+
}
|
|
174
183
|
return result;
|
|
175
184
|
};
|
|
176
185
|
|
|
@@ -181,14 +190,60 @@ const _generateKey = (context, tenantScoped, key) => {
|
|
|
181
190
|
return `${keyParts.join("##")}`;
|
|
182
191
|
};
|
|
183
192
|
|
|
193
|
+
const getAllLocksRedis = async () => {
|
|
194
|
+
const clientOrCluster = await redis.createMainClientAndConnect(config.redisOptions);
|
|
195
|
+
const output = [];
|
|
196
|
+
const results = [];
|
|
197
|
+
|
|
198
|
+
let clients;
|
|
199
|
+
if (redis.isClusterMode()) {
|
|
200
|
+
clients = clientOrCluster.masters.map((master) => master.client);
|
|
201
|
+
} else {
|
|
202
|
+
clients = [clientOrCluster];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// NOTE: use SCAN because KEYS is not supported for cluster clients
|
|
206
|
+
for (const client of clients) {
|
|
207
|
+
for await (const key of client.scanIterator({ MATCH: "EVENT*", COUNT: 1000 })) {
|
|
208
|
+
const [, tenant, guidOrType, subType] = key.split("##");
|
|
209
|
+
if (!subType) {
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const pipeline = client.multi();
|
|
214
|
+
output.push({
|
|
215
|
+
tenant: tenant,
|
|
216
|
+
type: guidOrType,
|
|
217
|
+
subType: subType,
|
|
218
|
+
});
|
|
219
|
+
pipeline.ttl(key).get(key);
|
|
220
|
+
const replies = await pipeline.exec();
|
|
221
|
+
results.push(...replies);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let counter = 0;
|
|
226
|
+
for (const row of output) {
|
|
227
|
+
const ttl = results[counter];
|
|
228
|
+
const createdAt = results[counter + 1];
|
|
229
|
+
Object.assign(row, { ttl, createdAt });
|
|
230
|
+
counter = counter + 2;
|
|
231
|
+
}
|
|
232
|
+
return output;
|
|
233
|
+
};
|
|
234
|
+
|
|
184
235
|
const shutdownHandler = async () => {
|
|
185
236
|
const logger = cds.log(COMPONENT_NAME);
|
|
186
237
|
logger.info("received shutdown event, trying to release all locks", {
|
|
187
238
|
numberOfLocks: Object.keys(existingLocks).length,
|
|
188
239
|
});
|
|
189
240
|
const result = await Promise.allSettled(
|
|
190
|
-
Object.
|
|
191
|
-
|
|
241
|
+
Object.entries(existingLocks).map(async ([key, tenant]) => {
|
|
242
|
+
if (config.redisEnabled) {
|
|
243
|
+
await _releaseLockRedis({ tenant }, key);
|
|
244
|
+
} else {
|
|
245
|
+
await _releaseLockDb({ tenant }, key);
|
|
246
|
+
}
|
|
192
247
|
logger.info("lock released", { key });
|
|
193
248
|
})
|
|
194
249
|
);
|
|
@@ -206,4 +261,5 @@ module.exports = {
|
|
|
206
261
|
setValueWithExpire,
|
|
207
262
|
shutdownHandler,
|
|
208
263
|
renewLock,
|
|
264
|
+
getAllLocksRedis,
|
|
209
265
|
};
|
package/src/shared/redis.js
CHANGED
|
@@ -178,6 +178,15 @@ const connectionCheck = async (options) => {
|
|
|
178
178
|
});
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
+
const isClusterMode = () => {
|
|
182
|
+
if (!("__clusterMode" in isClusterMode)) {
|
|
183
|
+
const env = getEnvInstance();
|
|
184
|
+
const { credentials } = env.redisRequires;
|
|
185
|
+
isClusterMode.__clusterMode = credentials.cluster_mode;
|
|
186
|
+
}
|
|
187
|
+
return isClusterMode.__clusterMode;
|
|
188
|
+
};
|
|
189
|
+
|
|
181
190
|
module.exports = {
|
|
182
191
|
createClientAndConnect,
|
|
183
192
|
createMainClientAndConnect,
|
|
@@ -186,4 +195,5 @@ module.exports = {
|
|
|
186
195
|
closeMainClient,
|
|
187
196
|
closeSubscribeClient,
|
|
188
197
|
connectionCheck,
|
|
198
|
+
isClusterMode,
|
|
189
199
|
};
|
|
@@ -22,6 +22,18 @@ service EventQueueAdminService {
|
|
|
22
22
|
attempts: Integer) returns Event;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
@cds.persistence.skip
|
|
26
|
+
@readonly
|
|
27
|
+
entity Lock {
|
|
28
|
+
key tenant: String;
|
|
29
|
+
key type: String;
|
|
30
|
+
key subType: String;
|
|
31
|
+
landscape: String;
|
|
32
|
+
space: String;
|
|
33
|
+
ttl: Integer;
|
|
34
|
+
createdAt: Integer;
|
|
35
|
+
}
|
|
36
|
+
|
|
25
37
|
@readonly
|
|
26
38
|
@cds.persistence.skip
|
|
27
39
|
entity Tenant {
|
|
@@ -4,10 +4,11 @@ const cds = require("@sap/cds");
|
|
|
4
4
|
const cdsHelper = require("../../src/shared/cdsHelper");
|
|
5
5
|
const { EventProcessingStatus } = require("../../src");
|
|
6
6
|
const config = require("../../src/config");
|
|
7
|
+
const distributedLock = require("../../src/shared/distributedLock");
|
|
7
8
|
|
|
8
9
|
module.exports = class AdminService extends cds.ApplicationService {
|
|
9
10
|
async init() {
|
|
10
|
-
const { Event: EventService, Tenant } = this.entities();
|
|
11
|
+
const { Event: EventService, Tenant, Lock: LockService } = this.entities();
|
|
11
12
|
const { Event: EventDb } = cds.db.entities("sap.eventqueue");
|
|
12
13
|
const { landscape, space } = this.getLandscapeAndSpace();
|
|
13
14
|
|
|
@@ -47,6 +48,18 @@ module.exports = class AdminService extends cds.ApplicationService {
|
|
|
47
48
|
});
|
|
48
49
|
});
|
|
49
50
|
|
|
51
|
+
this.on("READ", LockService, async () => {
|
|
52
|
+
if (!config.redisEnabled) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
const locks = await distributedLock.getAllLocksRedis();
|
|
56
|
+
return locks.map((lock) => ({
|
|
57
|
+
...lock,
|
|
58
|
+
landscape: landscape,
|
|
59
|
+
space: space,
|
|
60
|
+
}));
|
|
61
|
+
});
|
|
62
|
+
|
|
50
63
|
this.on("READ", Tenant, async () => {
|
|
51
64
|
const tenants = await cdsHelper.getAllTenantWithSubdomain();
|
|
52
65
|
return tenants ?? [];
|