bunqueue 2.8.48 → 2.8.50

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.
Files changed (129) hide show
  1. package/README.md +19 -9
  2. package/dist/application/contextFactory.d.ts +4 -2
  3. package/dist/application/contextFactory.js +3 -0
  4. package/dist/application/flowFailureRecovery.d.ts +17 -0
  5. package/dist/application/flowFailureRecovery.js +92 -0
  6. package/dist/application/operations/ack.d.ts +3 -2
  7. package/dist/application/operations/ack.js +34 -8
  8. package/dist/application/operations/flowPush.d.ts +9 -0
  9. package/dist/application/operations/flowPush.js +112 -0
  10. package/dist/application/operations/flowTopologyValidation.d.ts +3 -0
  11. package/dist/application/operations/flowTopologyValidation.js +101 -0
  12. package/dist/application/operations/flowValidation.d.ts +3 -0
  13. package/dist/application/operations/flowValidation.js +166 -0
  14. package/dist/application/operations/push.d.ts +3 -0
  15. package/dist/application/operations/push.js +2 -2
  16. package/dist/application/operations/pushInsert.d.ts +7 -1
  17. package/dist/application/operations/pushInsert.js +18 -8
  18. package/dist/application/operations/pushLocks.d.ts +1 -0
  19. package/dist/application/operations/pushLocks.js +5 -1
  20. package/dist/application/operations/queryOperations.js +7 -6
  21. package/dist/application/queueManager.d.ts +5 -0
  22. package/dist/application/queueManager.js +195 -60
  23. package/dist/cli/commandRouter.d.ts +1 -1
  24. package/dist/client/flow.d.ts +3 -6
  25. package/dist/client/flow.js +71 -210
  26. package/dist/client/flowAtomic.d.ts +9 -0
  27. package/dist/client/flowAtomic.js +24 -0
  28. package/dist/client/flowJobCoreMethods.d.ts +28 -0
  29. package/dist/client/flowJobCoreMethods.js +151 -0
  30. package/dist/client/flowJobDependencies.d.ts +7 -0
  31. package/dist/client/flowJobDependencies.js +64 -0
  32. package/dist/client/flowJobFactory.d.ts +11 -24
  33. package/dist/client/flowJobFactory.js +91 -360
  34. package/dist/client/flowJobMoveMethods.d.ts +16 -0
  35. package/dist/client/flowJobMoveMethods.js +112 -0
  36. package/dist/client/flowJobTypes.d.ts +29 -0
  37. package/dist/client/flowJobTypes.js +6 -0
  38. package/dist/client/flowLegacyPlan.d.ts +16 -0
  39. package/dist/client/flowLegacyPlan.js +118 -0
  40. package/dist/client/flowOptions.d.ts +10 -0
  41. package/dist/client/flowOptions.js +44 -0
  42. package/dist/client/flowPlan.d.ts +16 -0
  43. package/dist/client/flowPlan.js +93 -0
  44. package/dist/client/flowPush.js +4 -1
  45. package/dist/client/flowReader.d.ts +10 -0
  46. package/dist/client/flowReader.js +147 -0
  47. package/dist/client/jobHelpers.js +2 -0
  48. package/dist/client/workflow/clock.d.ts +3 -1
  49. package/dist/client/workflow/clock.js +18 -1
  50. package/dist/client/workflow/compensationChild.d.ts +30 -0
  51. package/dist/client/workflow/compensationChild.js +65 -0
  52. package/dist/client/workflow/compensationPass.d.ts +16 -0
  53. package/dist/client/workflow/compensationPass.js +96 -0
  54. package/dist/client/workflow/compensationSupport.d.ts +24 -0
  55. package/dist/client/workflow/compensationSupport.js +57 -0
  56. package/dist/client/workflow/compensator.d.ts +11 -50
  57. package/dist/client/workflow/compensator.js +22 -374
  58. package/dist/client/workflow/definitionGuard.d.ts +11 -0
  59. package/dist/client/workflow/definitionGuard.js +22 -0
  60. package/dist/client/workflow/engine.d.ts +2 -2
  61. package/dist/client/workflow/engine.js +8 -8
  62. package/dist/client/workflow/eventTypes.d.ts +44 -0
  63. package/dist/client/workflow/eventTypes.js +2 -0
  64. package/dist/client/workflow/executionTypes.d.ts +133 -0
  65. package/dist/client/workflow/executionTypes.js +2 -0
  66. package/dist/client/workflow/executor.d.ts +4 -17
  67. package/dist/client/workflow/executor.js +50 -208
  68. package/dist/client/workflow/executorLifecycle.d.ts +18 -0
  69. package/dist/client/workflow/executorLifecycle.js +69 -0
  70. package/dist/client/workflow/executorNodes.d.ts +17 -0
  71. package/dist/client/workflow/executorNodes.js +138 -0
  72. package/dist/client/workflow/identity.d.ts +2 -0
  73. package/dist/client/workflow/identity.js +8 -0
  74. package/dist/client/workflow/index.d.ts +1 -1
  75. package/dist/client/workflow/loops.d.ts +3 -5
  76. package/dist/client/workflow/loops.js +25 -23
  77. package/dist/client/workflow/mapRunner.d.ts +4 -0
  78. package/dist/client/workflow/mapRunner.js +45 -0
  79. package/dist/client/workflow/recovery.js +10 -3
  80. package/dist/client/workflow/runner.d.ts +2 -21
  81. package/dist/client/workflow/runner.js +39 -104
  82. package/dist/client/workflow/runnerTiming.d.ts +14 -0
  83. package/dist/client/workflow/runnerTiming.js +66 -0
  84. package/dist/client/workflow/stepTypes.d.ts +171 -0
  85. package/dist/client/workflow/stepTypes.js +3 -0
  86. package/dist/client/workflow/store.d.ts +17 -17
  87. package/dist/client/workflow/store.js +65 -100
  88. package/dist/client/workflow/storeExecutionCodec.d.ts +11 -0
  89. package/dist/client/workflow/storeExecutionCodec.js +34 -0
  90. package/dist/client/workflow/storeListing.d.ts +11 -0
  91. package/dist/client/workflow/storeListing.js +45 -0
  92. package/dist/client/workflow/storeMaintenance.d.ts +4 -0
  93. package/dist/client/workflow/storeMaintenance.js +40 -0
  94. package/dist/client/workflow/storeSignals.d.ts +9 -0
  95. package/dist/client/workflow/storeSignals.js +38 -2
  96. package/dist/client/workflow/subWorkflowRunner.d.ts +13 -0
  97. package/dist/client/workflow/subWorkflowRunner.js +40 -0
  98. package/dist/client/workflow/types.d.ts +4 -356
  99. package/dist/client/workflow/types.js +1 -3
  100. package/dist/client/workflow/waitFor.js +39 -26
  101. package/dist/client/workflow/workflow.d.ts +16 -59
  102. package/dist/client/workflow/workflow.js +53 -179
  103. package/dist/client/workflow/workflowDecisions.d.ts +11 -0
  104. package/dist/client/workflow/workflowDecisions.js +27 -0
  105. package/dist/client/workflow/workflowDefinition.d.ts +16 -0
  106. package/dist/client/workflow/workflowDefinition.js +123 -0
  107. package/dist/client/workflow/workflowIntrospection.d.ts +5 -0
  108. package/dist/client/workflow/workflowIntrospection.js +46 -0
  109. package/dist/client/workflow/workflowValidation.d.ts +44 -0
  110. package/dist/client/workflow/workflowValidation.js +143 -0
  111. package/dist/domain/types/command.d.ts +7 -1
  112. package/dist/domain/types/flow.d.ts +25 -0
  113. package/dist/domain/types/flow.js +1 -0
  114. package/dist/infrastructure/persistence/schema.d.ts +2 -2
  115. package/dist/infrastructure/persistence/schema.js +52 -3
  116. package/dist/infrastructure/persistence/sqlite.d.ts +16 -0
  117. package/dist/infrastructure/persistence/sqlite.js +129 -7
  118. package/dist/infrastructure/persistence/sqliteBatch.js +10 -4
  119. package/dist/infrastructure/persistence/sqliteSerializer.d.ts +2 -0
  120. package/dist/infrastructure/persistence/sqliteSerializer.js +18 -5
  121. package/dist/infrastructure/persistence/statements.d.ts +4 -0
  122. package/dist/infrastructure/persistence/statements.js +8 -2
  123. package/dist/infrastructure/server/handlerRoutes.js +3 -0
  124. package/dist/infrastructure/server/handlers/advanced.js +2 -2
  125. package/dist/infrastructure/server/handlers/flow.d.ts +7 -0
  126. package/dist/infrastructure/server/handlers/flow.js +11 -0
  127. package/dist/infrastructure/server/handlers/index.d.ts +1 -0
  128. package/dist/infrastructure/server/handlers/index.js +1 -0
  129. package/package.json +4 -2
