bunqueue 2.8.49 → 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
@@ -0,0 +1,166 @@
1
+ import { validateFlowTopology } from './flowTopologyValidation';
2
+ const MAX_FLOW_JOBS = 10_000;
3
+ const MAX_JOB_DATA_BYTES = 10 * 1024 * 1024;
4
+ const MAX_FLOW_DATA_BYTES = 64 * 1024 * 1024;
5
+ const QUEUE_RE = /^[a-zA-Z0-9_\-.:]+$/;
6
+ const BOOLEAN_OPTIONS = [
7
+ 'lifo',
8
+ 'removeOnComplete',
9
+ 'removeOnFail',
10
+ 'durable',
11
+ 'failParentOnFailure',
12
+ 'removeDependencyOnFailure',
13
+ 'continueParentOnFailure',
14
+ 'ignoreDependencyOnFailure',
15
+ ];
16
+ function numeric(value, name, min, max, integer = false) {
17
+ if (value === undefined || value === null)
18
+ return;
19
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
20
+ throw new Error(`${name} must be a finite number`);
21
+ }
22
+ if (integer && !Number.isInteger(value))
23
+ throw new Error(`${name} must be an integer`);
24
+ if (value < min || value > max) {
25
+ throw new Error(`${name} must be between ${min} and ${max}`);
26
+ }
27
+ }
28
+ function validateOptions(input) {
29
+ numeric(input.priority, 'priority', -1_000_000, 1_000_000, true);
30
+ numeric(input.delay, 'delay', 0, 365 * 24 * 60 * 60 * 1_000);
31
+ numeric(input.timeout, 'timeout', 0, 24 * 60 * 60 * 1_000);
32
+ numeric(input.ttl, 'ttl', 0, 365 * 24 * 60 * 60 * 1_000);
33
+ numeric(input.maxAttempts, 'attempts', 1, 1_000, true);
34
+ numeric(input.stallTimeout, 'stallTimeout', 0, 24 * 60 * 60 * 1_000);
35
+ numeric(input.stackTraceLimit, 'stackTraceLimit', 0, 10_000, true);
36
+ numeric(input.keepLogs, 'keepLogs', 0, 1_000_000, true);
37
+ numeric(input.sizeLimit, 'sizeLimit', 0, MAX_JOB_DATA_BYTES, true);
38
+ numeric(input.timestamp, 'timestamp', 0, Number.MAX_SAFE_INTEGER);
39
+ if (typeof input.backoff === 'object' && input.backoff !== null) {
40
+ if (input.backoff.type !== 'fixed' && input.backoff.type !== 'exponential') {
41
+ throw new Error("backoff.type must be 'fixed' or 'exponential'");
42
+ }
43
+ numeric(input.backoff.delay, 'backoff.delay', 0, 24 * 60 * 60 * 1_000);
44
+ }
45
+ else {
46
+ numeric(input.backoff, 'backoff', 0, 24 * 60 * 60 * 1_000);
47
+ }
48
+ for (const name of BOOLEAN_OPTIONS) {
49
+ const value = input[name];
50
+ if (value !== undefined && typeof value !== 'boolean') {
51
+ throw new Error(`${name} must be a boolean`);
52
+ }
53
+ }
54
+ if (input.groupId !== undefined &&
55
+ (typeof input.groupId !== 'string' || input.groupId.length > 256)) {
56
+ throw new Error('groupId must be a string of at most 256 characters');
57
+ }
58
+ if (input.tags !== undefined &&
59
+ (!Array.isArray(input.tags) ||
60
+ input.tags.some((tag) => typeof tag !== 'string' || tag.length > 256))) {
61
+ throw new Error('tags must be an array of strings of at most 256 characters');
62
+ }
63
+ if (input.repeat !== undefined) {
64
+ throw new Error('repeat is not supported inside an atomic flow');
65
+ }
66
+ if (input.uniqueKey !== undefined || input.dedup !== undefined) {
67
+ throw new Error('deduplication is not supported inside an atomic flow');
68
+ }
69
+ if (input.debounceId !== undefined || input.debounceTtl !== undefined) {
70
+ throw new Error('debounce is not supported inside an atomic flow');
71
+ }
72
+ const policies = [
73
+ input.failParentOnFailure,
74
+ input.removeDependencyOnFailure,
75
+ input.continueParentOnFailure,
76
+ input.ignoreDependencyOnFailure,
77
+ ].filter((enabled) => enabled === true);
78
+ if (policies.length > 1) {
79
+ throw new Error('flow failure policies are mutually exclusive');
80
+ }
81
+ if (policies.length === 1 && !input.parentId) {
82
+ throw new Error('a flow failure policy requires a parent');
83
+ }
84
+ }
85
+ function validateData(data) {
86
+ if (typeof data !== 'object' || data === null || Array.isArray(data)) {
87
+ throw new Error('flow job data must be an object');
88
+ }
89
+ let encoded;
90
+ try {
91
+ encoded = JSON.stringify(data);
92
+ }
93
+ catch {
94
+ throw new Error('job data must be JSON serializable');
95
+ }
96
+ if (encoded === undefined)
97
+ throw new Error('job data must be JSON serializable');
98
+ if (encoded.length > MAX_JOB_DATA_BYTES) {
99
+ throw new Error('Job data too large (max 10MB)');
100
+ }
101
+ const value = data;
102
+ if (typeof value.name !== 'string' || value.name.length === 0 || value.name.length > 256) {
103
+ throw new Error('flow job data.name must be a non-empty string of at most 256 characters');
104
+ }
105
+ return { encodedLength: encoded.length, value };
106
+ }
107
+ function validateIdList(value, name) {
108
+ if (value !== undefined &&
109
+ (!Array.isArray(value) ||
110
+ value.some((id) => typeof id !== 'string' || id.length === 0 || id.length > 1_024))) {
111
+ throw new Error(`${name} must be an array of valid string job ids`);
112
+ }
113
+ }
114
+ /** Validate the whole graph before any lock, storage write, or queue mutation. */
115
+ export function validateAtomicFlowBatch(batch) {
116
+ if (!batch || typeof batch !== 'object' || !Array.isArray(batch.jobs)) {
117
+ throw new Error('flow jobs must be an array');
118
+ }
119
+ if (batch.jobs.length > MAX_FLOW_JOBS) {
120
+ throw new Error(`flow exceeds the ${MAX_FLOW_JOBS} job limit`);
121
+ }
122
+ const ids = new Set();
123
+ const dataById = new Map();
124
+ let totalDataBytes = 0;
125
+ for (const [index, job] of batch.jobs.entries()) {
126
+ if (!job || typeof job !== 'object')
127
+ throw new Error(`flow jobs[${index}] must be an object`);
128
+ if (typeof job.id !== 'string')
129
+ throw new Error('flow job id must be a string');
130
+ const id = job.id;
131
+ if (!id || id.length > 1_024)
132
+ throw new Error('flow job id is invalid');
133
+ if (id.includes(':'))
134
+ throw new Error('flow job id cannot contain a colon');
135
+ if (ids.has(id))
136
+ throw new Error(`duplicate flow job id: ${id}`);
137
+ ids.add(id);
138
+ if (typeof job.queue !== 'string' ||
139
+ !job.queue ||
140
+ job.queue.length > 256 ||
141
+ !QUEUE_RE.test(job.queue)) {
142
+ throw new Error(`invalid flow queue: ${job.queue}`);
143
+ }
144
+ if (!job.input || typeof job.input !== 'object') {
145
+ throw new Error(`flow job input is invalid: ${id}`);
146
+ }
147
+ if (job.input.customId !== undefined &&
148
+ (typeof job.input.customId !== 'string' || job.input.customId !== id)) {
149
+ throw new Error(`custom id does not match planned id: ${id}`);
150
+ }
151
+ if (job.input.parentId !== undefined &&
152
+ (typeof job.input.parentId !== 'string' || job.input.parentId.length === 0)) {
153
+ throw new Error(`flow parent id is invalid: ${id}`);
154
+ }
155
+ validateIdList(job.input.dependsOn, 'dependsOn');
156
+ validateIdList(job.input.childrenIds, 'childrenIds');
157
+ const validatedData = validateData(job.input.data);
158
+ totalDataBytes += validatedData.encodedLength;
159
+ if (totalDataBytes > MAX_FLOW_DATA_BYTES) {
160
+ throw new Error('flow data is too large (max 64MB per atomic batch)');
161
+ }
162
+ dataById.set(id, validatedData.value);
163
+ validateOptions(job.input);
164
+ }
165
+ validateFlowTopology(batch, dataById);
166
+ }
@@ -14,6 +14,8 @@ export interface PushContext {
14
14
  storage: SqliteStorage | null;
15
15
  shards: Shard[];
16
16
  shardLocks: RWLock[];
17
+ /** Serializes deterministic IDs before any shard lock is acquired. */
18
+ customIdLock: RWLock;
17
19
  completedJobs: SetLike<JobId>;
18
20
  completedJobsData: MapLike<JobId, Job>;
19
21
  /** Bare completion ids for removeOnComplete jobs so dependents start ready */
@@ -34,6 +36,7 @@ export interface PushContext {
34
36
  timestamp: number;
35
37
  }) => void;
