bunqueue 2.8.58 → 2.8.60
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/README.md +5 -1
- package/dist/application/dlqManager.d.ts +1 -1
- package/dist/application/dlqManager.js +1 -1
- package/dist/application/lockManager.js +1 -1
- package/dist/application/queue-manager/ack.js +1 -1
- package/dist/application/queue-manager/delivery.js +1 -1
- package/dist/application/statsManager.d.ts +1 -1
- package/dist/application/statsManager.js +1 -1
- package/dist/client/events.js +2 -2
- package/dist/client/flow.js +0 -1
- package/dist/client/queue/operations/add/single.js +1 -1
- package/dist/client/sandboxed/runtime/state.js +2 -2
- package/dist/client/tcp/connection.d.ts +1 -1
- package/dist/client/tcp/connection.js +1 -1
- package/dist/client/tcp/runtime/state.js +2 -2
- package/dist/client/types/metrics.d.ts +1 -1
- package/dist/client/worker/runtime/control.js +0 -1
- package/dist/client/worker/runtime/lifecycle.js +0 -1
- package/dist/client/worker/runtime/state.js +3 -3
- package/dist/client/workflow/compensationChild.d.ts +2 -0
- package/dist/client/workflow/compensationChild.js +3 -1
- package/dist/client/workflow/compensationPass.d.ts +1 -0
- package/dist/client/workflow/compensationPass.js +18 -2
- package/dist/client/workflow/compensator.d.ts +2 -1
- package/dist/client/workflow/compensator.js +11 -3
- package/dist/client/workflow/engine.js +1 -1
- package/dist/client/workflow/executionFence.d.ts +15 -0
- package/dist/client/workflow/executionFence.js +29 -0
- package/dist/client/workflow/executor.d.ts +5 -11
- package/dist/client/workflow/executor.js +48 -47
- package/dist/client/workflow/executorLifecycle.d.ts +1 -0
- package/dist/client/workflow/executorLifecycle.js +41 -10
- package/dist/client/workflow/executorNodes.d.ts +1 -0
- package/dist/client/workflow/executorNodes.js +45 -16
- package/dist/client/workflow/executorQueue.d.ts +9 -0
- package/dist/client/workflow/executorQueue.js +15 -0
- package/dist/client/workflow/forEachRunner.d.ts +4 -0
- package/dist/client/workflow/forEachRunner.js +70 -0
- package/dist/client/workflow/loops.d.ts +4 -5
- package/dist/client/workflow/loops.js +27 -93
- package/dist/client/workflow/mapRunner.d.ts +1 -1
- package/dist/client/workflow/mapRunner.js +11 -2
- package/dist/client/workflow/recovery.d.ts +1 -0
- package/dist/client/workflow/recovery.js +24 -18
- package/dist/client/workflow/rollbackControl.d.ts +1 -0
- package/dist/client/workflow/rollbackControl.js +5 -2
- package/dist/client/workflow/runner.d.ts +3 -1
- package/dist/client/workflow/runner.js +26 -3
- package/dist/client/workflow/stepTypes.js +1 -1
- package/dist/client/workflow/subWorkflowRunner.d.ts +1 -0
- package/dist/client/workflow/subWorkflowRunner.js +7 -0
- package/dist/client/workflow/waitFor.d.ts +4 -0
- package/dist/client/workflow/waitFor.js +23 -11
- package/dist/client/workflow/workflowDecisions.d.ts +1 -1
- package/dist/client/workflow/workflowDecisions.js +3 -1
- package/dist/infrastructure/persistence/sqlite.d.ts +1 -1
- package/dist/infrastructure/server/handler-routes/jobs.js +0 -3
- package/dist/infrastructure/server/handlerRoutes.d.ts +2 -2
- package/dist/infrastructure/server/handlerRoutes.js +2 -2
- package/dist/infrastructure/server/httpEndpoints.js +2 -0
- package/package.json +12 -10
|
@@ -23,6 +23,7 @@ const TIMEOUT_ENQUEUE_RETRY_MS = 5_000;
|
|
|
23
23
|
*/
|
|
24
24
|
export function scheduleTimeoutCheck(deps, execId, workflowName, nodeIdx, ms) {
|
|
25
25
|
const arm = (requestedDelay) => {
|
|
26
|
+
deps.assertActive();
|
|
26
27
|
const delay = Math.min(Math.max(requestedDelay, 0), MAX_TIMER_MS);
|
|
27
28
|
// Replacing a live timer for the same execution — re-entering a waitFor node used
|
|
28
29
|
// to leak the previous one, which then fired against a node the run had left.
|
|
@@ -30,6 +31,11 @@ export function scheduleTimeoutCheck(deps, execId, workflowName, nodeIdx, ms) {
|
|
|
30
31
|
if (previous)
|
|
31
32
|
clock().clearTimeout(previous);
|
|
32
33
|
const timer = clock().setTimeout(() => {
|
|
34
|
+
if (!deps.isActive()) {
|
|
35
|
+
if (deps.timers.get(execId) === timer)
|
|
36
|
+
deps.timers.delete(execId);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
33
39
|
const jobData = { executionId: execId, workflowName, nodeIndex: nodeIdx };
|
|
34
40
|
const published = () => {
|
|
35
41
|
// Keep the handle in the map while add() is pending. signal() and close() use
|
|
@@ -40,6 +46,10 @@ export function scheduleTimeoutCheck(deps, execId, workflowName, nodeIdx, ms) {
|
|
|
40
46
|
const failed = () => {
|
|
41
47
|
if (deps.timers.get(execId) !== timer)
|
|
42
48
|
return;
|
|
49
|
+
if (!deps.isActive()) {
|
|
50
|
+
deps.timers.delete(execId);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
43
53
|
arm(TIMEOUT_ENQUEUE_RETRY_MS);
|
|
44
54
|
};
|
|
45
55
|
try {
|
|
@@ -75,6 +85,7 @@ export function clearTimers(timers) {
|
|
|
75
85
|
* short-circuits without treating the pause as an error.
|
|
76
86
|
*/
|
|
77
87
|
export async function runWaitFor(deps, exec, node, idx, wf) {
|
|
88
|
+
deps.assertActive();
|
|
78
89
|
if (hasSignal(exec.signals, node.event)) {
|
|
79
90
|
await deps.advance(exec, idx + 1, wf);
|
|
80
91
|
return;
|
|
@@ -92,11 +103,14 @@ export async function runWaitFor(deps, exec, node, idx, wf) {
|
|
|
92
103
|
// (refund, release stock) work the approver just authorised.
|
|
93
104
|
const fresh = deps.store.get(exec.id);
|
|
94
105
|
if (fresh && hasSignal(fresh.signals, node.event)) {
|
|
106
|
+
deps.assertActive();
|
|
95
107
|
exec.signals = fresh.signals;
|
|
96
108
|
await deps.advance(exec, idx + 1, wf);
|
|
97
109
|
return;
|
|
98
110
|
}
|
|
111
|
+
deps.assertActive();
|
|
99
112
|
deps.emitter?.emitSignal('signal:timeout', exec.id, exec.workflowName, node.event);
|
|
113
|
+
deps.assertActive();
|
|
100
114
|
const timeoutReason = `Signal "${node.event}" timed out after ${node.timeout}ms`;
|
|
101
115
|
exec.steps[waitKey] = {
|
|
102
116
|
status: 'failed',
|
|
@@ -106,30 +120,26 @@ export async function runWaitFor(deps, exec, node, idx, wf) {
|
|
|
106
120
|
};
|
|
107
121
|
exec.state = 'failed';
|
|
108
122
|
exec.failureReason = timeoutReason;
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
|
|
113
|
-
//
|
|
114
|
-
// A throw here skips the compensate CALL below but not the compensation: it
|
|
115
|
-
// propagates into `runNode`'s catch, which compensates anyway. That is the generic
|
|
116
|
-
// path the `WaitForSignalError` sentinel below exists to avoid on the normal route,
|
|
117
|
-
// so the outcome is a redundant route, not a missing rollback.
|
|
118
|
-
deps.store.update(exec);
|
|
123
|
+
// Persist failure before starting rollback. The lifecycle guard leaves the
|
|
124
|
+
// durable row waiting if force-close won, so replacement recovery re-evaluates
|
|
125
|
+
// the same elapsed deadline without starting an old executor's compensation.
|
|
126
|
+
deps.updateFn(exec);
|
|
119
127
|
// Compensate here, then signal completion via the WaitForSignalError sentinel
|
|
120
128
|
// so processStep short-circuits (return null) instead of re-running
|
|
121
129
|
// compensation through its generic catch path.
|
|
122
130
|
await deps.compensate(exec, wf);
|
|
131
|
+
deps.assertActive();
|
|
123
132
|
deps.emitter?.emitWorkflow('workflow:failed', exec.id, exec.workflowName, 'failed');
|
|
124
133
|
throw new WaitForSignalError(node.event);
|
|
125
134
|
}
|
|
126
|
-
deps.
|
|
135
|
+
deps.updateFn(exec);
|
|
127
136
|
remaining = node.timeout - (clock().now() - waitingSince);
|
|
128
137
|
}
|
|
129
138
|
// Park transactionally. Between the in-memory check above and this point a signal
|
|
130
139
|
// can land: it would be recorded with no parked run left to claim the resume,
|
|
131
140
|
// hanging the execution forever. parkForSignal() re-reads the persisted signals
|
|
132
141
|
// and only transitions 'running' -> 'waiting' when none has arrived.
|
|
142
|
+
deps.assertActive();
|
|
133
143
|
const park = deps.store.parkForSignal(exec.id, node.event);
|
|
134
144
|
if (park.signalPresent) {
|
|
135
145
|
exec.signals = park.signals;
|
|
@@ -143,8 +153,10 @@ export async function runWaitFor(deps, exec, node, idx, wf) {
|
|
|
143
153
|
}
|
|
144
154
|
exec.state = 'waiting';
|
|
145
155
|
if (node.timeout !== undefined) {
|
|
156
|
+
deps.assertActive();
|
|
146
157
|
deps.scheduleTimeoutCheck(exec.id, exec.workflowName, idx, remaining);
|
|
147
158
|
}
|
|
159
|
+
deps.assertActive();
|
|
148
160
|
deps.emitter?.emitWorkflow('workflow:waiting', exec.id, exec.workflowName, 'waiting');
|
|
149
161
|
throw new WaitForSignalError(node.event);
|
|
150
162
|
}
|
|
@@ -4,7 +4,7 @@ import type { Execution } from './types';
|
|
|
4
4
|
* Return an already persisted decision, or evaluate and persist it before the caller
|
|
5
5
|
* performs any effects selected by that decision.
|
|
6
6
|
*/
|
|
7
|
-
export declare function resolveDecision<T>(exec: Execution, key: string, evaluate: () => T | Promise<T>, updateFn: (exec: Execution) => void): Promise<T>;
|
|
7
|
+
export declare function resolveDecision<T>(exec: Execution, key: string, evaluate: () => T | Promise<T>, updateFn: (exec: Execution) => void, assertActive: () => void): Promise<T>;
|
|
8
8
|
export declare function branchDecisionKey(nodeIndex: number): string;
|
|
9
9
|
export declare function forEachItemsDecisionKey(stepName: string): string;
|
|
10
10
|
export declare function loopDecisionKey(kind: 'doUntil' | 'doWhile', firstStepName: string, iteration: number): string;
|
|
@@ -4,11 +4,13 @@ const hasOwn = (value, key) => Object.hasOwn(value, key);
|
|
|
4
4
|
* Return an already persisted decision, or evaluate and persist it before the caller
|
|
5
5
|
* performs any effects selected by that decision.
|
|
6
6
|
*/
|
|
7
|
-
export async function resolveDecision(exec, key, evaluate, updateFn) {
|
|
7
|
+
export async function resolveDecision(exec, key, evaluate, updateFn, assertActive) {
|
|
8
|
+
assertActive();
|
|
8
9
|
const decisions = (exec.decisions ??= {});
|
|
9
10
|
if (hasOwn(decisions, key))
|
|
10
11
|
return decisions[key];
|
|
11
12
|
const value = await evaluate();
|
|
13
|
+
assertActive();
|
|
12
14
|
decisions[key] = value;
|
|
13
15
|
updateFn(exec);
|
|
14
16
|
return value;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SqliteLifecycle } from './sqlite/lifecycle';
|
|
2
|
-
export type { SqliteConfig, SqliteCriticalLoss, SqliteCriticalLossCallback
|
|
2
|
+
export type { SqliteConfig, SqliteCriticalLoss, SqliteCriticalLossCallback } from './types/sqlite';
|
|
3
3
|
export type { DurableAdmissionMetadata } from './types/admission';
|
|
4
4
|
/** SQLite persistence façade. Implementation is split by responsibility. */
|
|
5
5
|
export declare class SqliteStorage extends SqliteLifecycle {
|
|
@@ -5,7 +5,6 @@ import { handleGetDeduplicationJobId, handleGetQueueLimits, handleMoveToWaitingC
|
|
|
5
5
|
import { handleCancel, handleGetProgress, handleProgress } from '../handlers/management';
|
|
6
6
|
import { handleExtendLock, handleExtendLocks } from '../handlers/monitoring';
|
|
7
7
|
import { handleGetChildrenValues, handleGetCountsPerPriority, handleGetJob, handleGetJobByCustomId, handleGetJobCounts, handleGetJobs, handleGetResult, handleGetState, } from '../handlers/query';
|
|
8
|
-
// biome-ignore lint/suspicious/useAwait: stable router API always returns a Promise.
|
|
9
8
|
export async function routeCoreCommand(command, context, requestId) {
|
|
10
9
|
switch (command.cmd) {
|
|
11
10
|
case 'PUSH':
|
|
@@ -28,7 +27,6 @@ export async function routeCoreCommand(command, context, requestId) {
|
|
|
28
27
|
return null;
|
|
29
28
|
}
|
|
30
29
|
}
|
|
31
|
-
// biome-ignore lint/suspicious/useAwait: stable router API always returns a Promise.
|
|
32
30
|
export async function routeQueryCommand(command, context, requestId) {
|
|
33
31
|
switch (command.cmd) {
|
|
34
32
|
case 'GetJob':
|
|
@@ -59,7 +57,6 @@ export async function routeQueryCommand(command, context, requestId) {
|
|
|
59
57
|
return null;
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
|
-
// biome-ignore lint/suspicious/useAwait: stable router API always returns a Promise.
|
|
63
60
|
export async function routeManagementCommand(command, context, requestId) {
|
|
64
61
|
switch (command.cmd) {
|
|
65
62
|
case 'Cancel':
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { routeConfigCommand, routeCronCommand, routeDlqCommand, routeQueueControlCommand, routeRateLimitCommand, } from './handler-routes/control';
|
|
2
|
-
export { routeCoreCommand, routeManagementCommand, routeQueryCommand
|
|
3
|
-
export { routeDashboardCommand, routeMonitoringCommand
|
|
2
|
+
export { routeCoreCommand, routeManagementCommand, routeQueryCommand } from './handler-routes/jobs';
|
|
3
|
+
export { routeDashboardCommand, routeMonitoringCommand } from './handler-routes/monitoring';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { routeConfigCommand, routeCronCommand, routeDlqCommand, routeQueueControlCommand, routeRateLimitCommand, } from './handler-routes/control';
|
|
2
|
-
export { routeCoreCommand, routeManagementCommand, routeQueryCommand
|
|
3
|
-
export { routeDashboardCommand, routeMonitoringCommand
|
|
2
|
+
export { routeCoreCommand, routeManagementCommand, routeQueryCommand } from './handler-routes/jobs';
|
|
3
|
+
export { routeDashboardCommand, routeMonitoringCommand } from './handler-routes/monitoring';
|
|
@@ -84,6 +84,7 @@ export function readinessEndpoint(queueManager, corsOrigins) {
|
|
|
84
84
|
/** GC endpoint - force garbage collection */
|
|
85
85
|
export function gcEndpoint(queueManager) {
|
|
86
86
|
const before = process.memoryUsage();
|
|
87
|
+
// oxlint-disable-next-line typescript/prefer-optional-chain -- the typeof guard avoids referencing an unavailable Bun global
|
|
87
88
|
if (typeof Bun !== 'undefined' && Bun.gc) {
|
|
88
89
|
Bun.gc(true);
|
|
89
90
|
}
|
|
@@ -105,6 +106,7 @@ export function gcEndpoint(queueManager) {
|
|
|
105
106
|
}
|
|
106
107
|
/** Heap stats endpoint - for debugging memory leaks */
|
|
107
108
|
export async function heapStatsEndpoint(queueManager) {
|
|
109
|
+
// oxlint-disable-next-line typescript/prefer-optional-chain -- the typeof guard avoids referencing an unavailable Bun global
|
|
108
110
|
if (typeof Bun !== 'undefined' && Bun.gc) {
|
|
109
111
|
Bun.gc(true);
|
|
110
112
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bunqueue",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.60",
|
|
4
4
|
"description": "High-performance job queue for Bun & AI agents. SQLite persistence, cron scheduling, priorities, retries, DLQ, webhooks, native MCP server. Zero external infrastructure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/main.js",
|
|
@@ -71,13 +71,13 @@
|
|
|
71
71
|
"bench:workflow:scale": "bun run bench/workflow-engine/scale.ts",
|
|
72
72
|
"bench:joblist": "bun run bench/job-list-perf.ts",
|
|
73
73
|
"bench:compare": "bun run bench/comparison/run.ts",
|
|
74
|
-
"lint": "
|
|
75
|
-
"lint:fix": "
|
|
76
|
-
"format": "
|
|
77
|
-
"format:check": "
|
|
74
|
+
"lint": "oxlint src",
|
|
75
|
+
"lint:fix": "oxlint --fix src",
|
|
76
|
+
"format": "oxfmt --write src",
|
|
77
|
+
"format:check": "oxfmt --check src",
|
|
78
78
|
"typecheck": "tsc --noEmit",
|
|
79
|
-
"check:
|
|
80
|
-
"check": "bun run typecheck &&
|
|
79
|
+
"check:oxc": "bun run lint && bun run format:check",
|
|
80
|
+
"check": "bun run typecheck && bun run check:oxc && bun run check:docs-data",
|
|
81
81
|
"prepublishOnly": "bun run build:lib"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
@@ -87,18 +87,20 @@
|
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@ai-sdk/anthropic": "^4.0.22",
|
|
89
89
|
"@anthropic-ai/claude-agent-sdk": "^0.3.220",
|
|
90
|
-
"@biomejs/biome": "2.5.1",
|
|
91
90
|
"@langchain/core": "^1.2.3",
|
|
92
91
|
"@langchain/langgraph": "^1.4.8",
|
|
93
92
|
"@mastra/core": "^1.53.0",
|
|
94
93
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
95
94
|
"@openai/agents": "^0.13.5",
|
|
96
|
-
"@types/bun": "^1.
|
|
95
|
+
"@types/bun": "^1.4.0",
|
|
97
96
|
"ai": "^7.0.38",
|
|
98
97
|
"bullmq": "^5.79.3",
|
|
99
98
|
"elysia": "^1.4.25",
|
|
100
99
|
"fast-check": "^4.9.0",
|
|
101
100
|
"ioredis": "^5.11.1",
|
|
101
|
+
"oxfmt": "0.64.0",
|
|
102
|
+
"oxlint": "1.79.0",
|
|
103
|
+
"oxlint-tsgolint": "7.0.2001",
|
|
102
104
|
"typedoc": "^0.28.20",
|
|
103
105
|
"typescript": "^5.9.3",
|
|
104
106
|
"zod": "^4.3.6"
|
|
@@ -162,6 +164,6 @@
|
|
|
162
164
|
},
|
|
163
165
|
"homepage": "https://bunqueue.dev",
|
|
164
166
|
"engines": {
|
|
165
|
-
"bun": ">=1.
|
|
167
|
+
"bun": ">=1.4.0"
|
|
166
168
|
}
|
|
167
169
|
}
|