@cap-js-community/event-queue 1.11.0-beta.2 → 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
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",
|
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;
|
|
@@ -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, {
|
|
@@ -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) => {
|
|
@@ -191,39 +191,36 @@ const _generateKey = (context, tenantScoped, key) => {
|
|
|
191
191
|
};
|
|
192
192
|
|
|
193
193
|
const getAllLocksRedis = async () => {
|
|
194
|
-
const
|
|
195
|
-
const batchSize = 500;
|
|
196
|
-
const results = [];
|
|
197
|
-
let pipeline = client.multi();
|
|
194
|
+
const clientOrCluster = await redis.createMainClientAndConnect(config.redisOptions);
|
|
198
195
|
const output = [];
|
|
199
|
-
|
|
196
|
+
const results = [];
|
|
200
197
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
198
|
+
let clients;
|
|
199
|
+
if (redis.isClusterMode()) {
|
|
200
|
+
clients = clientOrCluster.masters.map((master) => master.client);
|
|
201
|
+
} else {
|
|
202
|
+
clients = [clientOrCluster];
|
|
203
|
+
}
|
|
207
204
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
subType
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
+
}
|
|
215
212
|
|
|
216
|
-
|
|
213
|
+
const pipeline = client.multi();
|
|
214
|
+
output.push({
|
|
215
|
+
tenant: tenant,
|
|
216
|
+
type: guidOrType,
|
|
217
|
+
subType: subType,
|
|
218
|
+
});
|
|
219
|
+
pipeline.ttl(key).get(key);
|
|
217
220
|
const replies = await pipeline.exec();
|
|
218
221
|
results.push(...replies);
|
|
219
|
-
pipeline = client.multi();
|
|
220
|
-
count = 0;
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
|
-
if (count > 0) {
|
|
224
|
-
const replies = await pipeline.exec();
|
|
225
|
-
results.push(...replies);
|
|
226
|
-
}
|
|
227
224
|
|
|
228
225
|
let counter = 0;
|
|
229
226
|
for (const row of output) {
|
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
|
};
|