@@ -13,8 +13,9 @@ import { WorkerManager } from './workerManager';
13
13
  import { EventsManager } from './eventsManager';
14
14
  import { createMonitoringState } from './monitoringChecks';
15
15
  import { RWLock, withWriteLock } from '../shared/lock';
16
- import { shardIndex, SHARD_COUNT } from '../shared/hash';
16
+ import { processingShardIndex, shardIndex, SHARD_COUNT } from '../shared/hash';
17
17
  import { pushJob, pushJobBatch } from './operations/push';
18
+ import { pushFlowBatch } from './operations/flowPush';
18
19
  import { pullJob, pullJobBatch } from './operations/pull';
19
20
  import { ackJob, ackJobBatch, ackJobBatchWithResults, failJob } from './operations/ack';
20
21
  import * as queueControl from './operations/queueControl';
@@ -35,6 +36,7 @@ import { ContextFactory } from './contextFactory';
35
36
  import { processPendingDependencies } from './dependencyProcessor';
36
37
  import { handleTaskError, handleTaskSuccess } from './taskErrorTracking';
37
38
  import { DependencyResultTracker } from './dependencyResultTracker';
39
+ import { recoverFlowFailures } from './flowFailureRecovery';
38
40
  /**
39
41
  * QueueManager - Central coordinator
40
42
  */