36
38
  dashboardEmit?: (event: string, data: Record<string, unknown>) => void;
39
+ registerQueueName?: (queue: string) => void;
37
40
  }
38
41
  /**
39
42
  * Push a single job to queue
@@ -116,7 +116,7 @@ export async function pushJob(queue, input, ctx) {
116
116
  return;
117
117
  }
118
118
  // Insert to shard
119
- insertJobToShard(job, queue, shard, idx, ctx);
119
+ insertJobToShard(job, { queue, shard, shardIdx: idx }, ctx);
120
120
  shard.notify(queue);
121
121
  result = { job, persisted: true };
122
122
  });
@@ -172,7 +172,7 @@ export async function pushJobBatch(queue, inputs, ctx) {
172
172
  continue;
173
173
  }
174
174
  // Insert to shard
175
- insertJobToShard(job, queue, shard, idx, ctx);
175
+ insertJobToShard(job, { queue, shard, shardIdx: idx }, ctx);
176
176
  jobsToInsert.push(job);
177
177
  if (input.durable)
178
178
  durableJobs.push(job);
@@ -12,5 +12,11 @@ export interface PushInsertContext {
12
12
  jobIndex: Map<JobId, JobLocation>;
13
13
  dashboardEmit?: (event: string, data: Record<string, unknown>) => void;
14
14
  }
15
+ /** Resolve the first externally visible state without mutating queue structures. */
16
+ export declare function initialJobState(job: Job, ctx: Pick<PushInsertContext, 'completedJobs' | 'depCompletions'>, now?: number): 'waiting-children' | 'delayed' | 'prioritized' | 'waiting';
15
17
  /** Insert a new job into the runnable heap or dependency wait set. */
