power-queues 2.0.4 → 2.0.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/dist/index.cjs +466 -523
- package/dist/index.d.cts +67 -13
- package/dist/index.d.ts +67 -13
- package/dist/index.js +466 -523
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// src/PowerQueues.ts
|
|
2
|
+
import { PowerRedis } from "power-redis";
|
|
2
3
|
import { wait } from "full-utils";
|
|
3
|
-
|
|
4
|
-
// src/AddTasks.ts
|
|
5
4
|
import { v4 as uuid } from "uuid";
|
|
6
5
|
|
|
7
6
|
// src/scripts.ts
|
|
@@ -230,593 +229,537 @@ var SelectStuck = `
|
|
|
230
229
|
return results
|
|
231
230
|
`;
|
|
232
231
|
|
|
233
|
-
// src/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
232
|
+
// src/PowerQueues.ts
|
|
233
|
+
var PowerQueues = class extends PowerRedis {
|
|
234
|
+
constructor() {
|
|
235
|
+
super(...arguments);
|
|
236
|
+
this.abort = new AbortController();
|
|
237
|
+
this.strictCheckingConnection = ["true", "on", "yes", "y", "1"].includes(String(process.env.REDIS_STRICT_CHECK_CONNECTION ?? "").trim().toLowerCase());
|
|
238
|
+
this.scripts = {};
|
|
239
|
+
this.addingBatchTasksCount = 800;
|
|
240
|
+
this.addingBatchKeysLimit = 1e4;
|
|
241
|
+
this.idemOn = true;
|
|
242
|
+
this.idemKey = "";
|
|
243
|
+
this.workerExecuteLockTimeoutMs = 18e4;
|
|
244
|
+
this.workerCacheTaskTimeoutMs = 60;
|
|
245
|
+
this.approveBatchTasksCount = 2e3;
|
|
246
|
+
this.removeOnExecuted = false;
|
|
247
|
+
this.executeBatchAtOnce = false;
|
|
248
|
+
this.executeJobStatus = false;
|
|
249
|
+
this.executeJobStatusTtlSec = 300;
|
|
250
|
+
this.consumerHost = "host";
|
|
251
|
+
this.stream = "stream";
|
|
252
|
+
this.group = "group";
|
|
253
|
+
this.workerBatchTasksCount = 200;
|
|
254
|
+
this.recoveryStuckTasksTimeoutMs = 6e4;
|
|
255
|
+
this.workerLoopIntervalMs = 5e3;
|
|
256
|
+
this.workerSelectionTimeoutMs = 80;
|
|
257
|
+
}
|
|
258
|
+
async onSelected(data) {
|
|
259
|
+
return data;
|
|
260
|
+
}
|
|
261
|
+
async onExecute(id, payload, createdAt, job, key) {
|
|
262
|
+
}
|
|
263
|
+
async onExecuted(data) {
|
|
264
|
+
}
|
|
265
|
+
async onSuccess(id, payload, createdAt, job, key) {
|
|
266
|
+
}
|
|
267
|
+
async runQueue() {
|
|
268
|
+
await this.createGroup("0-0");
|
|
269
|
+
await this.consumerLoop();
|
|
270
|
+
}
|
|
271
|
+
async consumerLoop() {
|
|
272
|
+
const signal = this.signal();
|
|
273
|
+
while (!signal?.aborted) {
|
|
256
274
|
try {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
return await this.redis.evalsha(this.scripts[name].codeReady, keys.length, ...keys, ...args);
|
|
275
|
+
const tasks = await this.select();
|
|
276
|
+
if (!Array.isArray(tasks) || !(tasks.length > 0)) {
|
|
277
|
+
await wait(600);
|
|
278
|
+
continue;
|
|
262
279
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const scripts = full ? [
|
|
268
|
-
["XAddBulk", XAddBulk],
|
|
269
|
-
["Approve", Approve],
|
|
270
|
-
["IdempotencyAllow", IdempotencyAllow],
|
|
271
|
-
["IdempotencyStart", IdempotencyStart],
|
|
272
|
-
["IdempotencyDone", IdempotencyDone],
|
|
273
|
-
["IdempotencyFree", IdempotencyFree],
|
|
274
|
-
["SelectStuck", SelectStuck]
|
|
275
|
-
] : [
|
|
276
|
-
["XAddBulk", XAddBulk]
|
|
277
|
-
];
|
|
278
|
-
for (const [name, code] of scripts) {
|
|
279
|
-
await this.loadScript(this.saveScript(name, code));
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
async loadScript(code) {
|
|
283
|
-
for (let i = 0; i < 3; i++) {
|
|
284
|
-
try {
|
|
285
|
-
return await this.redis.script("LOAD", code);
|
|
286
|
-
} catch (e) {
|
|
287
|
-
if (i === 2) {
|
|
288
|
-
throw e;
|
|
289
|
-
}
|
|
290
|
-
await new Promise((r) => setTimeout(r, 10 + Math.floor(Math.random() * 40)));
|
|
280
|
+
const tasksP = await this.onSelected(tasks);
|
|
281
|
+
const ids = await this.execute(Array.isArray(tasksP) && tasksP.length > 0 ? tasksP : tasks);
|
|
282
|
+
if (Array.isArray(ids) && ids.length > 0) {
|
|
283
|
+
await this.approve(ids);
|
|
291
284
|
}
|
|
285
|
+
} catch (err) {
|
|
286
|
+
await wait(600);
|
|
292
287
|
}
|
|
293
|
-
throw new Error("Load lua script failed.");
|
|
294
|
-
}
|
|
295
|
-
saveScript(name, codeBody) {
|
|
296
|
-
if (typeof codeBody !== "string" || !(codeBody.length > 0)) {
|
|
297
|
-
throw new Error("Script body is empty.");
|
|
298
|
-
}
|
|
299
|
-
this.scripts[name] = { codeBody };
|
|
300
|
-
return codeBody;
|
|
301
288
|
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const batches = this.buildBatches(data);
|
|
323
|
-
const result = new Array(data.length);
|
|
324
|
-
const promises = [];
|
|
325
|
-
let cursor = 0;
|
|
326
|
-
for (const batch of batches) {
|
|
327
|
-
const start = cursor;
|
|
328
|
-
const end = start + batch.length;
|
|
329
|
-
cursor = end;
|
|
330
|
-
promises.push(async () => {
|
|
331
|
-
const partIds = await this.xaddBatch(queueName, ...this.payloadBatch(batch, opts));
|
|
332
|
-
for (let k = 0; k < partIds.length; k++) {
|
|
333
|
-
result[start + k] = partIds[k];
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
const runners = Array.from({ length: promises.length }, async () => {
|
|
338
|
-
while (promises.length) {
|
|
339
|
-
const promise = promises.shift();
|
|
340
|
-
if (promise) {
|
|
341
|
-
await promise();
|
|
342
|
-
}
|
|
289
|
+
}
|
|
290
|
+
async addTasks(queueName, data, opts = {}) {
|
|
291
|
+
if (!Array.isArray(data) || !(data.length > 0)) {
|
|
292
|
+
throw new Error("Tasks is not filled.");
|
|
293
|
+
}
|
|
294
|
+
if (typeof queueName !== "string" || !(queueName.length > 0)) {
|
|
295
|
+
throw new Error("Queue name is required.");
|
|
296
|
+
}
|
|
297
|
+
const batches = this.buildBatches(data);
|
|
298
|
+
const result = new Array(data.length);
|
|
299
|
+
const promises = [];
|
|
300
|
+
let cursor = 0;
|
|
301
|
+
for (const batch of batches) {
|
|
302
|
+
const start = cursor;
|
|
303
|
+
const end = start + batch.length;
|
|
304
|
+
cursor = end;
|
|
305
|
+
promises.push(async () => {
|
|
306
|
+
const partIds = await this.xaddBatch(queueName, ...this.payloadBatch(batch, opts));
|
|
307
|
+
for (let k = 0; k < partIds.length; k++) {
|
|
308
|
+
result[start + k] = partIds[k];
|
|
343
309
|
}
|
|
344
310
|
});
|
|
345
|
-
await Promise.all(runners);
|
|
346
|
-
return result;
|
|
347
311
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
const approx = opts?.exact ? 0 : opts?.approx !== false ? 1 : 0;
|
|
354
|
-
const exact = opts?.exact ? 1 : 0;
|
|
355
|
-
const nomkstream = opts?.nomkstream ? 1 : 0;
|
|
356
|
-
const trimLimit = Math.max(0, Math.floor(opts?.trimLimit ?? 0));
|
|
357
|
-
const minidWindowMs = Math.max(0, Math.floor(opts?.minidWindowMs ?? 0));
|
|
358
|
-
const minidExact = opts?.minidExact ? 1 : 0;
|
|
359
|
-
const argv = [
|
|
360
|
-
String(maxlen),
|
|
361
|
-
String(approx),
|
|
362
|
-
String(data.length),
|
|
363
|
-
String(exact),
|
|
364
|
-
String(nomkstream),
|
|
365
|
-
String(trimLimit),
|
|
366
|
-
String(minidWindowMs),
|
|
367
|
-
String(minidExact)
|
|
368
|
-
];
|
|
369
|
-
for (const item of data) {
|
|
370
|
-
const entry = item;
|
|
371
|
-
const id = entry.id ?? "*";
|
|
372
|
-
let flat;
|
|
373
|
-
if ("flat" in entry && Array.isArray(entry.flat) && entry.flat.length > 0) {
|
|
374
|
-
flat = entry.flat;
|
|
375
|
-
if (flat.length % 2 !== 0) {
|
|
376
|
-
throw new Error('Property "flat" must contain an even number of realKeysLength (field/value pairs).');
|
|
377
|
-
}
|
|
378
|
-
} else if ("payload" in entry && typeof entry.payload === "object" && Object.keys(entry.payload || {}).length > 0) {
|
|
379
|
-
flat = [];
|
|
380
|
-
for (const [k, v] of Object.entries(entry.payload)) {
|
|
381
|
-
flat.push(k, v);
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
throw new Error('Task must have "payload" or "flat".');
|
|
385
|
-
}
|
|
386
|
-
const pairs = flat.length / 2;
|
|
387
|
-
if (pairs <= 0) {
|
|
388
|
-
throw new Error('Task must have "payload" or "flat".');
|
|
389
|
-
}
|
|
390
|
-
argv.push(String(id));
|
|
391
|
-
argv.push(String(pairs));
|
|
392
|
-
for (const token of flat) {
|
|
393
|
-
argv.push(!token ? "" : typeof token === "string" && token.length > 0 ? token : String(token));
|
|
312
|
+
const runners = Array.from({ length: promises.length }, async () => {
|
|
313
|
+
while (promises.length) {
|
|
314
|
+
const promise = promises.shift();
|
|
315
|
+
if (promise) {
|
|
316
|
+
await promise();
|
|
394
317
|
}
|
|
395
318
|
}
|
|
396
|
-
|
|
319
|
+
});
|
|
320
|
+
await Promise.all(runners);
|
|
321
|
+
return result;
|
|
322
|
+
}
|
|
323
|
+
async loadScripts(full = false) {
|
|
324
|
+
const scripts = full ? [
|
|
325
|
+
["XAddBulk", XAddBulk],
|
|
326
|
+
["Approve", Approve],
|
|
327
|
+
["IdempotencyAllow", IdempotencyAllow],
|
|
328
|
+
["IdempotencyStart", IdempotencyStart],
|
|
329
|
+
["IdempotencyDone", IdempotencyDone],
|
|
330
|
+
["IdempotencyFree", IdempotencyFree],
|
|
331
|
+
["SelectStuck", SelectStuck]
|
|
332
|
+
] : [
|
|
333
|
+
["XAddBulk", XAddBulk]
|
|
334
|
+
];
|
|
335
|
+
for (const [name, code] of scripts) {
|
|
336
|
+
await this.loadScript(this.saveScript(name, code));
|
|
397
337
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
let idemKey = entry?.idemKey || uuid();
|
|
407
|
-
if (typeof entry.payload === "object") {
|
|
408
|
-
if (this.idemKey && typeof entry.payload[this.idemKey] === "string" && entry.payload[this.idemKey].length > 0) {
|
|
409
|
-
idemKey = entry.payload[this.idemKey];
|
|
410
|
-
}
|
|
411
|
-
entry = {
|
|
412
|
-
...entry,
|
|
413
|
-
payload: {
|
|
414
|
-
payload: JSON.stringify(entry.payload),
|
|
415
|
-
createdAt,
|
|
416
|
-
job,
|
|
417
|
-
idemKey
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
} else if (Array.isArray(entry.flat)) {
|
|
421
|
-
entry.flat.push("createdAt");
|
|
422
|
-
entry.flat.push(String(createdAt));
|
|
423
|
-
entry.flat.push("job");
|
|
424
|
-
entry.flat.push(job);
|
|
425
|
-
entry.flat.push("idemKey");
|
|
426
|
-
entry.flat.push(idemKey);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
const reqKeysLength = this.keysLength(entry);
|
|
430
|
-
if (batch.length && (batch.length >= this.addingBatchTasksCount || realKeysLength + reqKeysLength > this.addingBatchKeysLimit)) {
|
|
431
|
-
batches.push(batch);
|
|
432
|
-
batch = [];
|
|
433
|
-
realKeysLength = 0;
|
|
338
|
+
}
|
|
339
|
+
async loadScript(code) {
|
|
340
|
+
for (let i = 0; i < 3; i++) {
|
|
341
|
+
try {
|
|
342
|
+
return await this.redis.script("LOAD", code);
|
|
343
|
+
} catch (e) {
|
|
344
|
+
if (i === 2) {
|
|
345
|
+
throw e;
|
|
434
346
|
}
|
|
435
|
-
|
|
436
|
-
realKeysLength += reqKeysLength;
|
|
347
|
+
await new Promise((r) => setTimeout(r, 10 + Math.floor(Math.random() * 40)));
|
|
437
348
|
}
|
|
438
|
-
if (batch.length) {
|
|
439
|
-
batches.push(batch);
|
|
440
|
-
}
|
|
441
|
-
return batches;
|
|
442
|
-
}
|
|
443
|
-
keysLength(task) {
|
|
444
|
-
return 2 + ("flat" in task && Array.isArray(task.flat) && task.flat.length ? task.flat.length : Object.keys(task).length * 2);
|
|
445
349
|
}
|
|
446
|
-
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
return class extends mix(Base2, Script) {
|
|
452
|
-
constructor() {
|
|
453
|
-
super(...arguments);
|
|
454
|
-
this.consumerHost = "host";
|
|
455
|
-
this.stream = "stream";
|
|
456
|
-
this.group = "group";
|
|
457
|
-
this.workerBatchTasksCount = 200;
|
|
458
|
-
this.recoveryStuckTasksTimeoutMs = 6e4;
|
|
459
|
-
this.workerLoopIntervalMs = 5e3;
|
|
460
|
-
this.workerSelectionTimeoutMs = 80;
|
|
350
|
+
throw new Error("Load lua script failed.");
|
|
351
|
+
}
|
|
352
|
+
saveScript(name, codeBody) {
|
|
353
|
+
if (typeof codeBody !== "string" || !(codeBody.length > 0)) {
|
|
354
|
+
throw new Error("Script body is empty.");
|
|
461
355
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
356
|
+
this.scripts[name] = { codeBody };
|
|
357
|
+
return codeBody;
|
|
358
|
+
}
|
|
359
|
+
async runScript(name, keys, args, defaultCode) {
|
|
360
|
+
if (!this.scripts[name]) {
|
|
361
|
+
if (typeof defaultCode !== "string" || !(defaultCode.length > 0)) {
|
|
362
|
+
throw new Error(`Undefined script "${name}". Save it before executing.`);
|
|
470
363
|
}
|
|
364
|
+
this.saveScript(name, defaultCode);
|
|
471
365
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (!entries?.length) {
|
|
475
|
-
entries = await this.selectFresh();
|
|
476
|
-
}
|
|
477
|
-
return this.normalizeEntries(entries);
|
|
366
|
+
if (!this.scripts[name].codeReady) {
|
|
367
|
+
this.scripts[name].codeReady = await this.loadScript(this.scripts[name].codeBody);
|
|
478
368
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
await this.createGroup();
|
|
486
|
-
}
|
|
369
|
+
try {
|
|
370
|
+
return await this.redis.evalsha(this.scripts[name].codeReady, keys.length, ...keys, ...args);
|
|
371
|
+
} catch (err) {
|
|
372
|
+
if (String(err?.message || "").includes("NOSCRIPT")) {
|
|
373
|
+
this.scripts[name].codeReady = await this.loadScript(this.scripts[name].codeBody);
|
|
374
|
+
return await this.redis.evalsha(this.scripts[name].codeReady, keys.length, ...keys, ...args);
|
|
487
375
|
}
|
|
488
|
-
|
|
376
|
+
throw err;
|
|
489
377
|
}
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
378
|
+
}
|
|
379
|
+
async xaddBatch(queueName, ...batches) {
|
|
380
|
+
return await this.runScript("XAddBulk", [queueName], batches, XAddBulk);
|
|
381
|
+
}
|
|
382
|
+
payloadBatch(data, opts) {
|
|
383
|
+
const maxlen = Math.max(0, Math.floor(opts?.maxlen ?? 0));
|
|
384
|
+
const approx = opts?.exact ? 0 : opts?.approx !== false ? 1 : 0;
|
|
385
|
+
const exact = opts?.exact ? 1 : 0;
|
|
386
|
+
const nomkstream = opts?.nomkstream ? 1 : 0;
|
|
387
|
+
const trimLimit = Math.max(0, Math.floor(opts?.trimLimit ?? 0));
|
|
388
|
+
const minidWindowMs = Math.max(0, Math.floor(opts?.minidWindowMs ?? 0));
|
|
389
|
+
const minidExact = opts?.minidExact ? 1 : 0;
|
|
390
|
+
const argv = [
|
|
391
|
+
String(maxlen),
|
|
392
|
+
String(approx),
|
|
393
|
+
String(data.length),
|
|
394
|
+
String(exact),
|
|
395
|
+
String(nomkstream),
|
|
396
|
+
String(trimLimit),
|
|
397
|
+
String(minidWindowMs),
|
|
398
|
+
String(minidExact)
|
|
399
|
+
];
|
|
400
|
+
for (const item of data) {
|
|
401
|
+
const entry = item;
|
|
402
|
+
const id = entry.id ?? "*";
|
|
403
|
+
let flat;
|
|
404
|
+
if ("flat" in entry && Array.isArray(entry.flat) && entry.flat.length > 0) {
|
|
405
|
+
flat = entry.flat;
|
|
406
|
+
if (flat.length % 2 !== 0) {
|
|
407
|
+
throw new Error('Property "flat" must contain an even number of realKeysLength (field/value pairs).');
|
|
507
408
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
} catch (err) {
|
|
513
|
-
if (String(err?.message || "").includes("NOGROUP")) {
|
|
514
|
-
await this.createGroup();
|
|
409
|
+
} else if ("payload" in entry && typeof entry.payload === "object" && Object.keys(entry.payload || {}).length > 0) {
|
|
410
|
+
flat = [];
|
|
411
|
+
for (const [k, v] of Object.entries(entry.payload)) {
|
|
412
|
+
flat.push(k, v);
|
|
515
413
|
}
|
|
414
|
+
} else {
|
|
415
|
+
throw new Error('Task must have "payload" or "flat".');
|
|
516
416
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
417
|
+
const pairs = flat.length / 2;
|
|
418
|
+
if (pairs <= 0) {
|
|
419
|
+
throw new Error('Task must have "payload" or "flat".');
|
|
420
|
+
}
|
|
421
|
+
argv.push(String(id));
|
|
422
|
+
argv.push(String(pairs));
|
|
423
|
+
for (const token of flat) {
|
|
424
|
+
argv.push(!token ? "" : typeof token === "string" && token.length > 0 ? token : String(token));
|
|
522
425
|
}
|
|
523
|
-
return Array.from(raw || []).map((e) => {
|
|
524
|
-
const id = Buffer.isBuffer(e?.[0]) ? e[0].toString() : e?.[0];
|
|
525
|
-
const kvRaw = e?.[1] ?? [];
|
|
526
|
-
const kv = Array.isArray(kvRaw) ? kvRaw.map((x) => Buffer.isBuffer(x) ? x.toString() : x) : [];
|
|
527
|
-
return [id, kv, 0, "", ""];
|
|
528
|
-
}).filter(([id, kv]) => typeof id === "string" && id.length > 0 && Array.isArray(kv) && (kv.length & 1) === 0).map(([id, kv]) => {
|
|
529
|
-
const values = this.values(kv);
|
|
530
|
-
const { idemKey = "", createdAt, job, ...data } = this.payload(values);
|
|
531
|
-
return [id, data, createdAt, job, idemKey];
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
consumer() {
|
|
535
|
-
return `${String(this.consumerHost || "host")}:${process.pid}`;
|
|
536
|
-
}
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// src/ExecuteTasks.ts
|
|
541
|
-
function ExecuteTasks(Base2) {
|
|
542
|
-
return class extends mix(Base2, Script) {
|
|
543
|
-
constructor() {
|
|
544
|
-
super(...arguments);
|
|
545
|
-
this.abort = new AbortController();
|
|
546
|
-
this.workerExecuteLockTimeoutMs = 18e4;
|
|
547
|
-
this.workerCacheTaskTimeoutMs = 60;
|
|
548
|
-
this.approveBatchTasksCount = 2e3;
|
|
549
|
-
this.removeOnExecuted = false;
|
|
550
|
-
this.executeBatchAtOnce = false;
|
|
551
|
-
this.executeJobStatus = false;
|
|
552
|
-
this.executeJobStatusTtlSec = 300;
|
|
553
|
-
this.consumerHost = "host";
|
|
554
|
-
this.stream = "stream";
|
|
555
|
-
this.group = "group";
|
|
556
|
-
}
|
|
557
|
-
async onExecute(id, payload, createdAt, job, key) {
|
|
558
|
-
}
|
|
559
|
-
async onExecuted(data) {
|
|
560
|
-
}
|
|
561
|
-
async onSuccess(id, payload, createdAt, job, key) {
|
|
562
426
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
427
|
+
return argv;
|
|
428
|
+
}
|
|
429
|
+
buildBatches(tasks) {
|
|
430
|
+
const job = uuid();
|
|
431
|
+
const batches = [];
|
|
432
|
+
let batch = [], realKeysLength = 0;
|
|
433
|
+
for (let task of tasks) {
|
|
434
|
+
let entry = task;
|
|
435
|
+
if (this.idemOn) {
|
|
436
|
+
const createdAt = entry?.createdAt || Date.now();
|
|
437
|
+
let idemKey = entry?.idemKey || uuid();
|
|
438
|
+
if (typeof entry.payload === "object") {
|
|
439
|
+
if (this.idemKey && typeof entry.payload[this.idemKey] === "string" && entry.payload[this.idemKey].length > 0) {
|
|
440
|
+
idemKey = entry.payload[this.idemKey];
|
|
441
|
+
}
|
|
442
|
+
entry = {
|
|
443
|
+
...entry,
|
|
444
|
+
payload: {
|
|
445
|
+
payload: JSON.stringify(entry.payload),
|
|
446
|
+
createdAt,
|
|
447
|
+
job,
|
|
448
|
+
idemKey
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
} else if (Array.isArray(entry.flat)) {
|
|
452
|
+
entry.flat.push("createdAt");
|
|
453
|
+
entry.flat.push(String(createdAt));
|
|
454
|
+
entry.flat.push("job");
|
|
455
|
+
entry.flat.push(job);
|
|
456
|
+
entry.flat.push("idemKey");
|
|
457
|
+
entry.flat.push(idemKey);
|
|
458
|
+
}
|
|
566
459
|
}
|
|
567
|
-
|
|
460
|
+
const reqKeysLength = this.keysLength(entry);
|
|
461
|
+
if (batch.length && (batch.length >= this.addingBatchTasksCount || realKeysLength + reqKeysLength > this.addingBatchKeysLimit)) {
|
|
462
|
+
batches.push(batch);
|
|
463
|
+
batch = [];
|
|
464
|
+
realKeysLength = 0;
|
|
465
|
+
}
|
|
466
|
+
batch.push(entry);
|
|
467
|
+
realKeysLength += reqKeysLength;
|
|
568
468
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const { ready = 0, ok = 0 } = await this.getMany(prefix);
|
|
572
|
-
await this.redis.setMany([{ key: `${prefix}ready`, value: ready + 1 }, { key: `${prefix}ok`, value: ok + 1 }], this.executeJobStatusTtlSec);
|
|
469
|
+
if (batch.length) {
|
|
470
|
+
batches.push(batch);
|
|
573
471
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
472
|
+
return batches;
|
|
473
|
+
}
|
|
474
|
+
keysLength(task) {
|
|
475
|
+
return 2 + ("flat" in task && Array.isArray(task.flat) && task.flat.length ? task.flat.length : Object.keys(task).length * 2);
|
|
476
|
+
}
|
|
477
|
+
async success(id, payload, createdAt, job, key) {
|
|
478
|
+
if (this.executeJobStatus) {
|
|
479
|
+
await this.status(id, payload, createdAt, job, key);
|
|
480
|
+
}
|
|
481
|
+
await this.onSuccess(id, payload, createdAt, job, key);
|
|
482
|
+
}
|
|
483
|
+
async status(id, payload, createdAt, job, key) {
|
|
484
|
+
const prefix = `s:${this.stream}:`;
|
|
485
|
+
const { ready = 0, ok = 0 } = await this.getMany(prefix);
|
|
486
|
+
await this.setMany([{ key: `${prefix}ready`, value: ready + 1 }, { key: `${prefix}ok`, value: ok + 1 }], this.executeJobStatusTtlSec);
|
|
487
|
+
}
|
|
488
|
+
async execute(tasks) {
|
|
489
|
+
const result = [];
|
|
490
|
+
let contended = 0, promises = [];
|
|
491
|
+
for (const [id, payload, createdAt, job, idemKey] of tasks) {
|
|
492
|
+
if (this.executeBatchAtOnce) {
|
|
493
|
+
promises.push((async () => {
|
|
588
494
|
const r = await this.executeProcess(id, payload, createdAt, job, idemKey);
|
|
589
495
|
if (r.id) {
|
|
590
496
|
result.push(id);
|
|
591
497
|
} else if (r.contended) {
|
|
592
498
|
contended++;
|
|
593
499
|
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
try {
|
|
597
|
-
if (this.executeBatchAtOnce && promises.length > 0) {
|
|
598
|
-
await Promise.all(promises);
|
|
599
|
-
}
|
|
600
|
-
await this.onExecuted(tasks);
|
|
601
|
-
if ((!Array.isArray(result) || !(result.length > 0)) && contended > tasks.length >> 1) {
|
|
602
|
-
await this.waitAbortable(15 + Math.floor(Math.random() * 35) + Math.min(250, 15 * contended + Math.floor(Math.random() * 40)));
|
|
603
|
-
}
|
|
604
|
-
} catch (err) {
|
|
605
|
-
}
|
|
606
|
-
return result;
|
|
607
|
-
}
|
|
608
|
-
async executeProcess(id, payload, createdAt, job, key) {
|
|
609
|
-
if (key) {
|
|
610
|
-
return await this.idempotency(id, payload, createdAt, job, key);
|
|
500
|
+
})());
|
|
611
501
|
} else {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
502
|
+
const r = await this.executeProcess(id, payload, createdAt, job, idemKey);
|
|
503
|
+
if (r.id) {
|
|
504
|
+
result.push(id);
|
|
505
|
+
} else if (r.contended) {
|
|
506
|
+
contended++;
|
|
617
507
|
}
|
|
618
508
|
}
|
|
619
|
-
return {};
|
|
620
509
|
}
|
|
621
|
-
|
|
622
|
-
if (
|
|
623
|
-
|
|
510
|
+
try {
|
|
511
|
+
if (this.executeBatchAtOnce && promises.length > 0) {
|
|
512
|
+
await Promise.all(promises);
|
|
624
513
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
const room = Math.min(approveBatchTasksCount, ids.length - i);
|
|
629
|
-
const part = ids.slice(i, i + room);
|
|
630
|
-
const approved = await this.runScript("Approve", [this.stream], [this.group, this.removeOnExecuted ? "1" : "0", ...part], Approve);
|
|
631
|
-
total += Number(approved || 0);
|
|
632
|
-
i += room;
|
|
514
|
+
await this.onExecuted(tasks);
|
|
515
|
+
if ((!Array.isArray(result) || !(result.length > 0)) && contended > tasks.length >> 1) {
|
|
516
|
+
await this.waitAbortable(15 + Math.floor(Math.random() * 35) + Math.min(250, 15 * contended + Math.floor(Math.random() * 40)));
|
|
633
517
|
}
|
|
634
|
-
|
|
518
|
+
} catch (err) {
|
|
635
519
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
let ttl = -2;
|
|
643
|
-
try {
|
|
644
|
-
ttl = await this.redis.pttl(keys.startKey);
|
|
645
|
-
} catch (err) {
|
|
646
|
-
}
|
|
647
|
-
await this.waitAbortable(ttl);
|
|
648
|
-
return { contended: true };
|
|
649
|
-
}
|
|
650
|
-
if (!await this.idempotencyStart(keys)) {
|
|
651
|
-
return { contended: true };
|
|
652
|
-
}
|
|
653
|
-
const heartbeat = this.heartbeat(keys) || (() => {
|
|
654
|
-
});
|
|
520
|
+
return result;
|
|
521
|
+
}
|
|
522
|
+
async executeProcess(id, payload, createdAt, job, key) {
|
|
523
|
+
if (key) {
|
|
524
|
+
return await this.idempotency(id, payload, createdAt, job, key);
|
|
525
|
+
} else {
|
|
655
526
|
try {
|
|
656
527
|
await this.onExecute(id, payload, createdAt, job, key);
|
|
657
|
-
await this.idempotencyDone(keys);
|
|
658
528
|
await this.success(id, payload, createdAt, job, key);
|
|
659
529
|
return { id };
|
|
660
530
|
} catch (err) {
|
|
661
|
-
try {
|
|
662
|
-
await this.idempotencyFree(keys);
|
|
663
|
-
} catch (err2) {
|
|
664
|
-
}
|
|
665
|
-
} finally {
|
|
666
|
-
heartbeat();
|
|
667
531
|
}
|
|
668
532
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
533
|
+
return {};
|
|
534
|
+
}
|
|
535
|
+
async approve(ids) {
|
|
536
|
+
if (!Array.isArray(ids) || !(ids.length > 0)) {
|
|
537
|
+
return 0;
|
|
538
|
+
}
|
|
539
|
+
const approveBatchTasksCount = Math.max(500, Math.min(4e3, this.approveBatchTasksCount));
|
|
540
|
+
let total = 0, i = 0;
|
|
541
|
+
while (i < ids.length) {
|
|
542
|
+
const room = Math.min(approveBatchTasksCount, ids.length - i);
|
|
543
|
+
const part = ids.slice(i, i + room);
|
|
544
|
+
const approved = await this.runScript("Approve", [this.stream], [this.group, this.removeOnExecuted ? "1" : "0", ...part], Approve);
|
|
545
|
+
total += Number(approved || 0);
|
|
546
|
+
i += room;
|
|
547
|
+
}
|
|
548
|
+
return total;
|
|
549
|
+
}
|
|
550
|
+
async idempotency(id, payload, createdAt, job, key) {
|
|
551
|
+
const keys = this.idempotencyKeys(key);
|
|
552
|
+
const allow = await this.idempotencyAllow(keys);
|
|
553
|
+
if (allow === 1) {
|
|
554
|
+
return { id };
|
|
555
|
+
} else if (allow === 0) {
|
|
556
|
+
let ttl = -2;
|
|
557
|
+
try {
|
|
558
|
+
ttl = await this.redis.pttl(keys.startKey);
|
|
559
|
+
} catch (err) {
|
|
560
|
+
}
|
|
561
|
+
await this.waitAbortable(ttl);
|
|
562
|
+
return { contended: true };
|
|
563
|
+
}
|
|
564
|
+
if (!await this.idempotencyStart(keys)) {
|
|
565
|
+
return { contended: true };
|
|
566
|
+
}
|
|
567
|
+
const heartbeat = this.heartbeat(keys) || (() => {
|
|
568
|
+
});
|
|
569
|
+
try {
|
|
570
|
+
await this.onExecute(id, payload, createdAt, job, key);
|
|
571
|
+
await this.idempotencyDone(keys);
|
|
572
|
+
await this.success(id, payload, createdAt, job, key);
|
|
573
|
+
return { id };
|
|
574
|
+
} catch (err) {
|
|
575
|
+
try {
|
|
576
|
+
await this.idempotencyFree(keys);
|
|
577
|
+
} catch (err2) {
|
|
578
|
+
}
|
|
579
|
+
} finally {
|
|
580
|
+
heartbeat();
|
|
683
581
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
582
|
+
}
|
|
583
|
+
idempotencyKeys(key) {
|
|
584
|
+
const prefix = `q:${this.stream.replace(/[^\w:\-]/g, "_")}:`;
|
|
585
|
+
const keyP = key.replace(/[^\w:\-]/g, "_");
|
|
586
|
+
const doneKey = `${prefix}done:${keyP}`;
|
|
587
|
+
const lockKey = `${prefix}lock:${keyP}`;
|
|
588
|
+
const startKey = `${prefix}start:${keyP}`;
|
|
589
|
+
const token = `${this.consumer()}:${Date.now().toString(36)}:${Math.random().toString(36).slice(2)}`;
|
|
590
|
+
return {
|
|
591
|
+
prefix,
|
|
592
|
+
doneKey,
|
|
593
|
+
lockKey,
|
|
594
|
+
startKey,
|
|
595
|
+
token
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
async idempotencyAllow(keys) {
|
|
599
|
+
const res = await this.runScript("IdempotencyAllow", [keys.doneKey, keys.lockKey, keys.startKey], [String(this.workerExecuteLockTimeoutMs), keys.token], IdempotencyAllow);
|
|
600
|
+
return Number(res || 0);
|
|
601
|
+
}
|
|
602
|
+
async idempotencyStart(keys) {
|
|
603
|
+
const res = await this.runScript("IdempotencyStart", [keys.lockKey, keys.startKey], [keys.token, String(this.workerExecuteLockTimeoutMs)], IdempotencyStart);
|
|
604
|
+
return Number(res || 0) === 1;
|
|
605
|
+
}
|
|
606
|
+
async idempotencyDone(keys) {
|
|
607
|
+
await this.runScript("IdempotencyDone", [keys.doneKey, keys.lockKey, keys.startKey], [String(this.workerCacheTaskTimeoutMs), keys.token], IdempotencyDone);
|
|
608
|
+
}
|
|
609
|
+
async idempotencyFree(keys) {
|
|
610
|
+
await this.runScript("IdempotencyFree", [keys.lockKey, keys.startKey], [keys.token], IdempotencyFree);
|
|
611
|
+
}
|
|
612
|
+
async createGroup(from = "$") {
|
|
613
|
+
try {
|
|
614
|
+
await this.redis.xgroup("CREATE", this.stream, this.group, from, "MKSTREAM");
|
|
615
|
+
} catch (err) {
|
|
616
|
+
const msg = String(err?.message || "");
|
|
617
|
+
if (!msg.includes("BUSYGROUP")) {
|
|
618
|
+
throw err;
|
|
619
|
+
}
|
|
687
620
|
}
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
621
|
+
}
|
|
622
|
+
async select() {
|
|
623
|
+
let entries = await this.selectStuck();
|
|
624
|
+
if (!entries?.length) {
|
|
625
|
+
entries = await this.selectFresh();
|
|
691
626
|
}
|
|
692
|
-
|
|
693
|
-
|
|
627
|
+
return this.normalizeEntries(entries);
|
|
628
|
+
}
|
|
629
|
+
async selectStuck() {
|
|
630
|
+
try {
|
|
631
|
+
const res = await this.runScript("SelectStuck", [this.stream], [this.group, this.consumer(), String(this.recoveryStuckTasksTimeoutMs), String(this.workerBatchTasksCount), String(this.workerSelectionTimeoutMs)], SelectStuck);
|
|
632
|
+
return Array.isArray(res) ? res : [];
|
|
633
|
+
} catch (err) {
|
|
634
|
+
if (String(err?.message || "").includes("NOGROUP")) {
|
|
635
|
+
await this.createGroup();
|
|
636
|
+
}
|
|
694
637
|
}
|
|
695
|
-
|
|
696
|
-
|
|
638
|
+
return [];
|
|
639
|
+
}
|
|
640
|
+
async selectFresh() {
|
|
641
|
+
let entries = [];
|
|
642
|
+
try {
|
|
643
|
+
const res = await this.redis.xreadgroup(
|
|
644
|
+
"GROUP",
|
|
645
|
+
this.group,
|
|
646
|
+
this.consumer(),
|
|
647
|
+
"BLOCK",
|
|
648
|
+
Math.max(2, this.workerLoopIntervalMs | 0),
|
|
649
|
+
"COUNT",
|
|
650
|
+
this.workerBatchTasksCount,
|
|
651
|
+
"STREAMS",
|
|
652
|
+
this.stream,
|
|
653
|
+
">"
|
|
654
|
+
);
|
|
655
|
+
if (!res?.[0]?.[1]?.length) {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
entries = res?.[0]?.[1] ?? [];
|
|
659
|
+
if (!entries?.length) {
|
|
660
|
+
return [];
|
|
661
|
+
}
|
|
662
|
+
} catch (err) {
|
|
663
|
+
if (String(err?.message || "").includes("NOGROUP")) {
|
|
664
|
+
await this.createGroup();
|
|
665
|
+
}
|
|
697
666
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
}, ttl > 0 ? 25 + Math.floor(Math.random() * 50) : 5 + Math.floor(Math.random() * 15));
|
|
710
|
-
t.unref?.();
|
|
711
|
-
function onAbort() {
|
|
712
|
-
clearTimeout(t);
|
|
713
|
-
resolve();
|
|
667
|
+
return entries;
|
|
668
|
+
}
|
|
669
|
+
async waitAbortable(ttl) {
|
|
670
|
+
return new Promise((resolve) => {
|
|
671
|
+
const signal = this.signal();
|
|
672
|
+
if (signal?.aborted) {
|
|
673
|
+
return resolve();
|
|
674
|
+
}
|
|
675
|
+
const t = setTimeout(() => {
|
|
676
|
+
if (signal) {
|
|
677
|
+
signal.removeEventListener("abort", onAbort);
|
|
714
678
|
}
|
|
715
|
-
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
679
|
+
resolve();
|
|
680
|
+
}, ttl > 0 ? 25 + Math.floor(Math.random() * 50) : 5 + Math.floor(Math.random() * 15));
|
|
681
|
+
t.unref?.();
|
|
682
|
+
function onAbort() {
|
|
683
|
+
clearTimeout(t);
|
|
684
|
+
resolve();
|
|
685
|
+
}
|
|
686
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
heartbeat(keys) {
|
|
690
|
+
if (this.workerExecuteLockTimeoutMs <= 0) {
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
let timer, alive = true, hbFails = 0;
|
|
694
|
+
const workerHeartbeatTimeoutMs = Math.max(1e3, Math.floor(Math.max(5e3, this.workerExecuteLockTimeoutMs | 0) / 4));
|
|
695
|
+
const stop = () => {
|
|
696
|
+
alive = false;
|
|
697
|
+
if (timer) {
|
|
698
|
+
clearTimeout(timer);
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const onAbort = () => stop();
|
|
702
|
+
const signal = this.signal();
|
|
703
|
+
signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
704
|
+
const tick = async () => {
|
|
705
|
+
if (!alive) {
|
|
720
706
|
return;
|
|
721
707
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
clearTimeout(timer);
|
|
708
|
+
try {
|
|
709
|
+
const r = await this.heartbeat(keys);
|
|
710
|
+
hbFails = r ? 0 : hbFails + 1;
|
|
711
|
+
if (hbFails >= 3) {
|
|
712
|
+
throw new Error("Heartbeat lost.");
|
|
728
713
|
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const tick = async () => {
|
|
734
|
-
if (!alive) {
|
|
714
|
+
} catch {
|
|
715
|
+
hbFails++;
|
|
716
|
+
if (hbFails >= 6) {
|
|
717
|
+
stop();
|
|
735
718
|
return;
|
|
736
719
|
}
|
|
737
|
-
try {
|
|
738
|
-
const r = await this.heartbeat(keys);
|
|
739
|
-
hbFails = r ? 0 : hbFails + 1;
|
|
740
|
-
if (hbFails >= 3) {
|
|
741
|
-
throw new Error("Heartbeat lost.");
|
|
742
|
-
}
|
|
743
|
-
} catch {
|
|
744
|
-
hbFails++;
|
|
745
|
-
if (hbFails >= 6) {
|
|
746
|
-
stop();
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
751
|
-
};
|
|
752
|
-
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
753
|
-
return () => {
|
|
754
|
-
signal?.removeEventListener?.("abort", onAbort);
|
|
755
|
-
stop();
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
values(value) {
|
|
759
|
-
const result = {};
|
|
760
|
-
for (let i = 0; i < value.length; i += 2) {
|
|
761
|
-
result[value[i]] = value[i + 1];
|
|
762
720
|
}
|
|
763
|
-
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
return
|
|
721
|
+
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
722
|
+
};
|
|
723
|
+
timer = setTimeout(tick, workerHeartbeatTimeoutMs).unref?.();
|
|
724
|
+
return () => {
|
|
725
|
+
signal?.removeEventListener?.("abort", onAbort);
|
|
726
|
+
stop();
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
normalizeEntries(raw) {
|
|
730
|
+
if (!Array.isArray(raw)) {
|
|
731
|
+
return [];
|
|
774
732
|
}
|
|
775
|
-
|
|
776
|
-
|
|
733
|
+
return Array.from(raw || []).map((e) => {
|
|
734
|
+
const id = Buffer.isBuffer(e?.[0]) ? e[0].toString() : e?.[0];
|
|
735
|
+
const kvRaw = e?.[1] ?? [];
|
|
736
|
+
const kv = Array.isArray(kvRaw) ? kvRaw.map((x) => Buffer.isBuffer(x) ? x.toString() : x) : [];
|
|
737
|
+
return [id, kv, 0, "", ""];
|
|
738
|
+
}).filter(([id, kv]) => typeof id === "string" && id.length > 0 && Array.isArray(kv) && (kv.length & 1) === 0).map(([id, kv]) => {
|
|
739
|
+
const values = this.values(kv);
|
|
740
|
+
const { idemKey = "", createdAt, job, ...data } = this.payload(values);
|
|
741
|
+
return [id, data, createdAt, job, idemKey];
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
values(value) {
|
|
745
|
+
const result = {};
|
|
746
|
+
for (let i = 0; i < value.length; i += 2) {
|
|
747
|
+
result[value[i]] = value[i + 1];
|
|
777
748
|
}
|
|
778
|
-
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// src/PowerQueues.ts
|
|
782
|
-
var Base = class {
|
|
783
|
-
};
|
|
784
|
-
var PowerQueues = class extends mix(Base, AddTasks, SelectTasks, ExecuteTasks) {
|
|
785
|
-
constructor() {
|
|
786
|
-
super(...arguments);
|
|
787
|
-
this.abort = new AbortController();
|
|
749
|
+
return result;
|
|
788
750
|
}
|
|
789
|
-
|
|
751
|
+
payload(data) {
|
|
752
|
+
try {
|
|
753
|
+
return JSON.parse(data?.payload);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
}
|
|
790
756
|
return data;
|
|
791
757
|
}
|
|
792
|
-
|
|
758
|
+
signal() {
|
|
759
|
+
return this.abort.signal;
|
|
793
760
|
}
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
async onSuccess(id, payload, createdAt, job, key) {
|
|
797
|
-
}
|
|
798
|
-
async runQueue() {
|
|
799
|
-
await this.createGroup("0-0");
|
|
800
|
-
await this.consumerLoop();
|
|
801
|
-
}
|
|
802
|
-
async consumerLoop() {
|
|
803
|
-
const signal = this.signal();
|
|
804
|
-
while (!signal?.aborted) {
|
|
805
|
-
try {
|
|
806
|
-
const tasks = await this.select();
|
|
807
|
-
if (!Array.isArray(tasks) || !(tasks.length > 0)) {
|
|
808
|
-
await wait(600);
|
|
809
|
-
continue;
|
|
810
|
-
}
|
|
811
|
-
const tasksP = await this.onSelected(tasks);
|
|
812
|
-
const ids = await this.execute(Array.isArray(tasksP) && tasksP.length > 0 ? tasksP : tasks);
|
|
813
|
-
if (Array.isArray(ids) && ids.length > 0) {
|
|
814
|
-
await this.approve(ids);
|
|
815
|
-
}
|
|
816
|
-
} catch (err) {
|
|
817
|
-
await wait(600);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
761
|
+
consumer() {
|
|
762
|
+
return `${String(this.consumerHost || "host")}:${process.pid}`;
|
|
820
763
|
}
|
|
821
764
|
};
|
|
822
765
|
export {
|