@@ -44,6 +46,7 @@ export class QueueManager {
44
46
  // Sharded data structures
45
47
  shards = [];
46
48
  shardLocks = [];
49
+ customIdLock = new RWLock();
47
50
  processingShards = [];
48
51
  processingLocks = [];
49
52
  // Global indexes (bounded with LRU eviction)
@@ -162,6 +165,17 @@ export class QueueManager {
162
165
  this.contextFactory = new ContextFactory(this.getContextDependencies(), this.getContextCallbacks());
163
166
  // Load and start
164
167
  bgTasks.recover(this.contextFactory.getBackgroundContext());
168
+ if (this.storage) {
169
+ recoverFlowFailures({
170
+ storage: this.storage,
171
+ shards: this.shards,
172
+ jobIndex: this.jobIndex,
173
+ completedJobs: this.completedJobs,
174
+ dependencyResults: this.dependencyResults,
175
+ failedChildrenValues: this.failedChildrenValues,
176
+ ignoredChildrenFailures: this.ignoredChildrenFailures,
177
+ });
178
+ }
165
179
  this.recoveryStats = { queues: this.queueNamesCache.size, jobs: this.jobIndex.size };
166
180
  if (this.storage) {
167
181
  this.cronScheduler.load(this.storage.loadCronJobs());
@@ -175,6 +189,7 @@ export class QueueManager {
175
189
  storage: this.storage,
176
190
  shards: this.shards,
177
191
  shardLocks: this.shardLocks,
192
+ customIdLock: this.customIdLock,
178
193
  processingShards: this.processingShards,
179
194
  processingLocks: this.processingLocks,
180
195
  jobIndex: this.jobIndex,
@@ -208,6 +223,7 @@ export class QueueManager {
208
223
  registerQueueName: this.registerQueueName.bind(this),
209
224
  unregisterQueueName: this.unregisterQueueName.bind(this),
210
225
  onJobCompleted: this.onJobCompleted.bind(this),
226
+ onJobFailed: this.onJobFailed.bind(this),
211
227
  onJobsCompleted: this.onJobsCompleted.bind(this),
212
228
  hasPendingDeps: this.hasPendingDeps.bind(this),
213
229
  onRepeat: this.handleRepeat.bind(this),
@@ -260,6 +276,9 @@ export class QueueManager {
260
276
  this.registerQueueName(queue);
261
277
  return pushJobBatch(queue, inputs, this.contextFactory.getPushContext());
262
278
  }
279
+ async pushFlow(batch) {
280
+ return pushFlowBatch(batch, this.contextFactory.getPushContext());
281
+ }
263
282
  async pull(queue, timeoutMs = 0, signal) {
264
283
  return pullJob(queue, timeoutMs, this.contextFactory.getPullContext(), signal);
265
284
  }
@@ -665,49 +684,96 @@ export class QueueManager {
665
684
  * Used by FlowProducer when creating flows where children need to reference parent.
666
685
  */
667
686
  async updateJobParent(childJobId, parentJobId) {
687
+ if (childJobId === parentJobId)
688
+ throw new Error('A flow job cannot be its own parent');
668
689
  const childJob = await this.getJob(childJobId);
669
690
  if (!childJob)
670
- return;
691
+ throw new Error(`Child job not found: ${String(childJobId)}`);
671
692
  const parentJob = await this.getJob(parentJobId);
672
- // Update domain parentId field (cast to bypass readonly for flow linkage)
673
- childJob.parentId = parentJobId;
674
- // Update the job's data to include parent reference. __parentQueue must be
675
- // the PARENT's queue it feeds buildParentKey/extractParent/buildParentOpts
676
- // for child→parent navigation (Job.parent, .parentKey, .opts.parent, toJSON).
677
- // Using the child's own queue corrupted cross-queue flows (audit #102 follow-up).
678
- const jobData = childJob.data;
679
- jobData.__parentId = parentJobId;
680
- jobData.__parentQueue = parentJob?.queue ?? jobData.__parentQueue ?? childJob.queue;
681
- // Also add this child to parent's childrenIds
682
- if (parentJob) {
683
- if (!parentJob.childrenIds) {
684
- parentJob.childrenIds = [];
693
+ if (!parentJob)
694
+ throw new Error(`Parent job not found: ${String(parentJobId)}`);
695
+ const priorParent = childJob.parentId;
696
+ if (priorParent && String(priorParent) !== 'pending' && priorParent !== parentJobId) {
697
+ throw new Error(`Child job ${String(childJobId)} already belongs to parent ${String(priorParent)}`);
698
+ }
699
+ const parentLocation = this.jobIndex.get(parentJobId);
700
+ if (parentLocation?.type !== 'queue') {
701
+ throw new Error(`Parent job ${String(parentJobId)} is not linkable`);
702
+ }
703
+ const childLocation = this.jobIndex.get(childJobId);
704
+ const shardIndexes = [
705
+ ...new Set([shardIndex(childJob.queue), shardIndex(parentJob.queue)]),
706
+ ].sort((a, b) => a - b);
707
+ const processingIndexes = childLocation?.type === 'processing' ? [processingShardIndex(childJobId)] : [];
708
+ const guards = [await this.customIdLock.acquireWrite()];
709
+ try {
710
+ for (const index of shardIndexes)
711
+ guards.push(await this.shardLocks[index].acquireWrite());
712
+ for (const index of processingIndexes) {
713
+ guards.push(await this.processingLocks[index].acquireWrite());
685
714
  }
686
- if (!parentJob.childrenIds.includes(childJobId)) {
687
- parentJob.childrenIds.push(childJobId);
715
+ if (this.jobIndex.get(parentJobId)?.type !== 'queue') {
716
+ throw new Error(`Parent job ${String(parentJobId)} changed state while linking`);
688
717
  }
689
- }
690
- // Persist changes to SQLite if storage is available
691
- if (this.storage) {
692
- this.storage.updateJobData(childJobId, childJob.data);
693
- if (parentJob) {
694
- this.storage.updateJobChildrenIds(parentJobId, parentJob.childrenIds);
718
+ const childData = {
719
+ ...childJob.data,
720
+ __parentId: String(parentJobId),
721
+ __parentQueue: parentJob.queue,
722
+ };
723
+ const childrenIds = parentJob.childrenIds.includes(childJobId)
724
+ ? [...parentJob.childrenIds]
725
+ : [...parentJob.childrenIds, childJobId];
726
+ const dependsOn = parentJob.dependsOn.includes(childJobId)
727
+ ? [...parentJob.dependsOn]
728
+ : [...parentJob.dependsOn, childJobId];
729
+ const parentData = {
730
+ ...parentJob.data,
731
+ __childrenIds: childrenIds.map(String),
732
+ };
733
+ const linkedChild = { ...childJob, data: childData, parentId: parentJobId };
734
+ const linkedParent = { ...parentJob, data: parentData, childrenIds, dependsOn };
735
+ const childFinished = this.completedJobs.has(childJobId) || this.depCompletions.has(childJobId);
736
+ const parentState = childFinished
737
+ ? parentJob.runAt > Date.now()
738
+ ? 'delayed'
739
+ : parentJob.priority > 0
740
+ ? 'prioritized'
741
+ : 'waiting'
742
+ : 'waiting-children';
743
+ this.storage?.updateFlowLink(linkedChild, linkedParent, parentState);
744
+ childJob.parentId = parentJobId;
745
+ childJob.data = childData;
746
+ parentJob.childrenIds = childrenIds;
747
+ parentJob.dependsOn = dependsOn;
748
+ parentJob.data = parentData;
749
+ if (!childFinished) {
750
+ const shard = this.shards[shardIndex(parentJob.queue)];
751
+ if (!shard.waitingDeps.has(parentJobId)) {
752
+ const removed = shard.getQueue(parentJob.queue).remove(parentJobId);
753
+ if (removed)
754
+ shard.decrementQueued(parentJobId);
755
+ shard.waitingDeps.set(parentJobId, parentJob);
756
+ }
757
+ shard.registerDependencies(parentJobId, [childJobId]);
758
+ this.dependencyResults.registerConsumer(parentJobId, dependsOn);
695
759
  }
696
760
  }
761
+ finally {
762
+ for (let index = guards.length - 1; index >= 0; index--)
763
+ guards[index].release();
764
+ }
697
765
  // Handle race condition: child may have already terminally failed
698
766
  // before parent linkage was established (parentId was 'pending').
699
767
  // If so, propagate failParentOnFailure now with the real parent ID.
700
768
  const childLoc = this.jobIndex.get(childJobId);
701
769
  if (childLoc?.type === 'dlq' && childJob.failParentOnFailure) {
702
- this.moveParentToFailed(parentJobId, childJob, 'Child job failed').catch(() => {
703
- // Best-effort: parent may already be in DLQ or removed
704
- });
770
+ await this.moveParentToFailed(parentJobId, childJob, 'Child job failed');
705
771
  }
706
772
  if (childLoc?.type === 'dlq' &&
707
773
  (childJob.removeDependencyOnFailure ||
708
774
  childJob.ignoreDependencyOnFailure ||
709
775
  childJob.continueParentOnFailure)) {
710
- this.onChildDependencyOption(childJob, 'Child job failed');
776
+ await this.onChildDependencyOption(childJob, 'Child job failed');
711
777
  }
712
778
  }
713
779
  getJobByCustomId(customId) {
@@ -1221,10 +1287,17 @@ export class QueueManager {
1221
1287
  // leaked an entry permanently.
1222
1288
  this.failedChildrenValues.delete(completedId);
1223
1289
  this.ignoredChildrenFailures.delete(completedId);
1290
+ this.storage?.deleteFlowFailure(completedId);
1224
1291
  this.pendingDepChecks.add(completedId);
1225
1292
  this.scheduleDependencyFlush();
1226
1293
  void this.checkFlowCompleted(completedId);
1227
1294
  }
1295
+ /** Release failure metadata owned by a parent that reached terminal failure. */
1296
+ onJobFailed(failedId) {
1297
+ this.failedChildrenValues.delete(failedId);
1298
+ this.ignoredChildrenFailures.delete(failedId);
1299
+ this.storage?.deleteFlowFailure(failedId);
1300
+ }
1228
1301
  /** Check if completing this job completes an entire flow */
1229
1302
  async checkFlowCompleted(completedId) {
1230
1303
  const job = await this.getJob(completedId);
@@ -1246,13 +1319,11 @@ export class QueueManager {
1246
1319
  * BullMQ v5: failParentOnFailure — when a child terminally fails
1247
1320
  * and has failParentOnFailure: true, also fail the parent job.
1248
1321
  */
1249
- failParentOnChildFailure(childJob, error) {
1322
+ async failParentOnChildFailure(childJob, error) {
1250
1323
  const parentId = childJob.parentId;
1251
1324
  if (!parentId)
1252
1325
  return;
1253
- this.moveParentToFailed(parentId, childJob, error).catch(() => {
1254
- // Best-effort: parent may already be in DLQ or removed
1255
- });
1326
+ await this.moveParentToFailed(parentId, childJob, error);
1256
1327
  }
1257
1328
  /** Move a parent job to DLQ/failed state because a child failed */
1258
1329
  async moveParentToFailed(parentId, childJob, error) {
@@ -1292,6 +1363,7 @@ export class QueueManager {
1292
1363
  this.jobIndex.set(parentId, { type: 'dlq', queueName: parentJob.queue });
1293
1364
  this.storage?.saveDlqEntry(entry);
1294
1365
  this.storage?.deleteJob(parentId);
1366
+ this.storage?.deleteFlowFailure(parentId, childJob.id);
1295
1367
  });
1296
1368
  // Parent reached a terminal (DLQ) state — release its flow-failure tracking.
1297
1369
  this.failedChildrenValues.delete(parentId);
@@ -1316,19 +1388,15 @@ export class QueueManager {
1316
1388
  /**
1317
1389
  * Handle child dependency options: removeDependencyOnFailure, ignoreDependencyOnFailure, continueParentOnFailure
1318
1390
  */
1319
- onChildDependencyOption(childJob, error) {
1391
+ async onChildDependencyOption(childJob, error) {
1320
1392
  if (!childJob.parentId)
1321
1393
  return;
1322
1394
  if (childJob.continueParentOnFailure) {
1323
- this.continueParentOnChildFailure(childJob, error).catch(() => {
1324
- // Best-effort parent propagation; child failure is already durable.
1325
- });
1395
+ await this.continueParentOnChildFailure(childJob, error);
1326
1396
  }
1327
1397
  else {
1328
1398
  // removeDependencyOnFailure or ignoreDependencyOnFailure
1329
- this.removeChildFromParentDeps(childJob, error, childJob.ignoreDependencyOnFailure).catch(() => {
1330
- // Best-effort parent propagation; child failure is already durable.
1331
- });
1399
+ await this.removeChildFromParentDeps(childJob, error, childJob.ignoreDependencyOnFailure);
1332
1400
  }
1333
1401
  }
1334
1402
  /**
@@ -1351,6 +1419,18 @@ export class QueueManager {
1351
1419
  existing[childKey] = error ?? 'unknown error';
1352
1420
  this.failedChildrenValues.set(parentId, existing);
1353
1421
  const idx = shardIndex(parentJob.queue);
1422
+ await withWriteLock(this.shardLocks[idx], () => {
1423
+ if (this.jobIndex.get(parentId)?.type !== 'queue')
1424
+ return;
1425
+ const shard = this.shards[idx];
1426
+ const dependencies = [...parentJob.dependsOn];
1427
+ shard.unregisterDependencies(parentId, dependencies);
1428
+ for (const dependency of dependencies) {
1429
+ this.dependencyResults.releaseDependency(parentId, dependency);
1430
+ }
1431
+ parentJob.dependsOn = [];
1432
+ this.storage?.updateFlowParentResolution(parentJob);
1433
+ });
1354
1434
  await this.promoteParentAfterChildFailure(parentId, parentJob, idx);
1355
1435
  }
1356
1436
  /**
@@ -1387,6 +1467,7 @@ export class QueueManager {
1387
1467
  queue.push(parentJob);
1388
1468
  shard.incrementQueued(parentId, false, parentJob.createdAt, parentJob.queue, now);
1389
1469
  this.jobIndex.set(parentId, { type: 'queue', shardIdx: idx, queueName: parentJob.queue });
1470
+ this.storage?.updateFlowParentResolution(parentJob);
1390
1471
  shard.notify(parentJob.queue);
1391
1472
  promoted = true;
1392
1473
  }
@@ -1439,6 +1520,7 @@ export class QueueManager {
1439
1520
  parentJob.dependsOn.splice(depIndex, 1);
1440
1521
  shard.unregisterDependencies(parentId, [childJob.id]);
1441
1522
  this.dependencyResults.releaseDependency(parentId, childJob.id);
1523
+ this.storage?.updateFlowParentResolution(parentJob);
1442
1524
  }
1443
1525
  // If no more pending deps, the parent is ready to be promoted
1444
1526
  readyToPromote =
@@ -1448,6 +1530,8 @@ export class QueueManager {
1448
1530
  if (readyToPromote) {
1449
1531
  await this.promoteParentAfterChildFailure(parentId, parentJob, idx);
1450
1532
  }
1533
+ if (!storeIgnored)
1534
+ this.storage?.deleteFlowFailure(parentId, childJob.id);
1451
1535
  }
1452
1536
  /**
1453
1537
  * Get failed children values for a parent job (populated by continueParentOnFailure).
@@ -1479,34 +1563,85 @@ export class QueueManager {
1479
1563
  const parentLoc = this.jobIndex.get(parentId);
1480
1564
  if (parentLoc?.type !== 'queue')
1481
1565
  return false;
1482
- const idx = shardIndex(parentJob.queue);
1566
+ const childLoc = this.jobIndex.get(childJobId);
1567
+ if (childLoc?.type !== 'queue' && childLoc?.type !== 'processing')
1568
+ return false;
1569
+ const parentIdx = shardIndex(parentJob.queue);
1570
+ const shardIndexes = [...new Set([parentIdx, shardIndex(childJob.queue)])].sort((a, b) => a - b);
1571
+ const processingIndexes = childLoc.type === 'processing' ? [processingShardIndex(childJobId)] : [];
1572
+ const guards = [];
1573
+ let removed = false;
1483
1574
  let promoted = false;
1484
- await withWriteLock(this.shardLocks[idx], () => {
1485
- if (this.jobIndex.get(parentId)?.type !== 'queue')
1486
- return;
1487
- const shard = this.shards[idx];
1488
- const parentInDeps = shard.waitingDeps.get(parentId);
1489
- if (!parentInDeps)
1490
- return;
1491
- const depIndex = parentJob.dependsOn.indexOf(childJobId);
1492
- if (depIndex !== -1) {
1493
- parentJob.dependsOn.splice(depIndex, 1);
1494
- shard.unregisterDependencies(parentId, [childJobId]);
1495
- this.dependencyResults.releaseDependency(parentId, childJobId);
1575
+ try {
1576
+ for (const index of shardIndexes)
1577
+ guards.push(await this.shardLocks[index].acquireWrite());
1578
+ for (const index of processingIndexes) {
1579
+ guards.push(await this.processingLocks[index].acquireWrite());
1580
+ }
1581
+ if (this.jobIndex.get(parentId)?.type !== 'queue' ||
1582
+ this.jobIndex.get(childJobId)?.type !== childLoc.type) {
1583
+ return false;
1496
1584
  }
1497
- const allDone = parentJob.dependsOn.length === 0 ||
1498
- parentJob.dependsOn.every((dep) => this.completedJobs.has(dep));
1499
- if (allDone) {
1585
+ const shard = this.shards[parentIdx];
1586
+ if (!shard.waitingDeps.has(parentId) || !parentJob.dependsOn.includes(childJobId)) {
1587
+ return false;
1588
+ }
1589
+ const unresolved = parentJob.dependsOn.filter((dependency) => dependency !== childJobId &&
1590
+ !this.completedJobs.has(dependency) &&
1591
+ !this.depCompletions.has(dependency));
1592
+ const released = parentJob.dependsOn.filter((dependency) => !unresolved.includes(dependency));
1593
+ const childrenIds = parentJob.childrenIds.filter((childId) => childId !== childJobId);
1594
+ const childData = { ...childJob.data };
1595
+ delete childData.__parentId;
1596
+ delete childData.__parentQueue;
1597
+ const parentData = { ...parentJob.data };
1598
+ if (childrenIds.length > 0)
1599
+ parentData.__childrenIds = childrenIds.map(String);
1600
+ else
1601
+ delete parentData.__childrenIds;
1602
+ const runAt = unresolved.length === 0 ? Date.now() : parentJob.runAt;
1603
+ const parentState = unresolved.length > 0
1604
+ ? 'waiting-children'
1605
+ : parentJob.priority > 0
1606
+ ? 'prioritized'
1607
+ : 'waiting';
1608
+ const detachedChild = { ...childJob, parentId: null, data: childData };
1609
+ const detachedParent = {
1610
+ ...parentJob,
1611
+ childrenIds,
1612
+ dependsOn: unresolved,
1613
+ data: parentData,
1614
+ runAt,
1615
+ };
1616
+ this.storage?.removeFlowLink(detachedChild, detachedParent, parentState);
1617
+ childJob.parentId = null;
1618
+ childJob.data = childData;
1619
+ parentJob.childrenIds = childrenIds;
1620
+ parentJob.dependsOn = unresolved;
1621
+ parentJob.data = parentData;
1622
+ parentJob.runAt = runAt;
1623
+ shard.unregisterDependencies(parentId, released);
1624
+ for (const dependency of released) {
1625
+ this.dependencyResults.releaseDependency(parentId, dependency);
1626
+ }
1627
+ if (unresolved.length === 0) {
1500
1628
  shard.waitingDeps.delete(parentId);
1501
- const now = Date.now();
1502
- parentJob.runAt = now;
1503
1629
  shard.getQueue(parentJob.queue).push(parentJob);
1504
- shard.incrementQueued(parentId, false, parentJob.createdAt, parentJob.queue, now);
1505
- this.jobIndex.set(parentId, { type: 'queue', shardIdx: idx, queueName: parentJob.queue });
1630
+ shard.incrementQueued(parentId, false, parentJob.createdAt, parentJob.queue, runAt);
1631
+ this.jobIndex.set(parentId, {
1632
+ type: 'queue',
1633
+ shardIdx: parentIdx,
1634
+ queueName: parentJob.queue,
1635
+ });
1506
1636
  shard.notify(parentJob.queue);
1507
1637
  promoted = true;
1508
1638
  }
1509
- });
1639
+ removed = true;
1640
+ }
1641
+ finally {
1642
+ for (let index = guards.length - 1; index >= 0; index--)
1643
+ guards[index].release();
1644
+ }
1510
1645
  if (promoted) {
1511
1646
  this.eventsManager.broadcast({
1512
1647
  eventType: 'waiting',
@@ -1516,7 +1651,7 @@ export class QueueManager {
1516
1651
  prev: 'waiting-children',
1517
1652
  });
1518
1653
  }
1519
- return true;
1654
+ return removed;
1520
1655
  }
1521
1656
  /**
1522
1657
  * Remove all unprocessed (waiting/delayed) children of a parent job.
@@ -1,3 +1,3 @@
1
- export declare const CLI_NETWORK_COMMANDS: readonly ("push" | "queue" | "dlq" | "job" | "cron" | "worker" | "concurrency" | "ping" | "stats" | "metrics" | "pull" | "ack" | "fail" | "rate-limit" | "health" | "webhook")[];
1
+ export declare const CLI_NETWORK_COMMANDS: readonly ("push" | "fail" | "queue" | "dlq" | "job" | "cron" | "worker" | "concurrency" | "ping" | "stats" | "metrics" | "pull" | "ack" | "rate-limit" | "health" | "webhook")[];
2
2
  /** Build the protocol command for a routed network CLI command. */
3
3
  export declare function buildCommand(command: string, args: string[]): Promise<Record<string, unknown> | null>;
@@ -34,6 +34,7 @@ export declare class FlowProducer extends EventEmitter {
34
34
  private readonly embedded;
35
35
  private readonly tcp;
36
36
  private readonly useSharedPool;
37
+ private closed;
37
38
  constructor(opts?: FlowProducerOptions);
38
39
  /** Get push context for helper functions */
39
40
  private get pushCtx();
@@ -70,10 +71,6 @@ export declare class FlowProducer extends EventEmitter {
70
71
  getParentResults<R = unknown>(parentIds: string[]): Map<string, R>;
71
72
  /** Build callbacks that wire flow job methods to actual management operations */
72
73
  private buildCallbacks;
73
- private getFlowEmbedded;
74
- private getFlowTcp;
75
- private buildJobNode;
76
- private buildJobNodeFromTcp;
77
- private addFlowNode;
78
- private addTreeNode;
74
+ private buildPlannedNode;
75
+ private indexSnapshots;
79
76
  }