groupmq-plus 1.1.1 → 1.1.2

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 CHANGED
@@ -240,6 +240,7 @@ var Job = class Job {
240
240
  this.timestamp = args.timestamp;
241
241
  this.orderMs = args.orderMs;
242
242
  this.status = args.status ?? "unknown";
243
+ this.parentId = args.parentId;
243
244
  }
244
245
  async getState() {
245
246
  return this.status ?? "unknown";
@@ -288,6 +289,39 @@ var Job = class Job {
288
289
  async waitUntilFinished(timeoutMs = 0) {
289
290
  return this.queue.waitUntilFinished(this.id, timeoutMs);
290
291
  }
292
+ /**
293
+ * Get all child jobs of this job (if it's a parent in a flow).
294
+ * @returns Array of child Job instances
295
+ */
296
+ async getChildren() {
297
+ return this.queue.getFlowChildren(this.id);
298
+ }
299
+ /**
300
+ * Get the return values of all child jobs in a flow.
301
+ * @returns Object mapping child job IDs to their return values
302
+ */
303
+ async getChildrenValues() {
304
+ return this.queue.getFlowResults(this.id);
305
+ }
306
+ /**
307
+ * Get the number of remaining child jobs that haven't completed yet.
308
+ * @returns Number of remaining dependencies, or null if not a parent job
309
+ */
310
+ async getDependenciesCount() {
311
+ return this.queue.getFlowDependencies(this.id);
312
+ }
313
+ /**
314
+ * Get the parent job of this job (if it's a child in a flow).
315
+ * @returns Parent Job instance, or undefined if no parent or parent was deleted
316
+ */
317
+ async getParent() {
318
+ if (!this.parentId) return void 0;
319
+ try {
320
+ return await this.queue.getJob(this.parentId);
321
+ } catch (_e) {
322
+ return;
323
+ }
324
+ }
291
325
  static fromReserved(queue, reserved, meta) {
292
326
  return new Job({
293
327
  queue,
@@ -327,6 +361,7 @@ var Job = class Job {
327
361
  const failedReason = (raw.failedReason ?? raw.lastErrorMessage) || void 0;
328
362
  const stacktrace = (raw.stacktrace ?? raw.lastErrorStack) || void 0;
329
363
  const returnvalue = raw.returnvalue ? safeJsonParse$1(raw.returnvalue) : void 0;
364
+ const parentId = raw.parentId || void 0;
330
365
  return new Job({
331
366
  queue,
332
367
  id,
@@ -345,7 +380,8 @@ var Job = class Job {
345
380
  returnvalue,
346
381
  timestamp: timestampMs || Date.now(),
347
382
  orderMs,
348
- status: knownStatus ?? coerceStatus(raw.status)
383
+ status: knownStatus ?? coerceStatus(raw.status),
384
+ parentId
349
385
  });
350
386
  }
351
387
  static async fromStore(queue, id) {
@@ -364,6 +400,7 @@ var Job = class Job {
364
400
  const failedReason = (raw.failedReason ?? raw.lastErrorMessage) || void 0;
365
401
  const stacktrace = (raw.stacktrace ?? raw.lastErrorStack) || void 0;
366
402
  const returnvalue = raw.returnvalue ? safeJsonParse$1(raw.returnvalue) : void 0;
403
+ const parentId = raw.parentId || void 0;
367
404
  const [inProcessing, inDelayed] = await Promise.all([queue.redis.zscore(`${queue.namespace}:processing`, id), queue.redis.zscore(`${queue.namespace}:delayed`, id)]);
368
405
  let status = raw.status;
369
406
  if (inProcessing !== null) status = "active";
@@ -389,7 +426,8 @@ var Job = class Job {
389
426
  returnvalue,
390
427
  timestamp: timestampMs || Date.now(),
391
428
  orderMs,
392
- status: coerceStatus(status)
429
+ status: coerceStatus(status),
430
+ parentId
393
431
  });
394
432
  }
395
433
  };
@@ -637,6 +675,38 @@ var Queue = class {
637
675
  }
638
676
  return parsed;
639
677
  }
678
+ /**
679
+ * Gets all child job IDs for a parent job in a flow.
680
+ * @param parentId The ID of the parent job
681
+ * @returns An array of child job IDs
682
+ */
683
+ async getFlowChildrenIds(parentId) {
684
+ return this.r.smembers(`${this.ns}:flow:children:${parentId}`);
685
+ }
686
+ /**
687
+ * Gets all child jobs for a parent job in a flow.
688
+ * @param parentId The ID of the parent job
689
+ * @returns An array of Job instances for all children
690
+ */
691
+ async getFlowChildren(parentId) {
692
+ const ids = await this.getFlowChildrenIds(parentId);
693
+ if (ids.length === 0) return [];
694
+ const pipe = this.r.multi();
695
+ for (const id of ids) pipe.hgetall(`${this.ns}:job:${id}`);
696
+ const rows = await pipe.exec();
697
+ const jobs = [];
698
+ for (let i = 0; i < ids.length; i++) {
699
+ const id = ids[i];
700
+ const raw = rows?.[i]?.[1] || {};
701
+ if (!raw || Object.keys(raw).length === 0) {
702
+ this.logger.warn(`Skipping child job ${id} - not found (likely cleaned up)`);
703
+ continue;
704
+ }
705
+ const job = Job.fromRawHash(this, id, raw);
706
+ jobs.push(job);
707
+ }
708
+ return jobs;
709
+ }
640
710
  async addSingle(opts) {
641
711
  const now = Date.now();
642
712
  let delayUntil = 0;
@@ -2520,7 +2590,10 @@ var _Worker = class extends TypedEventEmitter {
2520
2590
  const oldest = Array.from(this.jobsInProgress)[0];
2521
2591
  const now = Date.now();
2522
2592
  return {
2523
- job: oldest.job,
2593
+ job: Job.fromReserved(this.q, oldest.job, {
2594
+ processedOn: oldest.ts,
2595
+ status: "active"
2596
+ }),
2524
2597
  processingTimeMs: now - oldest.ts
2525
2598
  };
2526
2599
  }
@@ -2530,7 +2603,10 @@ var _Worker = class extends TypedEventEmitter {
2530
2603
  getCurrentJobs() {
2531
2604
  const now = Date.now();
2532
2605
  return Array.from(this.jobsInProgress).map((item) => ({
2533
- job: item.job,
2606
+ job: Job.fromReserved(this.q, item.job, {
2607
+ processedOn: item.ts,
2608
+ status: "active"
2609
+ }),
2534
2610
  processingTimeMs: now - item.ts
2535
2611
  }));
2536
2612
  }
@@ -2560,7 +2636,7 @@ var _Worker = class extends TypedEventEmitter {
2560
2636
  } catch (e) {
2561
2637
  const isConnErr = this.q.isConnectionError(e);
2562
2638
  if (!isConnErr || !this.stopping) this.logger.error(`Heartbeat error for job ${job.id}:`, e instanceof Error ? e.message : String(e));
2563
- this.onError?.(e, job);
2639
+ this.onError?.(e, Job.fromReserved(this.q, job, { status: "active" }));
2564
2640
  if (!isConnErr || !this.stopping) this.emit("error", e instanceof Error ? e : new Error(String(e)));
2565
2641
  }
2566
2642
  }, minInterval);
@@ -2571,7 +2647,11 @@ var _Worker = class extends TypedEventEmitter {
2571
2647
  heartbeatDelayTimer = setTimeout(() => {
2572
2648
  startHeartbeat();
2573
2649
  }, heartbeatThreshold);
2574
- const handlerResult = await this.handler(job);
2650
+ const jobInstance = Job.fromReserved(this.q, job, {
2651
+ processedOn: jobStartWallTime,
2652
+ status: "active"
2653
+ });
2654
+ const handlerResult = await this.handler(jobInstance);
2575
2655
  if (heartbeatDelayTimer) clearTimeout(heartbeatDelayTimer);
2576
2656
  if (hbTimer) clearInterval(hbTimer);
2577
2657
  const finishedAtWall = Date.now();
@@ -2595,7 +2675,11 @@ var _Worker = class extends TypedEventEmitter {
2595
2675
  * Handle job failure: emit events, retry or dead-letter
2596
2676
  */
2597
2677
  async handleJobFailure(err, job, jobStartWallTime) {
2598
- this.onError?.(err, job);
2678
+ const jobInstance = Job.fromReserved(this.q, job, {
2679
+ processedOn: jobStartWallTime,
2680
+ status: "active"
2681
+ });
2682
+ this.onError?.(err, jobInstance);
2599
2683
  this.blockingStats.consecutiveEmptyReserves = 0;
2600
2684
  this.emptyReserveBackoffMs = 0;
2601
2685
  try {