16
- export declare function insertJobToShard(job: Job, queue: string, shard: Shard, shardIdx: number, ctx: PushInsertContext): void;
18
+ export declare function insertJobToShard(job: Job, target: {
19
+ queue: string;
20
+ shard: Shard;
21
+ shardIdx: number;
22
+ }, ctx: PushInsertContext, recordTimeline?: boolean): void;
@@ -1,13 +1,23 @@
1
- /** Insert a new job into the runnable heap or dependency wait set. */
2
- export function insertJobToShard(job, queue, shard, shardIdx, ctx) {
3
- const hasDeps = job.dependsOn.length > 0;
4
- const needsWaiting = hasDeps &&
1
+ /** Resolve the first externally visible state without mutating queue structures. */
2
+ export function initialJobState(job, ctx, now = Date.now()) {
3
+ const needsWaiting = job.dependsOn.length > 0 &&
5
4
  !job.dependsOn.every((depId) => ctx.completedJobs.has(depId) || (ctx.depCompletions?.has(depId) ?? false));
5
+ if (needsWaiting)
6
+ return 'waiting-children';
7
+ if (job.runAt > now)
8
+ return 'delayed';
9
+ return job.priority > 0 ? 'prioritized' : 'waiting';
10
+ }
11
+ /** Insert a new job into the runnable heap or dependency wait set. */
12
+ export function insertJobToShard(job, target, ctx, recordTimeline = true) {
13
+ const { queue, shard, shardIdx } = target;
6
14
  const now = Date.now();
7
- if (needsWaiting) {
15
+ const state = initialJobState(job, ctx, now);
16
+ if (state === 'waiting-children') {
8
17
  shard.waitingDeps.set(job.id, job);
9
18
  shard.registerDependencies(job.id, job.dependsOn);
10
- job.timeline.push({ state: 'waiting-children', timestamp: now });
19
+ if (recordTimeline)
20
+ job.timeline.push({ state, timestamp: now });
11
21
  ctx.dashboardEmit?.('job:waiting-children', {
12
22
  jobId: String(job.id),
13
23
  queue,
@@ -18,8 +28,8 @@ export function insertJobToShard(job, queue, shard, shardIdx, ctx) {
18
28
  shard.getQueue(queue).push(job);
19
29
  const isDelayed = job.runAt > now;
20
30
  shard.incrementQueued(job.id, isDelayed, job.createdAt, queue, job.runAt);
21
- const state = isDelayed ? 'delayed' : job.priority > 0 ? 'prioritized' : 'waiting';
22
- job.timeline.push({ state, timestamp: now });
31
+ if (recordTimeline)
32
+ job.timeline.push({ state, timestamp: now });
23
33
  }
24
34
  ctx.jobIndex.set(job.id, { type: 'queue', shardIdx, queueName: queue });
25
35
  ctx.dependencyResults.registerConsumer(job.id, job.dependsOn);
@@ -3,6 +3,7 @@ import type { JobLocation } from '../../domain/types/queue';
3
3
  import type { RWLock } from '../../shared/lock';
4
4
  interface PushLockContext {
5
5
  shardLocks: RWLock[];
6
+ customIdLock: RWLock;
6
7
  jobIndex: Map<JobId, JobLocation>;
7
8
  }
8
9
  /**
@@ -22,9 +22,12 @@ function requiredShardIndexes(queue, inputs, jobIndex) {
22
22
  */
23
23
  export async function withPushWriteLocks(queue, inputs, ctx, fn) {
24
24
  while (true) {
25
- const indexes = requiredShardIndexes(queue, inputs, ctx.jobIndex);
25
+ const customIdGuard = inputs.some((input) => input.customId)
26
+ ? await ctx.customIdLock.acquireWrite()
27
+ : null;
26
28
  const guards = [];
27
29
  try {
30
+ const indexes = requiredShardIndexes(queue, inputs, ctx.jobIndex);
28
31
  for (const index of indexes) {
29
32
  guards.push(await ctx.shardLocks[index].acquireWrite());
30
33
  }
@@ -38,6 +41,7 @@ export async function withPushWriteLocks(queue, inputs, ctx, fn) {
38
41
  for (let i = guards.length - 1; i >= 0; i--) {
39
42
  guards[i].release();
40
43
  }
44
+ customIdGuard?.release();
41
45
  }
42
46
  }
43
47
  }
@@ -361,9 +361,10 @@ function querySqliteByLogicalState(storage, queue, sqlFilteredStates, opts) {
361
361
  if (opts.excludedIds.size === 0) {
362
362
  return storage.queryJobsByLogicalStates(queue, sqlFilteredStates, opts);
363
363
  }
364
- // waitingDeps/waitingChildren rows remain persisted as waiting/delayed. Fetch
365
- // enough rows to account for every possible exclusion, then paginate the
366
- // logical result so parked jobs cannot create short or shifted pages.
364
+ // Legacy waitingDeps/waitingChildren rows may be persisted as waiting/delayed,
365
+ // while current inserts retain waiting-children. Fetch enough rows to account
366
+ // for every possible exclusion, then paginate the logical result so parked
367
+ // jobs cannot create short or shifted pages.
367
368
  const pageEnd = opts.offset + opts.limit;
368
369
  const jobs = storage.queryJobsByLogicalStates(queue, sqlFilteredStates, {
369
370
  limit: pageEnd + opts.excludedIds.size,
@@ -419,9 +420,9 @@ export function getJobs(queue, shardIdx, options, ctx) {
419
420
  s !== 'waiting-children' &&
420
421
  s !== 'paused' &&
421
422
  !(isPaused && (s === 'waiting' || s === 'prioritized')));
422
- // In-memory parked state is authoritative over the persisted row. Initial
423
- // dependency jobs are stored as waiting/delayed, while parents moved after
424
- // a pull can still be stored as active.
423
+ // In-memory parked state is authoritative over the persisted row. Current
424
+ // inserts retain waiting-children; legacy rows may still be waiting/delayed,
425
+ // while parents moved after a pull can still be stored as active.
425
426
  const parkedJobs = states.includes('waiting-children') || sqlFilteredStates.length > 0
426
427
  ? collectWaitingChildrenJobs(shard, queue)
427
428
  : [];
@@ -3,6 +3,7 @@
3
3
  * Core orchestrator for all queue operations
4
4
  */
5
5
  import type { Job, JobId, JobInput, JobLock, LockToken } from '../domain/types/job';
6
+ import type { AtomicFlowBatchInput, AtomicFlowBatchResult } from '../domain/types/flow';
6
7
  import type { JobLocation, JobEvent } from '../domain/types/queue';
7
8
  import type { CronJob, CronJobInput } from '../domain/types/cron';
8
9
  import type { JobLogEntry, CreateWorkerOptions } from '../domain/types/worker';
@@ -24,6 +25,7 @@ export declare class QueueManager {
24
25
  private readonly storage;
25
26
  private readonly shards;
26
27
  private readonly shardLocks;
28
+ private readonly customIdLock;
27
29
  private readonly processingShards;
28
30
  private readonly processingLocks;
29
31
  private readonly jobIndex;
@@ -67,6 +69,7 @@ export declare class QueueManager {
67
69
  private handleRepeat;
68
70
  push(queue: string, input: JobInput): Promise<Job>;
69
71
  pushBatch(queue: string, inputs: JobInput[]): Promise<JobId[]>;
72
+ pushFlow(batch: AtomicFlowBatchInput): Promise<AtomicFlowBatchResult>;
70
73
  pull(queue: string, timeoutMs?: number, signal?: AbortSignal): Promise<Job | null>;
71
74
  pullWithLock(queue: string, owner: string, timeoutMs?: number, lockTtl?: number, signal?: AbortSignal): Promise<{
72
75
  job: Job | null;
@@ -278,6 +281,8 @@ export declare class QueueManager {
278
281
  getDepCompletions(): SetLike<JobId>;
279
282
  getShards(): Shard[];
280
283
  private onJobCompleted;
284
+ /** Release failure metadata owned by a parent that reached terminal failure. */
285
+ private onJobFailed;
281
286
  /** Check if completing this job completes an entire flow */
282
287
  private checkFlowCompleted;
283
288
  /**