bunqueue 1.9.8 → 2.0.0
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/application/backgroundTasks.d.ts +10 -0
- package/dist/application/backgroundTasks.d.ts.map +1 -1
- package/dist/application/backgroundTasks.js +64 -13
- package/dist/application/backgroundTasks.js.map +1 -1
- package/dist/application/cleanupTasks.js +5 -1
- package/dist/application/cleanupTasks.js.map +1 -1
- package/dist/application/clientTracking.d.ts.map +1 -1
- package/dist/application/clientTracking.js +25 -16
- package/dist/application/clientTracking.js.map +1 -1
- package/dist/application/contextFactory.d.ts.map +1 -1
- package/dist/application/contextFactory.js +1 -0
- package/dist/application/contextFactory.js.map +1 -1
- package/dist/application/dependencyProcessor.d.ts.map +1 -1
- package/dist/application/dependencyProcessor.js +14 -13
- package/dist/application/dependencyProcessor.js.map +1 -1
- package/dist/application/eventsManager.d.ts.map +1 -1
- package/dist/application/eventsManager.js +16 -4
- package/dist/application/eventsManager.js.map +1 -1
- package/dist/application/jobLogsManager.d.ts +2 -2
- package/dist/application/jobLogsManager.d.ts.map +1 -1
- package/dist/application/jobLogsManager.js +13 -3
- package/dist/application/jobLogsManager.js.map +1 -1
- package/dist/application/lockManager.d.ts +1 -0
- package/dist/application/lockManager.d.ts.map +1 -1
- package/dist/application/lockManager.js +54 -36
- package/dist/application/lockManager.js.map +1 -1
- package/dist/application/operations/ack.d.ts +3 -1
- package/dist/application/operations/ack.d.ts.map +1 -1
- package/dist/application/operations/ack.js +25 -1
- package/dist/application/operations/ack.js.map +1 -1
- package/dist/application/operations/ackHelpers.d.ts +1 -0
- package/dist/application/operations/ackHelpers.d.ts.map +1 -1
- package/dist/application/operations/ackHelpers.js +12 -1
- package/dist/application/operations/ackHelpers.js.map +1 -1
- package/dist/application/operations/jobManagement.d.ts +1 -1
- package/dist/application/operations/jobManagement.d.ts.map +1 -1
- package/dist/application/operations/jobManagement.js +23 -3
- package/dist/application/operations/jobManagement.js.map +1 -1
- package/dist/application/operations/push.d.ts +1 -1
- package/dist/application/operations/push.d.ts.map +1 -1
- package/dist/application/operations/push.js +25 -9
- package/dist/application/operations/push.js.map +1 -1
- package/dist/application/operations/queryOperations.d.ts +3 -0
- package/dist/application/operations/queryOperations.d.ts.map +1 -1
- package/dist/application/operations/queryOperations.js +29 -0
- package/dist/application/operations/queryOperations.js.map +1 -1
- package/dist/application/queueManager.d.ts +15 -1
- package/dist/application/queueManager.d.ts.map +1 -1
- package/dist/application/queueManager.js +77 -3
- package/dist/application/queueManager.js.map +1 -1
- package/dist/application/stallDetection.js +27 -22
- package/dist/application/stallDetection.js.map +1 -1
- package/dist/application/types.js +1 -1
- package/dist/application/types.js.map +1 -1
- package/dist/application/webhookManager.d.ts.map +1 -1
- package/dist/application/webhookManager.js +18 -2
- package/dist/application/webhookManager.js.map +1 -1
- package/dist/application/workerManager.d.ts.map +1 -1
- package/dist/application/workerManager.js +4 -2
- package/dist/application/workerManager.js.map +1 -1
- package/dist/cli/client.d.ts +3 -5
- package/dist/cli/client.d.ts.map +1 -1
- package/dist/cli/client.js +31 -27
- package/dist/cli/client.js.map +1 -1
- package/dist/cli/commands/core.js +3 -3
- package/dist/cli/commands/core.js.map +1 -1
- package/dist/cli/commands/job.js +14 -14
- package/dist/cli/commands/job.js.map +1 -1
- package/dist/cli/commands/server.d.ts.map +1 -1
- package/dist/cli/commands/server.js +5 -29
- package/dist/cli/commands/server.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -9
- package/dist/cli/index.js.map +1 -1
- package/dist/client/events.d.ts +29 -1
- package/dist/client/events.d.ts.map +1 -1
- package/dist/client/events.js +96 -28
- package/dist/client/events.js.map +1 -1
- package/dist/client/flow.d.ts +144 -3
- package/dist/client/flow.d.ts.map +1 -1
- package/dist/client/flow.js +538 -68
- package/dist/client/flow.js.map +1 -1
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/queue/queue.d.ts +260 -1
- package/dist/client/queue/queue.d.ts.map +1 -1
- package/dist/client/queue/queue.js +1346 -17
- package/dist/client/queue/queue.js.map +1 -1
- package/dist/client/sandboxed/types.d.ts +1 -0
- package/dist/client/sandboxed/types.d.ts.map +1 -1
- package/dist/client/sandboxed/worker.d.ts +1 -0
- package/dist/client/sandboxed/worker.d.ts.map +1 -1
- package/dist/client/sandboxed/worker.js +31 -8
- package/dist/client/sandboxed/worker.js.map +1 -1
- package/dist/client/sandboxed/wrapper.d.ts.map +1 -1
- package/dist/client/sandboxed/wrapper.js +10 -1
- package/dist/client/sandboxed/wrapper.js.map +1 -1
- package/dist/client/tcp/client.d.ts +4 -1
- package/dist/client/tcp/client.d.ts.map +1 -1
- package/dist/client/tcp/client.js +26 -8
- package/dist/client/tcp/client.js.map +1 -1
- package/dist/client/tcp/connection.d.ts +6 -8
- package/dist/client/tcp/connection.d.ts.map +1 -1
- package/dist/client/tcp/connection.js +24 -22
- package/dist/client/tcp/connection.js.map +1 -1
- package/dist/client/tcp/index.d.ts +0 -1
- package/dist/client/tcp/index.d.ts.map +1 -1
- package/dist/client/tcp/index.js +0 -1
- package/dist/client/tcp/index.js.map +1 -1
- package/dist/client/tcp/types.d.ts +8 -13
- package/dist/client/tcp/types.d.ts.map +1 -1
- package/dist/client/tcp/types.js +0 -1
- package/dist/client/tcp/types.js.map +1 -1
- package/dist/client/tcpPool.d.ts.map +1 -1
- package/dist/client/tcpPool.js +19 -14
- package/dist/client/tcpPool.js.map +1 -1
- package/dist/client/types.d.ts +430 -13
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +346 -5
- package/dist/client/types.js.map +1 -1
- package/dist/client/worker/ackBatcher.d.ts +2 -1
- package/dist/client/worker/ackBatcher.d.ts.map +1 -1
- package/dist/client/worker/ackBatcher.js +29 -18
- package/dist/client/worker/ackBatcher.js.map +1 -1
- package/dist/client/worker/jobParser.d.ts.map +1 -1
- package/dist/client/worker/jobParser.js +8 -7
- package/dist/client/worker/jobParser.js.map +1 -1
- package/dist/client/worker/processor.d.ts.map +1 -1
- package/dist/client/worker/processor.js +15 -6
- package/dist/client/worker/processor.js.map +1 -1
- package/dist/client/worker/worker.d.ts +117 -0
- package/dist/client/worker/worker.d.ts.map +1 -1
- package/dist/client/worker/worker.js +375 -3
- package/dist/client/worker/worker.js.map +1 -1
- package/dist/domain/queue/dlqShard.d.ts +2 -0
- package/dist/domain/queue/dlqShard.d.ts.map +1 -1
- package/dist/domain/queue/dlqShard.js +12 -2
- package/dist/domain/queue/dlqShard.js.map +1 -1
- package/dist/domain/queue/priorityQueue.d.ts.map +1 -1
- package/dist/domain/queue/priorityQueue.js +24 -18
- package/dist/domain/queue/priorityQueue.js.map +1 -1
- package/dist/domain/queue/shard.d.ts +8 -2
- package/dist/domain/queue/shard.d.ts.map +1 -1
- package/dist/domain/queue/shard.js +27 -9
- package/dist/domain/queue/shard.js.map +1 -1
- package/dist/domain/queue/temporalManager.d.ts +1 -0
- package/dist/domain/queue/temporalManager.d.ts.map +1 -1
- package/dist/domain/queue/temporalManager.js +2 -1
- package/dist/domain/queue/temporalManager.js.map +1 -1
- package/dist/domain/queue/uniqueKeyManager.d.ts +2 -2
- package/dist/domain/queue/uniqueKeyManager.d.ts.map +1 -1
- package/dist/domain/queue/uniqueKeyManager.js +3 -3
- package/dist/domain/queue/uniqueKeyManager.js.map +1 -1
- package/dist/domain/types/command.d.ts +6 -0
- package/dist/domain/types/command.d.ts.map +1 -1
- package/dist/domain/types/job.d.ts +89 -2
- package/dist/domain/types/job.d.ts.map +1 -1
- package/dist/domain/types/job.js +94 -26
- package/dist/domain/types/job.js.map +1 -1
- package/dist/domain/types/queue.d.ts +11 -1
- package/dist/domain/types/queue.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqlite.d.ts +2 -0
- package/dist/infrastructure/persistence/sqlite.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqlite.js +5 -1
- package/dist/infrastructure/persistence/sqlite.js.map +1 -1
- package/dist/infrastructure/persistence/sqliteBatch.d.ts +9 -4
- package/dist/infrastructure/persistence/sqliteBatch.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqliteBatch.js +38 -21
- package/dist/infrastructure/persistence/sqliteBatch.js.map +1 -1
- package/dist/infrastructure/persistence/sqliteSerializer.d.ts.map +1 -1
- package/dist/infrastructure/persistence/sqliteSerializer.js +14 -0
- package/dist/infrastructure/persistence/sqliteSerializer.js.map +1 -1
- package/dist/infrastructure/persistence/statements.d.ts +1 -1
- package/dist/infrastructure/persistence/statements.d.ts.map +1 -1
- package/dist/infrastructure/persistence/statements.js +3 -2
- package/dist/infrastructure/persistence/statements.js.map +1 -1
- package/dist/infrastructure/scheduler/cronScheduler.d.ts +7 -0
- package/dist/infrastructure/scheduler/cronScheduler.d.ts.map +1 -1
- package/dist/infrastructure/scheduler/cronScheduler.js +46 -12
- package/dist/infrastructure/scheduler/cronScheduler.js.map +1 -1
- package/dist/infrastructure/server/handlers/core.d.ts.map +1 -1
- package/dist/infrastructure/server/handlers/core.js +26 -19
- package/dist/infrastructure/server/handlers/core.js.map +1 -1
- package/dist/infrastructure/server/handlers/query.d.ts.map +1 -1
- package/dist/infrastructure/server/handlers/query.js +1 -16
- package/dist/infrastructure/server/handlers/query.js.map +1 -1
- package/dist/infrastructure/server/http.d.ts.map +1 -1
- package/dist/infrastructure/server/http.js +27 -2
- package/dist/infrastructure/server/http.js.map +1 -1
- package/dist/infrastructure/server/protocol.d.ts +15 -1
- package/dist/infrastructure/server/protocol.d.ts.map +1 -1
- package/dist/infrastructure/server/protocol.js +37 -3
- package/dist/infrastructure/server/protocol.js.map +1 -1
- package/dist/infrastructure/server/rateLimiter.d.ts.map +1 -1
- package/dist/infrastructure/server/rateLimiter.js +5 -3
- package/dist/infrastructure/server/rateLimiter.js.map +1 -1
- package/dist/infrastructure/server/tcp.d.ts +8 -10
- package/dist/infrastructure/server/tcp.d.ts.map +1 -1
- package/dist/infrastructure/server/tcp.js +87 -46
- package/dist/infrastructure/server/tcp.js.map +1 -1
- package/dist/main.js +7 -6
- package/dist/main.js.map +1 -1
- package/dist/shared/lock.d.ts +1 -1
- package/dist/shared/lock.d.ts.map +1 -1
- package/dist/shared/lock.js +6 -4
- package/dist/shared/lock.js.map +1 -1
- package/dist/shared/lru.d.ts +51 -0
- package/dist/shared/lru.d.ts.map +1 -1
- package/dist/shared/lru.js +89 -3
- package/dist/shared/lru.js.map +1 -1
- package/dist/shared/skipList.d.ts +10 -2
- package/dist/shared/skipList.d.ts.map +1 -1
- package/dist/shared/skipList.js +22 -1
- package/dist/shared/skipList.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/tcp/lineBuffer.d.ts +0 -17
- package/dist/client/tcp/lineBuffer.d.ts.map +0 -1
- package/dist/client/tcp/lineBuffer.js +0 -32
- package/dist/client/tcp/lineBuffer.js.map +0 -1
package/dist/client/flow.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FlowProducer - Job chaining and pipelines
|
|
3
|
+
* BullMQ v5 compatible
|
|
3
4
|
*/
|
|
4
5
|
import { getSharedManager } from './manager';
|
|
6
|
+
import { TcpConnectionPool, getSharedPool, releaseSharedPool } from './tcpPool';
|
|
5
7
|
import { jobId } from '../domain/types/job';
|
|
8
|
+
const FORCE_EMBEDDED = process.env.BUNQUEUE_EMBEDDED === '1';
|
|
6
9
|
/**
|
|
7
10
|
* FlowProducer creates job flows with automatic dependencies.
|
|
8
11
|
*
|
|
@@ -28,6 +31,453 @@ import { jobId } from '../domain/types/job';
|
|
|
28
31
|
* ```
|
|
29
32
|
*/
|
|
30
33
|
export class FlowProducer {
|
|
34
|
+
embedded;
|
|
35
|
+
tcp;
|
|
36
|
+
useSharedPool;
|
|
37
|
+
constructor(opts = {}) {
|
|
38
|
+
this.embedded = opts.embedded ?? FORCE_EMBEDDED;
|
|
39
|
+
if (this.embedded) {
|
|
40
|
+
this.tcp = null;
|
|
41
|
+
this.useSharedPool = false;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const connOpts = opts.connection ?? {};
|
|
45
|
+
const poolSize = connOpts.poolSize ?? 4;
|
|
46
|
+
if (poolSize === 4 && !connOpts.token) {
|
|
47
|
+
this.tcp = getSharedPool({ host: connOpts.host, port: connOpts.port, poolSize });
|
|
48
|
+
this.useSharedPool = true;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.tcp = new TcpConnectionPool({
|
|
52
|
+
host: connOpts.host ?? 'localhost',
|
|
53
|
+
port: connOpts.port ?? 6789,
|
|
54
|
+
token: connOpts.token,
|
|
55
|
+
poolSize,
|
|
56
|
+
});
|
|
57
|
+
this.useSharedPool = false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Close the connection pool (only if using dedicated pool) */
|
|
62
|
+
close() {
|
|
63
|
+
if (this.tcp && !this.useSharedPool) {
|
|
64
|
+
this.tcp.close();
|
|
65
|
+
}
|
|
66
|
+
else if (this.tcp && this.useSharedPool) {
|
|
67
|
+
releaseSharedPool(this.tcp);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Disconnect from the server (BullMQ v5 compatible).
|
|
72
|
+
* Alias for close().
|
|
73
|
+
*/
|
|
74
|
+
disconnect() {
|
|
75
|
+
this.close();
|
|
76
|
+
return Promise.resolve();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Wait until the FlowProducer is ready (BullMQ v5 compatible).
|
|
80
|
+
* In embedded mode, resolves immediately. In TCP mode, ensures connection.
|
|
81
|
+
*/
|
|
82
|
+
async waitUntilReady() {
|
|
83
|
+
if (this.embedded) {
|
|
84
|
+
// Embedded mode is always ready
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// TCP mode - ensure we can send a ping
|
|
88
|
+
if (this.tcp) {
|
|
89
|
+
await this.tcp.send({ cmd: 'Ping' });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// ============================================================================
|
|
93
|
+
// BullMQ v5 Compatible Methods
|
|
94
|
+
// ============================================================================
|
|
95
|
+
/**
|
|
96
|
+
* Add a flow (BullMQ v5 compatible).
|
|
97
|
+
*
|
|
98
|
+
* Children are processed BEFORE their parent. When all children complete,
|
|
99
|
+
* the parent becomes processable and can access children results via
|
|
100
|
+
* `job.getChildrenValues()`.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const flow = new FlowProducer();
|
|
105
|
+
*
|
|
106
|
+
* // Children execute first, then parent
|
|
107
|
+
* const { job, children } = await flow.add({
|
|
108
|
+
* name: 'parent',
|
|
109
|
+
* queueName: 'main',
|
|
110
|
+
* data: { type: 'aggregate' },
|
|
111
|
+
* children: [
|
|
112
|
+
* { name: 'child1', queueName: 'main', data: { id: 1 } },
|
|
113
|
+
* { name: 'child2', queueName: 'main', data: { id: 2 } },
|
|
114
|
+
* ],
|
|
115
|
+
* });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
async add(flow) {
|
|
119
|
+
return this.addFlowNode(flow, null);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Add multiple flows (BullMQ v5 compatible).
|
|
123
|
+
*/
|
|
124
|
+
async addBulk(flows) {
|
|
125
|
+
const results = [];
|
|
126
|
+
for (const flow of flows) {
|
|
127
|
+
results.push(await this.add(flow));
|
|
128
|
+
}
|
|
129
|
+
return results;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get a flow tree starting from a job (BullMQ v5 compatible).
|
|
133
|
+
*
|
|
134
|
+
* Retrieves the job and all its children recursively, building a JobNode tree.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```typescript
|
|
138
|
+
* const flow = new FlowProducer();
|
|
139
|
+
*
|
|
140
|
+
* // Get a previously created flow
|
|
141
|
+
* const node = await flow.getFlow({
|
|
142
|
+
* id: 'job-id',
|
|
143
|
+
* queueName: 'my-queue',
|
|
144
|
+
* });
|
|
145
|
+
*
|
|
146
|
+
* if (node) {
|
|
147
|
+
* console.log('Parent:', node.job.name);
|
|
148
|
+
* console.log('Children:', node.children?.length ?? 0);
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*/
|
|
152
|
+
async getFlow(opts) {
|
|
153
|
+
const { id, queueName, depth, maxChildren } = opts;
|
|
154
|
+
if (this.embedded) {
|
|
155
|
+
return this.getFlowEmbedded(id, queueName, depth ?? Infinity, maxChildren);
|
|
156
|
+
}
|
|
157
|
+
return this.getFlowTcp(id, queueName, depth ?? Infinity, maxChildren);
|
|
158
|
+
}
|
|
159
|
+
/** Get flow in embedded mode */
|
|
160
|
+
async getFlowEmbedded(id, queueName, depth, maxChildren) {
|
|
161
|
+
const manager = getSharedManager();
|
|
162
|
+
const job = await manager.getJob(jobId(id));
|
|
163
|
+
if (job?.queue !== queueName) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
return this.buildJobNode(job, depth, maxChildren);
|
|
167
|
+
}
|
|
168
|
+
/** Get flow in TCP mode */
|
|
169
|
+
async getFlowTcp(id, queueName, depth, maxChildren) {
|
|
170
|
+
if (!this.tcp)
|
|
171
|
+
throw new Error('TCP connection not initialized');
|
|
172
|
+
const response = await this.tcp.send({
|
|
173
|
+
cmd: 'GetJob',
|
|
174
|
+
id,
|
|
175
|
+
});
|
|
176
|
+
if (!response.ok || !response.job) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const jobData = response.job;
|
|
180
|
+
if (jobData.queue !== queueName) {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
// Build job node from TCP response
|
|
184
|
+
return this.buildJobNodeFromTcp(jobData, depth, maxChildren);
|
|
185
|
+
}
|
|
186
|
+
/** Build JobNode recursively from internal job */
|
|
187
|
+
async buildJobNode(job, depth, maxChildren) {
|
|
188
|
+
const data = job.data;
|
|
189
|
+
const nameValue = data.name;
|
|
190
|
+
const name = typeof nameValue === 'string' ? nameValue : 'default';
|
|
191
|
+
const userData = this.extractUserDataFromInternal(data);
|
|
192
|
+
const jobObj = this.createJobObject(String(job.id), name, userData, job.queue);
|
|
193
|
+
// If depth exhausted or no children, return without children
|
|
194
|
+
if (depth <= 0 || job.childrenIds.length === 0) {
|
|
195
|
+
return { job: jobObj };
|
|
196
|
+
}
|
|
197
|
+
// Fetch children recursively
|
|
198
|
+
const manager = getSharedManager();
|
|
199
|
+
const childNodes = [];
|
|
200
|
+
const childrenToFetch = maxChildren ? job.childrenIds.slice(0, maxChildren) : job.childrenIds;
|
|
201
|
+
for (const childId of childrenToFetch) {
|
|
202
|
+
const childJob = await manager.getJob(childId);
|
|
203
|
+
if (childJob) {
|
|
204
|
+
const childNode = await this.buildJobNode(childJob, depth - 1, maxChildren);
|
|
205
|
+
childNodes.push(childNode);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
job: jobObj,
|
|
210
|
+
children: childNodes.length > 0 ? childNodes : undefined,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Build JobNode from TCP response */
|
|
214
|
+
async buildJobNodeFromTcp(jobData, depth, maxChildren) {
|
|
215
|
+
const id = String(jobData.id);
|
|
216
|
+
const queueName = String(jobData.queue);
|
|
217
|
+
const data = jobData.data;
|
|
218
|
+
const nameValue = data?.name;
|
|
219
|
+
const name = typeof nameValue === 'string' ? nameValue : 'default';
|
|
220
|
+
const userData = this.extractUserDataFromInternal(data ?? {});
|
|
221
|
+
const rawChildrenIds = data?.__childrenIds;
|
|
222
|
+
const childrenIds = Array.isArray(rawChildrenIds) ? rawChildrenIds : [];
|
|
223
|
+
const jobObj = this.createJobObject(id, name, userData, queueName);
|
|
224
|
+
// If depth exhausted or no children, return without children
|
|
225
|
+
if (depth <= 0 || childrenIds.length === 0) {
|
|
226
|
+
return { job: jobObj };
|
|
227
|
+
}
|
|
228
|
+
// Fetch children recursively via TCP
|
|
229
|
+
if (!this.tcp) {
|
|
230
|
+
return { job: jobObj };
|
|
231
|
+
}
|
|
232
|
+
const childNodes = [];
|
|
233
|
+
const childrenToFetch = maxChildren ? childrenIds.slice(0, maxChildren) : childrenIds;
|
|
234
|
+
for (const childId of childrenToFetch) {
|
|
235
|
+
const response = await this.tcp.send({ cmd: 'GetJob', id: childId });
|
|
236
|
+
if (response.ok && response.job) {
|
|
237
|
+
const childNode = await this.buildJobNodeFromTcp(response.job, depth - 1, maxChildren);
|
|
238
|
+
childNodes.push(childNode);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
job: jobObj,
|
|
243
|
+
children: childNodes.length > 0 ? childNodes : undefined,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/** Extract user data (remove internal fields) */
|
|
247
|
+
extractUserDataFromInternal(data) {
|
|
248
|
+
const result = {};
|
|
249
|
+
for (const [key, value] of Object.entries(data)) {
|
|
250
|
+
if (!key.startsWith('__') && key !== 'name') {
|
|
251
|
+
result[key] = value;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Internal: Recursively add a flow node and its children.
|
|
258
|
+
* Children are created first with parent reference, then parent is created.
|
|
259
|
+
*/
|
|
260
|
+
async addFlowNode(node, parentRef) {
|
|
261
|
+
// First, create all children recursively
|
|
262
|
+
const childNodes = [];
|
|
263
|
+
const childIds = [];
|
|
264
|
+
if (node.children && node.children.length > 0) {
|
|
265
|
+
// Create a placeholder parent ID for children to reference
|
|
266
|
+
// In BullMQ, children reference the parent, not the other way around
|
|
267
|
+
const tempParentRef = { id: 'pending', queue: node.queueName };
|
|
268
|
+
for (const child of node.children) {
|
|
269
|
+
const childNode = await this.addFlowNode(child, tempParentRef);
|
|
270
|
+
childNodes.push(childNode);
|
|
271
|
+
childIds.push(childNode.job.id);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Create the job data with parent info if this is a child
|
|
275
|
+
const jobData = {
|
|
276
|
+
name: node.name,
|
|
277
|
+
...node.data,
|
|
278
|
+
};
|
|
279
|
+
// Add parent reference if this job has a parent
|
|
280
|
+
if (parentRef) {
|
|
281
|
+
jobData.__parentId = parentRef.id;
|
|
282
|
+
jobData.__parentQueue = parentRef.queue;
|
|
283
|
+
}
|
|
284
|
+
// Add children IDs so parent knows its children
|
|
285
|
+
if (childIds.length > 0) {
|
|
286
|
+
jobData.__childrenIds = childIds;
|
|
287
|
+
}
|
|
288
|
+
// Push the job
|
|
289
|
+
const jobIdStr = await this.pushJobWithParent(node.queueName, jobData, node.opts ?? {}, parentRef, childIds);
|
|
290
|
+
// Create the Job object
|
|
291
|
+
const job = this.createJobObject(jobIdStr, node.name, node.data, node.queueName);
|
|
292
|
+
return {
|
|
293
|
+
job,
|
|
294
|
+
children: childNodes.length > 0 ? childNodes : undefined,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** Push a job with parent/children tracking */
|
|
298
|
+
async pushJobWithParent(queueName, data, opts, parentRef, childIds) {
|
|
299
|
+
if (this.embedded) {
|
|
300
|
+
const manager = getSharedManager();
|
|
301
|
+
// Parse removeOnComplete/removeOnFail (can be boolean, number, or KeepJobs)
|
|
302
|
+
const removeOnComplete = typeof opts.removeOnComplete === 'boolean' ? opts.removeOnComplete : false;
|
|
303
|
+
const removeOnFail = typeof opts.removeOnFail === 'boolean' ? opts.removeOnFail : false;
|
|
304
|
+
const job = await manager.push(queueName, {
|
|
305
|
+
data,
|
|
306
|
+
priority: opts.priority,
|
|
307
|
+
delay: opts.delay,
|
|
308
|
+
maxAttempts: opts.attempts,
|
|
309
|
+
backoff: opts.backoff,
|
|
310
|
+
timeout: opts.timeout,
|
|
311
|
+
customId: opts.jobId,
|
|
312
|
+
removeOnComplete,
|
|
313
|
+
removeOnFail,
|
|
314
|
+
parentId: parentRef ? jobId(parentRef.id) : undefined,
|
|
315
|
+
// Note: childrenIds tracking happens via job.childrenIds field
|
|
316
|
+
});
|
|
317
|
+
// If this job has children, update the children's parent references
|
|
318
|
+
// with the actual parent ID (they were created with 'pending')
|
|
319
|
+
if (childIds.length > 0) {
|
|
320
|
+
for (const childIdStr of childIds) {
|
|
321
|
+
await manager.updateJobParent(jobId(childIdStr), job.id);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return String(job.id);
|
|
325
|
+
}
|
|
326
|
+
// TCP mode
|
|
327
|
+
if (!this.tcp)
|
|
328
|
+
throw new Error('TCP connection not initialized');
|
|
329
|
+
const response = await this.tcp.send({
|
|
330
|
+
cmd: 'PUSH',
|
|
331
|
+
queue: queueName,
|
|
332
|
+
data,
|
|
333
|
+
priority: opts.priority,
|
|
334
|
+
delay: opts.delay,
|
|
335
|
+
maxAttempts: opts.attempts,
|
|
336
|
+
backoff: opts.backoff,
|
|
337
|
+
timeout: opts.timeout,
|
|
338
|
+
jobId: opts.jobId,
|
|
339
|
+
removeOnComplete: opts.removeOnComplete,
|
|
340
|
+
removeOnFail: opts.removeOnFail,
|
|
341
|
+
parentId: parentRef?.id,
|
|
342
|
+
childIds,
|
|
343
|
+
});
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw new Error(response.error ?? 'Failed to add job');
|
|
346
|
+
}
|
|
347
|
+
return response.id;
|
|
348
|
+
}
|
|
349
|
+
/** Create a simple Job object */
|
|
350
|
+
createJobObject(id, name, data, queueName) {
|
|
351
|
+
const ts = Date.now();
|
|
352
|
+
return {
|
|
353
|
+
id,
|
|
354
|
+
name,
|
|
355
|
+
data,
|
|
356
|
+
queueName,
|
|
357
|
+
attemptsMade: 0,
|
|
358
|
+
timestamp: ts,
|
|
359
|
+
progress: 0,
|
|
360
|
+
// BullMQ v5 properties
|
|
361
|
+
delay: 0,
|
|
362
|
+
processedOn: undefined,
|
|
363
|
+
finishedOn: undefined,
|
|
364
|
+
stacktrace: null,
|
|
365
|
+
stalledCounter: 0,
|
|
366
|
+
priority: 0,
|
|
367
|
+
parentKey: undefined,
|
|
368
|
+
opts: {},
|
|
369
|
+
token: undefined,
|
|
370
|
+
processedBy: undefined,
|
|
371
|
+
deduplicationId: undefined,
|
|
372
|
+
repeatJobKey: undefined,
|
|
373
|
+
attemptsStarted: 0,
|
|
374
|
+
// Methods
|
|
375
|
+
updateProgress: () => Promise.resolve(),
|
|
376
|
+
log: () => Promise.resolve(),
|
|
377
|
+
getState: () => Promise.resolve('waiting'),
|
|
378
|
+
remove: () => Promise.resolve(),
|
|
379
|
+
retry: () => Promise.resolve(),
|
|
380
|
+
getChildrenValues: () => Promise.resolve({}),
|
|
381
|
+
// BullMQ v5 state check methods
|
|
382
|
+
isWaiting: () => Promise.resolve(true),
|
|
383
|
+
isActive: () => Promise.resolve(false),
|
|
384
|
+
isDelayed: () => Promise.resolve(false),
|
|
385
|
+
isCompleted: () => Promise.resolve(false),
|
|
386
|
+
isFailed: () => Promise.resolve(false),
|
|
387
|
+
isWaitingChildren: () => Promise.resolve(false),
|
|
388
|
+
// BullMQ v5 mutation methods
|
|
389
|
+
updateData: () => Promise.resolve(),
|
|
390
|
+
promote: () => Promise.resolve(),
|
|
391
|
+
changeDelay: () => Promise.resolve(),
|
|
392
|
+
changePriority: () => Promise.resolve(),
|
|
393
|
+
extendLock: () => Promise.resolve(0),
|
|
394
|
+
clearLogs: () => Promise.resolve(),
|
|
395
|
+
// BullMQ v5 dependency methods
|
|
396
|
+
getDependencies: () => Promise.resolve({ processed: {}, unprocessed: [] }),
|
|
397
|
+
getDependenciesCount: () => Promise.resolve({ processed: 0, unprocessed: 0 }),
|
|
398
|
+
// BullMQ v5 serialization methods
|
|
399
|
+
toJSON: () => ({
|
|
400
|
+
id,
|
|
401
|
+
name,
|
|
402
|
+
data,
|
|
403
|
+
opts: {},
|
|
404
|
+
progress: 0,
|
|
405
|
+
delay: 0,
|
|
406
|
+
timestamp: ts,
|
|
407
|
+
attemptsMade: 0,
|
|
408
|
+
stacktrace: null,
|
|
409
|
+
queueQualifiedName: `bull:${queueName}`,
|
|
410
|
+
}),
|
|
411
|
+
asJSON: () => ({
|
|
412
|
+
id,
|
|
413
|
+
name,
|
|
414
|
+
data: JSON.stringify(data),
|
|
415
|
+
opts: '{}',
|
|
416
|
+
progress: '0',
|
|
417
|
+
delay: '0',
|
|
418
|
+
timestamp: String(ts),
|
|
419
|
+
attemptsMade: '0',
|
|
420
|
+
stacktrace: null,
|
|
421
|
+
}),
|
|
422
|
+
// BullMQ v5 move methods
|
|
423
|
+
moveToCompleted: () => Promise.resolve(null),
|
|
424
|
+
moveToFailed: () => Promise.resolve(),
|
|
425
|
+
moveToWait: () => Promise.resolve(false),
|
|
426
|
+
moveToDelayed: () => Promise.resolve(),
|
|
427
|
+
moveToWaitingChildren: () => Promise.resolve(false),
|
|
428
|
+
waitUntilFinished: () => Promise.resolve(undefined),
|
|
429
|
+
// BullMQ v5 additional methods
|
|
430
|
+
discard: () => { },
|
|
431
|
+
getFailedChildrenValues: () => Promise.resolve({}),
|
|
432
|
+
getIgnoredChildrenFailures: () => Promise.resolve({}),
|
|
433
|
+
removeChildDependency: () => Promise.resolve(false),
|
|
434
|
+
removeDeduplicationKey: () => Promise.resolve(false),
|
|
435
|
+
removeUnprocessedChildren: () => Promise.resolve(),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
/** Push a job via embedded manager or TCP */
|
|
439
|
+
async pushJob(queueName, data, opts = {}, dependsOn) {
|
|
440
|
+
if (this.embedded) {
|
|
441
|
+
const manager = getSharedManager();
|
|
442
|
+
// Parse removeOnComplete/removeOnFail (can be boolean, number, or KeepJobs)
|
|
443
|
+
const removeOnComplete = typeof opts.removeOnComplete === 'boolean' ? opts.removeOnComplete : false;
|
|
444
|
+
const removeOnFail = typeof opts.removeOnFail === 'boolean' ? opts.removeOnFail : false;
|
|
445
|
+
const job = await manager.push(queueName, {
|
|
446
|
+
data,
|
|
447
|
+
priority: opts.priority,
|
|
448
|
+
delay: opts.delay,
|
|
449
|
+
maxAttempts: opts.attempts,
|
|
450
|
+
backoff: opts.backoff,
|
|
451
|
+
timeout: opts.timeout,
|
|
452
|
+
customId: opts.jobId,
|
|
453
|
+
removeOnComplete,
|
|
454
|
+
removeOnFail,
|
|
455
|
+
dependsOn: dependsOn?.map((id) => jobId(id)),
|
|
456
|
+
});
|
|
457
|
+
return String(job.id);
|
|
458
|
+
}
|
|
459
|
+
// TCP mode - tcp is guaranteed to exist when not embedded
|
|
460
|
+
if (!this.tcp)
|
|
461
|
+
throw new Error('TCP connection not initialized');
|
|
462
|
+
const response = await this.tcp.send({
|
|
463
|
+
cmd: 'PUSH',
|
|
464
|
+
queue: queueName,
|
|
465
|
+
data,
|
|
466
|
+
priority: opts.priority,
|
|
467
|
+
delay: opts.delay,
|
|
468
|
+
maxAttempts: opts.attempts,
|
|
469
|
+
backoff: opts.backoff,
|
|
470
|
+
timeout: opts.timeout,
|
|
471
|
+
jobId: opts.jobId,
|
|
472
|
+
removeOnComplete: opts.removeOnComplete,
|
|
473
|
+
removeOnFail: opts.removeOnFail,
|
|
474
|
+
dependsOn,
|
|
475
|
+
});
|
|
476
|
+
if (!response.ok) {
|
|
477
|
+
throw new Error(response.error ?? 'Failed to add job');
|
|
478
|
+
}
|
|
479
|
+
return response.id;
|
|
480
|
+
}
|
|
31
481
|
/**
|
|
32
482
|
* Add a chain of jobs where each depends on the previous.
|
|
33
483
|
* Jobs execute sequentially: step[0] → step[1] → step[2] → ...
|
|
@@ -36,27 +486,21 @@ export class FlowProducer {
|
|
|
36
486
|
if (steps.length === 0) {
|
|
37
487
|
return { jobIds: [] };
|
|
38
488
|
}
|
|
39
|
-
const manager = getSharedManager();
|
|
40
489
|
const jobIds = [];
|
|
41
490
|
let prevId = null;
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
data
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
};
|
|
56
|
-
const job = await manager.push(step.queueName, input);
|
|
57
|
-
const id = String(job.id);
|
|
58
|
-
jobIds.push(id);
|
|
59
|
-
prevId = id;
|
|
491
|
+
try {
|
|
492
|
+
for (const step of steps) {
|
|
493
|
+
const merged = step.opts ?? {};
|
|
494
|
+
const data = { name: step.name, __flowParentId: prevId, ...step.data };
|
|
495
|
+
const id = await this.pushJob(step.queueName, data, merged, prevId ? [prevId] : undefined);
|
|
496
|
+
jobIds.push(id);
|
|
497
|
+
prevId = id;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
// Cleanup already-created jobs on failure
|
|
502
|
+
await this.cleanupJobs(jobIds);
|
|
503
|
+
throw error;
|
|
60
504
|
}
|
|
61
505
|
return { jobIds };
|
|
62
506
|
}
|
|
@@ -72,42 +516,33 @@ export class FlowProducer {
|
|
|
72
516
|
* ```
|
|
73
517
|
*/
|
|
74
518
|
async addBulkThen(parallel, final) {
|
|
75
|
-
const manager = getSharedManager();
|
|
76
519
|
// Create parallel jobs (no dependencies)
|
|
77
520
|
const parallelIds = [];
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
data
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
removeOnComplete: finalMerged.removeOnComplete,
|
|
104
|
-
removeOnFail: finalMerged.removeOnFail,
|
|
105
|
-
dependsOn: parallelIds.map((id) => jobId(id)),
|
|
106
|
-
});
|
|
107
|
-
return {
|
|
108
|
-
parallelIds,
|
|
109
|
-
finalId: String(finalJob.id),
|
|
110
|
-
};
|
|
521
|
+
try {
|
|
522
|
+
for (const step of parallel) {
|
|
523
|
+
const merged = step.opts ?? {};
|
|
524
|
+
const data = { name: step.name, ...step.data };
|
|
525
|
+
const id = await this.pushJob(step.queueName, data, merged);
|
|
526
|
+
parallelIds.push(id);
|
|
527
|
+
}
|
|
528
|
+
// Create final job with dependencies on all parallel jobs
|
|
529
|
+
const finalMerged = final.opts ?? {};
|
|
530
|
+
const finalData = {
|
|
531
|
+
name: final.name,
|
|
532
|
+
__flowParentIds: parallelIds,
|
|
533
|
+
...final.data,
|
|
534
|
+
};
|
|
535
|
+
const finalId = await this.pushJob(final.queueName, finalData, finalMerged, parallelIds);
|
|
536
|
+
return {
|
|
537
|
+
parallelIds,
|
|
538
|
+
finalId,
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
// Cleanup already-created parallel jobs on failure
|
|
543
|
+
await this.cleanupJobs(parallelIds);
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
111
546
|
}
|
|
112
547
|
/**
|
|
113
548
|
* Add a tree of jobs where children depend on parent.
|
|
@@ -115,25 +550,20 @@ export class FlowProducer {
|
|
|
115
550
|
*/
|
|
116
551
|
async addTree(root) {
|
|
117
552
|
const jobIds = [];
|
|
118
|
-
|
|
553
|
+
try {
|
|
554
|
+
await this.addTreeNode(root, null, jobIds);
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
// Cleanup already-created jobs on failure
|
|
558
|
+
await this.cleanupJobs(jobIds);
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
119
561
|
return { jobIds };
|
|
120
562
|
}
|
|
121
563
|
async addTreeNode(step, parentId, jobIds) {
|
|
122
|
-
const manager = getSharedManager();
|
|
123
564
|
const merged = step.opts ?? {};
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
priority: merged.priority,
|
|
127
|
-
delay: merged.delay,
|
|
128
|
-
maxAttempts: merged.attempts,
|
|
129
|
-
backoff: merged.backoff,
|
|
130
|
-
timeout: merged.timeout,
|
|
131
|
-
customId: merged.jobId,
|
|
132
|
-
removeOnComplete: merged.removeOnComplete,
|
|
133
|
-
removeOnFail: merged.removeOnFail,
|
|
134
|
-
dependsOn: parentId ? [jobId(parentId)] : undefined,
|
|
135
|
-
});
|
|
136
|
-
const id = String(job.id);
|
|
565
|
+
const data = { name: step.name, __flowParentId: parentId, ...step.data };
|
|
566
|
+
const id = await this.pushJob(step.queueName, data, merged, parentId ? [parentId] : undefined);
|
|
137
567
|
jobIds.push(id);
|
|
138
568
|
// Create children with this job as parent
|
|
139
569
|
if (step.children) {
|
|
@@ -143,18 +573,58 @@ export class FlowProducer {
|
|
|
143
573
|
}
|
|
144
574
|
return id;
|
|
145
575
|
}
|
|
576
|
+
/**
|
|
577
|
+
* Cleanup jobs that were created before a failure occurred.
|
|
578
|
+
* Cancels each job to prevent orphaned jobs in the queue.
|
|
579
|
+
*/
|
|
580
|
+
async cleanupJobs(jobIds) {
|
|
581
|
+
if (jobIds.length === 0)
|
|
582
|
+
return;
|
|
583
|
+
if (this.embedded) {
|
|
584
|
+
const manager = getSharedManager();
|
|
585
|
+
const cleanupPromises = jobIds.map(async (id) => {
|
|
586
|
+
try {
|
|
587
|
+
await manager.cancel(jobId(id));
|
|
588
|
+
}
|
|
589
|
+
catch {
|
|
590
|
+
// Ignore errors during cleanup - job may already be processed or removed
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
await Promise.all(cleanupPromises);
|
|
594
|
+
}
|
|
595
|
+
else if (this.tcp) {
|
|
596
|
+
// TCP mode cleanup
|
|
597
|
+
const cleanupPromises = jobIds.map(async (id) => {
|
|
598
|
+
try {
|
|
599
|
+
await this.tcp?.send({ cmd: 'Cancel', id });
|
|
600
|
+
}
|
|
601
|
+
catch {
|
|
602
|
+
// Ignore errors during cleanup
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
await Promise.all(cleanupPromises);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
146
608
|
/**
|
|
147
609
|
* Get the result of a completed parent job.
|
|
148
610
|
* Call this from within a worker to access the previous step's result.
|
|
611
|
+
* Note: Only works in embedded mode.
|
|
149
612
|
*/
|
|
150
613
|
getParentResult(parentId) {
|
|
614
|
+
if (!this.embedded) {
|
|
615
|
+
throw new Error('getParentResult is only available in embedded mode');
|
|
616
|
+
}
|
|
151
617
|
const manager = getSharedManager();
|
|
152
618
|
return manager.getResult(jobId(parentId));
|
|
153
619
|
}
|
|
154
620
|
/**
|
|
155
621
|
* Get results from multiple parent jobs (for merge scenarios).
|
|
622
|
+
* Note: Only works in embedded mode.
|
|
156
623
|
*/
|
|
157
624
|
getParentResults(parentIds) {
|
|
625
|
+
if (!this.embedded) {
|
|
626
|
+
throw new Error('getParentResults is only available in embedded mode');
|
|
627
|
+
}
|
|
158
628
|
const manager = getSharedManager();
|
|
159
629
|
const results = new Map();
|
|
160
630
|
for (const id of parentIds) {
|