@zudojs/queue 0.1.0 → 1.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/LICENSE +21 -0
- package/README.md +40 -8
- package/dist/deadLetter/deadLetter.core.js +1 -1
- package/dist/inMemoryQueue/inMemoryQueue.core.d.ts +95 -4
- package/dist/inMemoryQueue/inMemoryQueue.core.js +398 -73
- package/dist/inMemoryQueue/inMemoryQueue.processing.d.ts +53 -8
- package/dist/inMemoryQueue/inMemoryQueue.processing.js +83 -36
- package/dist/inMemoryQueue/inMemoryQueue.scheduling.d.ts +17 -1
- package/dist/inMemoryQueue/inMemoryQueue.scheduling.js +43 -11
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -2
- package/dist/job/job.core.js +20 -12
- package/dist/job/job.type.d.ts +3 -2
- package/dist/jobOptions/jobOptions.type.d.ts +12 -3
- package/dist/jobTypes/index.d.ts +1 -1
- package/dist/jobTypes/jobTypes.type.d.ts +9 -0
- package/dist/middleware/middleware.core.d.ts +10 -1
- package/dist/middleware/middleware.core.js +40 -17
- package/dist/processor/index.d.ts +1 -1
- package/dist/processor/index.js +1 -1
- package/dist/processor/processor.core.js +2 -0
- package/dist/processor/processor.type.d.ts +18 -1
- package/dist/processor/processor.type.js +15 -0
- package/dist/queue/index.d.ts +1 -1
- package/dist/queue/queue.core.js +2 -2
- package/dist/queue/queue.type.d.ts +108 -2
- package/dist/queueEmitter/queueEmitter.core.d.ts +22 -2
- package/dist/queueEmitter/queueEmitter.core.js +47 -7
- package/dist/queueManager/queueManager.core.js +24 -5
- package/dist/queueRegistry/queueRegistry.core.js +15 -0
- package/dist/queueRegistry/queueRegistry.type.d.ts +2 -0
- package/dist/retryPolicy/retryPolicy.core.d.ts +42 -3
- package/dist/retryPolicy/retryPolicy.core.js +82 -13
- package/dist/serializer/serializer.core.d.ts +2 -1
- package/dist/worker/worker.core.d.ts +4 -0
- package/dist/worker/worker.core.js +87 -23
- package/dist/worker/worker.type.d.ts +17 -3
- package/package.json +21 -14
- package/dist/.tsbuildinfo +0 -1
- package/dist/deadLetter/deadLetter.core.d.ts.map +0 -1
- package/dist/deadLetter/deadLetter.core.js.map +0 -1
- package/dist/deadLetter/deadLetter.type.d.ts.map +0 -1
- package/dist/deadLetter/deadLetter.type.js.map +0 -1
- package/dist/deadLetter/index.d.ts.map +0 -1
- package/dist/deadLetter/index.js.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.core.d.ts.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.core.js.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.factory.d.ts.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.factory.js.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.processing.d.ts.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.processing.js.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.scheduling.d.ts.map +0 -1
- package/dist/inMemoryQueue/inMemoryQueue.scheduling.js.map +0 -1
- package/dist/inMemoryQueue/index.d.ts.map +0 -1
- package/dist/inMemoryQueue/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/job/index.d.ts.map +0 -1
- package/dist/job/index.js.map +0 -1
- package/dist/job/job.core.d.ts.map +0 -1
- package/dist/job/job.core.js.map +0 -1
- package/dist/job/job.type.d.ts.map +0 -1
- package/dist/job/job.type.js.map +0 -1
- package/dist/jobContext/index.d.ts.map +0 -1
- package/dist/jobContext/index.js.map +0 -1
- package/dist/jobContext/jobContext.core.d.ts.map +0 -1
- package/dist/jobContext/jobContext.core.js.map +0 -1
- package/dist/jobContext/jobContext.type.d.ts.map +0 -1
- package/dist/jobContext/jobContext.type.js.map +0 -1
- package/dist/jobOptions/index.d.ts.map +0 -1
- package/dist/jobOptions/index.js.map +0 -1
- package/dist/jobOptions/jobOptions.core.d.ts.map +0 -1
- package/dist/jobOptions/jobOptions.core.js.map +0 -1
- package/dist/jobOptions/jobOptions.type.d.ts.map +0 -1
- package/dist/jobOptions/jobOptions.type.js.map +0 -1
- package/dist/jobResult/index.d.ts.map +0 -1
- package/dist/jobResult/index.js.map +0 -1
- package/dist/jobResult/jobResult.core.d.ts.map +0 -1
- package/dist/jobResult/jobResult.core.js.map +0 -1
- package/dist/jobResult/jobResult.type.d.ts.map +0 -1
- package/dist/jobResult/jobResult.type.js.map +0 -1
- package/dist/jobTypes/index.d.ts.map +0 -1
- package/dist/jobTypes/index.js.map +0 -1
- package/dist/jobTypes/jobTypes.type.d.ts.map +0 -1
- package/dist/jobTypes/jobTypes.type.js.map +0 -1
- package/dist/middleware/index.d.ts.map +0 -1
- package/dist/middleware/index.js.map +0 -1
- package/dist/middleware/middleware.core.d.ts.map +0 -1
- package/dist/middleware/middleware.core.js.map +0 -1
- package/dist/middleware/middleware.type.d.ts.map +0 -1
- package/dist/middleware/middleware.type.js.map +0 -1
- package/dist/processor/index.d.ts.map +0 -1
- package/dist/processor/index.js.map +0 -1
- package/dist/processor/processor.core.d.ts.map +0 -1
- package/dist/processor/processor.core.js.map +0 -1
- package/dist/processor/processor.type.d.ts.map +0 -1
- package/dist/processor/processor.type.js.map +0 -1
- package/dist/queue/index.d.ts.map +0 -1
- package/dist/queue/index.js.map +0 -1
- package/dist/queue/queue.core.d.ts.map +0 -1
- package/dist/queue/queue.core.js.map +0 -1
- package/dist/queue/queue.type.d.ts.map +0 -1
- package/dist/queue/queue.type.js.map +0 -1
- package/dist/queueEmitter/index.d.ts.map +0 -1
- package/dist/queueEmitter/index.js.map +0 -1
- package/dist/queueEmitter/queueEmitter.core.d.ts.map +0 -1
- package/dist/queueEmitter/queueEmitter.core.js.map +0 -1
- package/dist/queueEmitter/queueEmitter.type.d.ts.map +0 -1
- package/dist/queueEmitter/queueEmitter.type.js.map +0 -1
- package/dist/queueEvents/index.d.ts +0 -8
- package/dist/queueEvents/index.d.ts.map +0 -1
- package/dist/queueEvents/index.js +0 -7
- package/dist/queueEvents/index.js.map +0 -1
- package/dist/queueEvents/queueEvents.core.d.ts +0 -6
- package/dist/queueEvents/queueEvents.core.d.ts.map +0 -1
- package/dist/queueEvents/queueEvents.core.js +0 -12
- package/dist/queueEvents/queueEvents.core.js.map +0 -1
- package/dist/queueEvents/queueEvents.type.d.ts +0 -97
- package/dist/queueEvents/queueEvents.type.d.ts.map +0 -1
- package/dist/queueEvents/queueEvents.type.js +0 -2
- package/dist/queueEvents/queueEvents.type.js.map +0 -1
- package/dist/queueManager/index.d.ts.map +0 -1
- package/dist/queueManager/index.js.map +0 -1
- package/dist/queueManager/queueManager.core.d.ts.map +0 -1
- package/dist/queueManager/queueManager.core.js.map +0 -1
- package/dist/queueManager/queueManager.type.d.ts.map +0 -1
- package/dist/queueManager/queueManager.type.js.map +0 -1
- package/dist/queueRegistry/index.d.ts.map +0 -1
- package/dist/queueRegistry/index.js.map +0 -1
- package/dist/queueRegistry/queueRegistry.core.d.ts.map +0 -1
- package/dist/queueRegistry/queueRegistry.core.js.map +0 -1
- package/dist/queueRegistry/queueRegistry.type.d.ts.map +0 -1
- package/dist/queueRegistry/queueRegistry.type.js.map +0 -1
- package/dist/retryPolicy/index.d.ts.map +0 -1
- package/dist/retryPolicy/index.js.map +0 -1
- package/dist/retryPolicy/retryPolicy.core.d.ts.map +0 -1
- package/dist/retryPolicy/retryPolicy.core.js.map +0 -1
- package/dist/serializer/index.d.ts.map +0 -1
- package/dist/serializer/index.js.map +0 -1
- package/dist/serializer/serializer.core.d.ts.map +0 -1
- package/dist/serializer/serializer.core.js.map +0 -1
- package/dist/serializer/serializer.type.d.ts.map +0 -1
- package/dist/serializer/serializer.type.js.map +0 -1
- package/dist/worker/index.d.ts.map +0 -1
- package/dist/worker/index.js.map +0 -1
- package/dist/worker/worker.core.d.ts.map +0 -1
- package/dist/worker/worker.core.js.map +0 -1
- package/dist/worker/worker.type.d.ts.map +0 -1
- package/dist/worker/worker.type.js.map +0 -1
|
@@ -1,48 +1,105 @@
|
|
|
1
|
-
import { WorkerState } from "../jobTypes/jobTypes.type.js";
|
|
1
|
+
import { JobState as JobStateEnum, WorkerState, } from "../jobTypes/jobTypes.type.js";
|
|
2
2
|
import { WorkerLifecycleError } from "@zudojs/errors";
|
|
3
|
+
/** How long `stop()` waits for in-flight jobs before forcing a stop. */
|
|
4
|
+
const DEFAULT_DRAIN_TIMEOUT_MS = 30_000;
|
|
3
5
|
/**
|
|
4
6
|
* Creates a new Worker.
|
|
7
|
+
*
|
|
8
|
+
* The worker claims each job before running it, so a job is never picked
|
|
9
|
+
* up twice — by this worker on its next poll, or by another worker on the
|
|
10
|
+
* same queue.
|
|
5
11
|
*/
|
|
6
12
|
export function createWorker(id, queue, options) {
|
|
7
13
|
let state = WorkerState.CREATED;
|
|
8
|
-
|
|
9
|
-
const concurrency = options?.concurrency ?? 1;
|
|
14
|
+
const stats = { processed: 0, succeeded: 0, failed: 0 };
|
|
15
|
+
const concurrency = Math.max(1, options?.concurrency ?? 1);
|
|
10
16
|
const pollInterval = options?.pollInterval ?? 100;
|
|
17
|
+
const drainTimeout = options?.drainTimeout ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
11
18
|
let pollTimer = null;
|
|
12
19
|
let activeJobs = 0;
|
|
13
20
|
let abortController = null;
|
|
21
|
+
const onError = options?.onError ??
|
|
22
|
+
((error) => {
|
|
23
|
+
queueMicrotask(() => {
|
|
24
|
+
console.error(`[@zudojs/queue] Worker "${id}" poll failed.`, error);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
const scheduleNextPoll = (delay) => {
|
|
28
|
+
if (state !== WorkerState.RUNNING) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
pollTimer = setTimeout(runPoll, delay);
|
|
32
|
+
pollTimer.unref?.();
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Wraps `poll` so a rejection can never escape as an unhandled promise
|
|
36
|
+
* rejection — which, under a runtime configured to treat those as
|
|
37
|
+
* fatal, would take down the whole application.
|
|
38
|
+
*/
|
|
39
|
+
const runPoll = () => {
|
|
40
|
+
void poll().catch((error) => {
|
|
41
|
+
onError(error);
|
|
42
|
+
scheduleNextPoll(pollInterval);
|
|
43
|
+
});
|
|
44
|
+
};
|
|
14
45
|
const poll = async () => {
|
|
15
46
|
if (state !== WorkerState.RUNNING || abortController?.signal.aborted) {
|
|
16
47
|
return;
|
|
17
48
|
}
|
|
18
49
|
if (activeJobs >= concurrency) {
|
|
19
|
-
|
|
50
|
+
scheduleNextPoll(pollInterval);
|
|
20
51
|
return;
|
|
21
52
|
}
|
|
22
|
-
const job = await queue.
|
|
53
|
+
const job = await queue.claimNextJob();
|
|
23
54
|
if (!job) {
|
|
24
|
-
|
|
55
|
+
scheduleNextPoll(pollInterval);
|
|
25
56
|
return;
|
|
26
57
|
}
|
|
27
58
|
const proc = queue.getProcessor(job.name);
|
|
28
59
|
if (!proc) {
|
|
29
|
-
|
|
60
|
+
// Claimed but unrunnable: release it rather than stranding it in
|
|
61
|
+
// `active` where nothing would ever pick it up again.
|
|
62
|
+
await queue.releaseJob(job.id);
|
|
63
|
+
scheduleNextPoll(pollInterval);
|
|
30
64
|
return;
|
|
31
65
|
}
|
|
32
66
|
activeJobs++;
|
|
33
67
|
stats.processed++;
|
|
34
68
|
try {
|
|
35
|
-
|
|
36
|
-
|
|
69
|
+
// Dispatch through the queue rather than invoking the processor
|
|
70
|
+
// directly. The queue owns job state, retry, dead-lettering and
|
|
71
|
+
// middleware; calling the processor here left every job the worker
|
|
72
|
+
// ran stuck in `active` forever.
|
|
73
|
+
await queue.runJob(job, {
|
|
74
|
+
...(options?.middleware ? { middleware: options.middleware } : {}),
|
|
75
|
+
...(abortController ? { signal: abortController.signal } : {}),
|
|
76
|
+
...(options?.timeoutMs !== undefined
|
|
77
|
+
? { timeoutMs: options.timeoutMs }
|
|
78
|
+
: {}),
|
|
37
79
|
});
|
|
38
|
-
|
|
80
|
+
const settled = await queue.getJob(job.id);
|
|
81
|
+
if (settled?.state === JobStateEnum.COMPLETED) {
|
|
82
|
+
stats.succeeded++;
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
stats.failed++;
|
|
86
|
+
}
|
|
39
87
|
}
|
|
40
|
-
catch {
|
|
88
|
+
catch (error) {
|
|
41
89
|
stats.failed++;
|
|
90
|
+
onError(error);
|
|
42
91
|
}
|
|
43
92
|
finally {
|
|
44
93
|
activeJobs--;
|
|
45
|
-
|
|
94
|
+
// Poll again immediately while there is capacity, but yield to the
|
|
95
|
+
// event loop first so a saturated queue cannot starve timers.
|
|
96
|
+
scheduleNextPoll(0);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
const clearPollTimer = () => {
|
|
100
|
+
if (pollTimer) {
|
|
101
|
+
clearTimeout(pollTimer);
|
|
102
|
+
pollTimer = null;
|
|
46
103
|
}
|
|
47
104
|
};
|
|
48
105
|
return {
|
|
@@ -60,34 +117,41 @@ export function createWorker(id, queue, options) {
|
|
|
60
117
|
abortController = new AbortController();
|
|
61
118
|
try {
|
|
62
119
|
state = WorkerState.RUNNING;
|
|
63
|
-
|
|
120
|
+
scheduleNextPoll(0);
|
|
64
121
|
}
|
|
65
122
|
catch (error) {
|
|
66
123
|
state = WorkerState.FAILED;
|
|
67
124
|
throw error;
|
|
68
125
|
}
|
|
69
126
|
},
|
|
127
|
+
/**
|
|
128
|
+
* Stops accepting new jobs and waits for in-flight ones to finish.
|
|
129
|
+
*
|
|
130
|
+
* The wait is bounded: past `drainTimeout` the worker force-stops
|
|
131
|
+
* rather than hanging shutdown on a job that never settles.
|
|
132
|
+
*/
|
|
70
133
|
async stop() {
|
|
71
|
-
if (state !== WorkerState.RUNNING) {
|
|
134
|
+
if (state !== WorkerState.RUNNING && state !== WorkerState.STARTING) {
|
|
135
|
+
// Still clear any timer armed before the state moved on.
|
|
136
|
+
clearPollTimer();
|
|
72
137
|
return;
|
|
73
138
|
}
|
|
74
139
|
state = WorkerState.DRAINING;
|
|
140
|
+
clearPollTimer();
|
|
75
141
|
abortController?.abort();
|
|
76
|
-
|
|
77
|
-
|
|
142
|
+
const deadline = Date.now() + Math.max(0, drainTimeout);
|
|
143
|
+
while (activeJobs > 0 && Date.now() < deadline) {
|
|
144
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
78
145
|
}
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
pollTimer = null;
|
|
146
|
+
if (activeJobs > 0) {
|
|
147
|
+
onError(new WorkerLifecycleError(`Worker "${id}" still had ${activeJobs} job(s) in flight after ${drainTimeout}ms; forcing stop.`, { workerId: id }));
|
|
82
148
|
}
|
|
149
|
+
clearPollTimer();
|
|
83
150
|
state = WorkerState.STOPPED;
|
|
84
151
|
},
|
|
85
152
|
async forceStop() {
|
|
86
153
|
abortController?.abort();
|
|
87
|
-
|
|
88
|
-
clearTimeout(pollTimer);
|
|
89
|
-
pollTimer = null;
|
|
90
|
-
}
|
|
154
|
+
clearPollTimer();
|
|
91
155
|
state = WorkerState.STOPPED;
|
|
92
156
|
},
|
|
93
157
|
isRunning() {
|
|
@@ -9,12 +9,26 @@ export interface WorkerOptions {
|
|
|
9
9
|
readonly concurrency?: number;
|
|
10
10
|
/** Poll interval in milliseconds. */
|
|
11
11
|
readonly pollInterval?: number;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Default job timeout in milliseconds, applied to jobs that do not
|
|
14
|
+
* carry their own. Stall detection is a queue-level concern; configure
|
|
15
|
+
* it with `QueueOptions.stalledAfter` and `maxStalledCount`.
|
|
16
|
+
*/
|
|
15
17
|
readonly timeoutMs?: number;
|
|
16
18
|
/** Middleware for job processing. */
|
|
17
19
|
readonly middleware?: QueueMiddleware[];
|
|
20
|
+
/**
|
|
21
|
+
* How long `stop()` waits for in-flight jobs before forcing a stop, in
|
|
22
|
+
* milliseconds. Defaults to 30000. Without a bound, one stuck job
|
|
23
|
+
* hangs shutdown forever.
|
|
24
|
+
*/
|
|
25
|
+
readonly drainTimeout?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Invoked for errors raised outside a job — a failing poll, a job that
|
|
28
|
+
* threw, or a drain that timed out. Defaults to reporting on the
|
|
29
|
+
* console. Poll errors are never left as unhandled rejections.
|
|
30
|
+
*/
|
|
31
|
+
readonly onError?: (error: unknown) => void;
|
|
18
32
|
}
|
|
19
33
|
/**
|
|
20
34
|
* Worker lifecycle states.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/queue",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Background job and asynchronous task infrastructure with in-memory and adapter-based queue implementations.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -15,19 +15,15 @@
|
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
|
-
"dist"
|
|
18
|
+
"dist",
|
|
19
|
+
"!dist/**/*.map",
|
|
20
|
+
"!dist/**/*.tsbuildinfo",
|
|
21
|
+
"!dist/.tsbuildinfo"
|
|
19
22
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc -p tsconfig.json",
|
|
22
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
-
"clean": "rm -rf dist",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"test:watch": "vitest"
|
|
26
|
-
},
|
|
27
23
|
"dependencies": {
|
|
28
|
-
"@zudojs/errors": "
|
|
29
|
-
"@zudojs/constants": "
|
|
30
|
-
"@zudojs/serialization": "
|
|
24
|
+
"@zudojs/errors": "1.0.0",
|
|
25
|
+
"@zudojs/constants": "1.0.0",
|
|
26
|
+
"@zudojs/serialization": "1.0.0"
|
|
31
27
|
},
|
|
32
28
|
"devDependencies": {
|
|
33
29
|
"typescript": "7.0.2",
|
|
@@ -47,8 +43,19 @@
|
|
|
47
43
|
"worker"
|
|
48
44
|
],
|
|
49
45
|
"homepage": "https://github.com/oyinlola-tech/zudo#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
48
|
+
},
|
|
50
49
|
"repository": {
|
|
51
50
|
"type": "git",
|
|
52
|
-
"url": "https://github.com/oyinlola-tech/zudo"
|
|
51
|
+
"url": "https://github.com/oyinlola-tech/zudo",
|
|
52
|
+
"directory": "packages/queue"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
57
|
+
"clean": "rm -rf dist",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest"
|
|
53
60
|
}
|
|
54
|
-
}
|
|
61
|
+
}
|