@zudojs/queue 0.0.1 → 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
package/dist/queue/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@
|
|
|
5
5
|
* and managing job queues.
|
|
6
6
|
*/
|
|
7
7
|
export { createQueue, isQueue } from "./queue.core.js";
|
|
8
|
-
export type { Queue, QueueOptions, QueueStats, QueueEventMap, } from "./queue.type.js";
|
|
8
|
+
export type { Queue, QueueLogger, QueueOptions, QueueStats, QueueEventMap, } from "./queue.type.js";
|
|
9
9
|
export type { QueueEventEmitter } from "../queueEmitter/queueEmitter.type.js";
|
|
10
10
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/queue/queue.core.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { QueueClosedError, QueueDisposedError } from "@zudojs/errors";
|
|
2
1
|
import { createInMemoryQueue } from "../inMemoryQueue/index.js";
|
|
3
2
|
/**
|
|
4
3
|
* Creates a new queue.
|
|
@@ -18,6 +17,7 @@ export function isQueue(value) {
|
|
|
18
17
|
"add" in value &&
|
|
19
18
|
"process" in value &&
|
|
20
19
|
"getJob" in value &&
|
|
21
|
-
"getNextJob" in value
|
|
20
|
+
"getNextJob" in value &&
|
|
21
|
+
"claimNextJob" in value);
|
|
22
22
|
}
|
|
23
23
|
//# sourceMappingURL=queue.core.js.map
|
|
@@ -5,10 +5,26 @@ import type { Processor } from "../processor/processor.type.js";
|
|
|
5
5
|
import type { Serializer } from "../serializer/serializer.type.js";
|
|
6
6
|
import type { QueueMiddleware } from "../middleware/middleware.type.js";
|
|
7
7
|
import type { QueueEventEmitter } from "../queueEmitter/queueEmitter.type.js";
|
|
8
|
+
import type { DeadLetterJob, DeadLetterStore } from "../deadLetter/deadLetter.type.js";
|
|
9
|
+
/**
|
|
10
|
+
* Somewhere for a job to write a log line.
|
|
11
|
+
*
|
|
12
|
+
* Structurally compatible with `@zudojs/logger` and with `console`.
|
|
13
|
+
*/
|
|
14
|
+
export interface QueueLogger {
|
|
15
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
18
|
* Options for creating a queue.
|
|
10
19
|
*/
|
|
11
20
|
export interface QueueOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Receives lines a processor writes with `context.log()`.
|
|
23
|
+
*
|
|
24
|
+
* Without one, `JobContext.log` is a documented method that discards
|
|
25
|
+
* everything handed to it.
|
|
26
|
+
*/
|
|
27
|
+
readonly logger?: QueueLogger;
|
|
12
28
|
/** Queue concurrency limit. */
|
|
13
29
|
readonly concurrency?: number;
|
|
14
30
|
/** Default job options. */
|
|
@@ -21,6 +37,48 @@ export interface QueueOptions {
|
|
|
21
37
|
readonly pollInterval?: number;
|
|
22
38
|
/** Event emitter for queue lifecycle events. */
|
|
23
39
|
readonly eventEmitter?: QueueEventEmitter;
|
|
40
|
+
/** Store that receives jobs which exhausted their attempts. */
|
|
41
|
+
readonly deadLetterStore?: DeadLetterStore<never>;
|
|
42
|
+
/**
|
|
43
|
+
* Whether `add()` rejects while the queue is paused.
|
|
44
|
+
*
|
|
45
|
+
* Defaults to `true`. Set to `false` for the conventional
|
|
46
|
+
* producer/consumer split, where pausing stops consumption only and
|
|
47
|
+
* producers may keep enqueuing.
|
|
48
|
+
*/
|
|
49
|
+
readonly pauseRejectsAdd?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Whether job payloads are round-tripped through the serializer on
|
|
52
|
+
* `add()`. Defaults to `true`, which isolates stored payloads from
|
|
53
|
+
* later caller mutation and rejects non-serializable payloads up
|
|
54
|
+
* front. Set to `false` to store payloads by reference.
|
|
55
|
+
*/
|
|
56
|
+
readonly serializePayloads?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Number of terminal (completed, failed, dead-lettered) jobs retained
|
|
59
|
+
* before the oldest are evicted. Defaults to 1000. Without a bound the
|
|
60
|
+
* queue grows for the life of the process.
|
|
61
|
+
*/
|
|
62
|
+
readonly retainSettledJobs?: number;
|
|
63
|
+
/**
|
|
64
|
+
* How long `close()` waits for in-flight jobs before aborting them,
|
|
65
|
+
* in milliseconds. Defaults to 30000.
|
|
66
|
+
*/
|
|
67
|
+
readonly closeTimeout?: number;
|
|
68
|
+
/**
|
|
69
|
+
* How long a job may sit in `active` without a live consumer before it
|
|
70
|
+
* is reclaimed, in milliseconds. Defaults to `0` (disabled).
|
|
71
|
+
*
|
|
72
|
+
* Guards against a consumer that claimed a job and then died, which
|
|
73
|
+
* would otherwise leave the job `active` with nothing able to retry
|
|
74
|
+
* it.
|
|
75
|
+
*/
|
|
76
|
+
readonly stalledAfter?: number;
|
|
77
|
+
/**
|
|
78
|
+
* How many times a job may stall before it is dead-lettered instead of
|
|
79
|
+
* reclaimed again. Defaults to 3.
|
|
80
|
+
*/
|
|
81
|
+
readonly maxStalledCount?: number;
|
|
24
82
|
}
|
|
25
83
|
/**
|
|
26
84
|
* Statistics for a queue.
|
|
@@ -38,6 +96,16 @@ export interface QueueStats {
|
|
|
38
96
|
readonly delayed: number;
|
|
39
97
|
/** Number of retrying jobs. */
|
|
40
98
|
readonly retrying: number;
|
|
99
|
+
/** Total jobs dispatched over the queue's lifetime. */
|
|
100
|
+
readonly processed: number;
|
|
101
|
+
/** Total jobs that completed successfully. */
|
|
102
|
+
readonly succeeded: number;
|
|
103
|
+
/** Total jobs that failed terminally. */
|
|
104
|
+
readonly errored: number;
|
|
105
|
+
/** Total retry attempts scheduled. */
|
|
106
|
+
readonly retried: number;
|
|
107
|
+
/** Total jobs moved to the dead letter store. */
|
|
108
|
+
readonly deadLettered: number;
|
|
41
109
|
}
|
|
42
110
|
/**
|
|
43
111
|
* A named stream of jobs.
|
|
@@ -51,8 +119,42 @@ export interface Queue<TData = unknown> {
|
|
|
51
119
|
process(name: string, processor: Processor<TData>): void;
|
|
52
120
|
/** Get a job by ID. */
|
|
53
121
|
getJob(jobId: JobId): Promise<Job<TData> | null>;
|
|
54
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Peeks at the job that would be processed next, without claiming it.
|
|
124
|
+
*
|
|
125
|
+
* Consumers that intend to run the job must use {@link claimNextJob};
|
|
126
|
+
* peeking leaves the job `waiting`, so two consumers polling with this
|
|
127
|
+
* method would both run it.
|
|
128
|
+
*/
|
|
55
129
|
getNextJob(): Promise<Job<TData> | null>;
|
|
130
|
+
/**
|
|
131
|
+
* Atomically claims the next runnable job, marking it `active`.
|
|
132
|
+
*
|
|
133
|
+
* Returns `null` when nothing is runnable — including when a waiting
|
|
134
|
+
* job has no registered processor.
|
|
135
|
+
*/
|
|
136
|
+
claimNextJob(): Promise<Job<TData> | null>;
|
|
137
|
+
/**
|
|
138
|
+
* Returns a claimed job to the waiting pool.
|
|
139
|
+
*
|
|
140
|
+
* Used when a consumer claims a job it turns out it cannot run, so the
|
|
141
|
+
* job is retried by another consumer rather than stranded in `active`.
|
|
142
|
+
*/
|
|
143
|
+
releaseJob(jobId: JobId): Promise<boolean>;
|
|
144
|
+
/**
|
|
145
|
+
* Runs an already-claimed job through the queue's processing pipeline.
|
|
146
|
+
*
|
|
147
|
+
* The queue owns job state, so consumers must dispatch through this
|
|
148
|
+
* rather than invoking a processor themselves — doing so would leave
|
|
149
|
+
* the job stuck in `active` and skip retry, dead-lettering and
|
|
150
|
+
* middleware.
|
|
151
|
+
*/
|
|
152
|
+
runJob(job: Job<TData>, options?: {
|
|
153
|
+
middleware?: readonly QueueMiddleware[];
|
|
154
|
+
signal?: AbortSignal;
|
|
155
|
+
/** Fallback timeout for a job that carries none of its own. */
|
|
156
|
+
timeoutMs?: number;
|
|
157
|
+
}): Promise<void>;
|
|
56
158
|
/** Get a registered processor by job name. */
|
|
57
159
|
getProcessor(name: string): Processor<TData> | undefined;
|
|
58
160
|
/** Get queue statistics. */
|
|
@@ -63,7 +165,11 @@ export interface Queue<TData = unknown> {
|
|
|
63
165
|
resume(): Promise<void>;
|
|
64
166
|
/** Check if the queue is paused. */
|
|
65
167
|
isPaused(): boolean;
|
|
66
|
-
/**
|
|
168
|
+
/** Check if the queue has been closed. */
|
|
169
|
+
isDisposed(): boolean;
|
|
170
|
+
/** Get the jobs that exhausted their attempts. */
|
|
171
|
+
getDeadLetterJobs(): Promise<readonly DeadLetterJob<TData>[]>;
|
|
172
|
+
/** Close the queue, draining in-flight jobs first. */
|
|
67
173
|
close(): Promise<void>;
|
|
68
174
|
}
|
|
69
175
|
/**
|
|
@@ -2,20 +2,40 @@ import type { QueueEventEmitter } from "./queueEmitter.type.js";
|
|
|
2
2
|
import type { QueueEventMap } from "../queue/queue.type.js";
|
|
3
3
|
type EventName = keyof QueueEventMap;
|
|
4
4
|
type Handler<T extends EventName> = (data: QueueEventMap[T]) => void;
|
|
5
|
+
/**
|
|
6
|
+
* Options for the in-memory queue event emitter.
|
|
7
|
+
*/
|
|
8
|
+
export interface QueueEventEmitterOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Invoked when a handler throws. Defaults to reporting the error on the
|
|
11
|
+
* next tick so it surfaces in normal error reporting without unwinding
|
|
12
|
+
* the emitting code path.
|
|
13
|
+
*/
|
|
14
|
+
readonly onHandlerError?: (error: unknown, event: EventName) => void;
|
|
15
|
+
}
|
|
5
16
|
/**
|
|
6
17
|
* In-memory queue event emitter.
|
|
7
18
|
*
|
|
8
|
-
* Stores handlers in memory and emits events synchronously.
|
|
19
|
+
* Stores handlers in memory and emits events synchronously. A handler
|
|
20
|
+
* that throws is isolated: the remaining handlers still run, and the
|
|
21
|
+
* failure never propagates back into queue processing, where it would be
|
|
22
|
+
* misreported as a job failure.
|
|
9
23
|
*/
|
|
10
24
|
export declare class InMemoryQueueEventEmitter implements QueueEventEmitter {
|
|
11
25
|
private readonly handlers;
|
|
26
|
+
private readonly onHandlerError;
|
|
27
|
+
constructor(options?: QueueEventEmitterOptions);
|
|
12
28
|
emit<K extends EventName>(event: K, data: QueueEventMap[K]): void;
|
|
13
29
|
on<K extends EventName>(event: K, handler: Handler<K>): () => void;
|
|
30
|
+
/**
|
|
31
|
+
* Removes every registered handler.
|
|
32
|
+
*/
|
|
33
|
+
removeAllListeners(): void;
|
|
14
34
|
}
|
|
15
35
|
/**
|
|
16
36
|
* Creates an in-memory queue event emitter.
|
|
17
37
|
*/
|
|
18
|
-
export declare function createInMemoryQueueEventEmitter(): QueueEventEmitter;
|
|
38
|
+
export declare function createInMemoryQueueEventEmitter(options?: QueueEventEmitterOptions): QueueEventEmitter;
|
|
19
39
|
/**
|
|
20
40
|
* Creates a no-op queue event emitter.
|
|
21
41
|
*/
|
|
@@ -1,16 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* In-memory queue event emitter.
|
|
3
3
|
*
|
|
4
|
-
* Stores handlers in memory and emits events synchronously.
|
|
4
|
+
* Stores handlers in memory and emits events synchronously. A handler
|
|
5
|
+
* that throws is isolated: the remaining handlers still run, and the
|
|
6
|
+
* failure never propagates back into queue processing, where it would be
|
|
7
|
+
* misreported as a job failure.
|
|
5
8
|
*/
|
|
6
9
|
export class InMemoryQueueEventEmitter {
|
|
7
10
|
handlers = new Map();
|
|
11
|
+
onHandlerError;
|
|
12
|
+
constructor(options = {}) {
|
|
13
|
+
this.onHandlerError =
|
|
14
|
+
options.onHandlerError ??
|
|
15
|
+
((error, event) => {
|
|
16
|
+
queueMicrotask(() => {
|
|
17
|
+
console.error(`[@zudojs/queue] Listener for "${event}" threw.`, error);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
}
|
|
8
21
|
emit(event, data) {
|
|
9
22
|
const handlers = this.handlers.get(event);
|
|
10
|
-
if (handlers) {
|
|
11
|
-
|
|
23
|
+
if (!handlers) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Snapshot so a handler that subscribes or unsubscribes during
|
|
27
|
+
// dispatch cannot mutate the set being iterated.
|
|
28
|
+
for (const handler of [...handlers]) {
|
|
29
|
+
try {
|
|
12
30
|
handler(data);
|
|
13
31
|
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
this.onHandlerError(error, event);
|
|
34
|
+
}
|
|
14
35
|
}
|
|
15
36
|
}
|
|
16
37
|
on(event, handler) {
|
|
@@ -19,17 +40,36 @@ export class InMemoryQueueEventEmitter {
|
|
|
19
40
|
handlers = new Set();
|
|
20
41
|
this.handlers.set(event, handlers);
|
|
21
42
|
}
|
|
22
|
-
|
|
43
|
+
const registered = handler;
|
|
44
|
+
handlers.add(registered);
|
|
45
|
+
let removed = false;
|
|
23
46
|
return () => {
|
|
24
|
-
|
|
47
|
+
if (removed) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
removed = true;
|
|
51
|
+
const current = this.handlers.get(event);
|
|
52
|
+
if (!current) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
current.delete(registered);
|
|
56
|
+
if (current.size === 0) {
|
|
57
|
+
this.handlers.delete(event);
|
|
58
|
+
}
|
|
25
59
|
};
|
|
26
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* Removes every registered handler.
|
|
63
|
+
*/
|
|
64
|
+
removeAllListeners() {
|
|
65
|
+
this.handlers.clear();
|
|
66
|
+
}
|
|
27
67
|
}
|
|
28
68
|
/**
|
|
29
69
|
* Creates an in-memory queue event emitter.
|
|
30
70
|
*/
|
|
31
|
-
export function createInMemoryQueueEventEmitter() {
|
|
32
|
-
return new InMemoryQueueEventEmitter();
|
|
71
|
+
export function createInMemoryQueueEventEmitter(options) {
|
|
72
|
+
return new InMemoryQueueEventEmitter(options);
|
|
33
73
|
}
|
|
34
74
|
/**
|
|
35
75
|
* Creates a no-op queue event emitter.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { QueueError } from "@zudojs/errors";
|
|
1
2
|
import { createQueue } from "../queue/queue.core.js";
|
|
2
3
|
/**
|
|
3
4
|
* Creates a new QueueManager.
|
|
@@ -5,12 +6,23 @@ import { createQueue } from "../queue/queue.core.js";
|
|
|
5
6
|
export function createQueueManager() {
|
|
6
7
|
const queues = new Map();
|
|
7
8
|
return {
|
|
9
|
+
/**
|
|
10
|
+
* Returns the queue with this name, creating it on first request.
|
|
11
|
+
*
|
|
12
|
+
* `options` apply only when the queue is created. Passing different
|
|
13
|
+
* options for an existing queue throws rather than silently
|
|
14
|
+
* returning a queue configured some other way.
|
|
15
|
+
*/
|
|
8
16
|
getQueue(name, options) {
|
|
9
|
-
|
|
10
|
-
if (
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
const existing = queues.get(name);
|
|
18
|
+
if (existing) {
|
|
19
|
+
if (options !== undefined) {
|
|
20
|
+
throw new QueueError(`Queue "${name}" already exists; options are only applied when a queue is created.`, { queueName: name });
|
|
21
|
+
}
|
|
22
|
+
return existing;
|
|
13
23
|
}
|
|
24
|
+
const queue = createQueue(name, options);
|
|
25
|
+
queues.set(name, queue);
|
|
14
26
|
return queue;
|
|
15
27
|
},
|
|
16
28
|
getExistingQueue(name) {
|
|
@@ -22,10 +34,17 @@ export function createQueueManager() {
|
|
|
22
34
|
getQueueNames() {
|
|
23
35
|
return Array.from(queues.keys());
|
|
24
36
|
},
|
|
37
|
+
/**
|
|
38
|
+
* Closes every queue, clearing the registry even if some fail.
|
|
39
|
+
*/
|
|
25
40
|
async closeAll() {
|
|
26
41
|
const closePromises = Array.from(queues.values()).map((queue) => queue.close());
|
|
27
|
-
await Promise.all(closePromises);
|
|
28
42
|
queues.clear();
|
|
43
|
+
const results = await Promise.allSettled(closePromises);
|
|
44
|
+
const failure = results.find((result) => result.status === "rejected");
|
|
45
|
+
if (failure && failure.status === "rejected") {
|
|
46
|
+
throw failure.reason;
|
|
47
|
+
}
|
|
29
48
|
},
|
|
30
49
|
};
|
|
31
50
|
}
|
|
@@ -28,11 +28,26 @@ export function createQueueRegistry() {
|
|
|
28
28
|
return Array.from(infoMap.values());
|
|
29
29
|
},
|
|
30
30
|
unregister(name) {
|
|
31
|
+
const queue = queues.get(name);
|
|
31
32
|
const deleted = queues.delete(name);
|
|
32
33
|
infoMap.delete(name);
|
|
34
|
+
// Dropping the reference without closing would leave the queue's
|
|
35
|
+
// poll timer running with nothing able to stop it.
|
|
36
|
+
if (queue) {
|
|
37
|
+
void queue.close().catch(() => { });
|
|
38
|
+
}
|
|
33
39
|
return deleted;
|
|
34
40
|
},
|
|
41
|
+
async closeAll() {
|
|
42
|
+
const pending = Array.from(queues.values()).map((queue) => queue.close());
|
|
43
|
+
queues.clear();
|
|
44
|
+
infoMap.clear();
|
|
45
|
+
await Promise.allSettled(pending);
|
|
46
|
+
},
|
|
35
47
|
clear() {
|
|
48
|
+
for (const queue of queues.values()) {
|
|
49
|
+
void queue.close().catch(() => { });
|
|
50
|
+
}
|
|
36
51
|
queues.clear();
|
|
37
52
|
infoMap.clear();
|
|
38
53
|
},
|
|
@@ -15,6 +15,8 @@ export interface QueueInfo {
|
|
|
15
15
|
* Registry for queue instances.
|
|
16
16
|
*/
|
|
17
17
|
export interface QueueRegistry {
|
|
18
|
+
/** Close every registered queue and clear the registry. */
|
|
19
|
+
closeAll(): Promise<void>;
|
|
18
20
|
/** Register a queue. */
|
|
19
21
|
register<TData>(queue: Queue<TData>): void;
|
|
20
22
|
/** Get a queue by name. */
|
|
@@ -1,9 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { BackoffStrategy } from "../jobTypes/jobTypes.type.js";
|
|
2
2
|
import type { BackoffOptions } from "../jobOptions/jobOptions.type.js";
|
|
3
|
+
/**
|
|
4
|
+
* Largest delay Node's timer subsystem accepts.
|
|
5
|
+
*
|
|
6
|
+
* A `setTimeout` delay above this overflows a signed 32-bit integer; Node
|
|
7
|
+
* warns and clamps it to `1`, turning a long backoff into an immediate
|
|
8
|
+
* retry. Every computed delay is clamped to this ceiling instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_TIMER_DELAY = 2147483647;
|
|
11
|
+
/**
|
|
12
|
+
* Backoff applied to a retryable job that declares no backoff of its own.
|
|
13
|
+
*
|
|
14
|
+
* Without this a job configured with `attempts > 1` and no `backoff` would
|
|
15
|
+
* retry with a zero delay, hammering whatever dependency just failed.
|
|
16
|
+
*/
|
|
17
|
+
export declare const DEFAULT_RETRY_BACKOFF: BackoffOptions;
|
|
18
|
+
/**
|
|
19
|
+
* Applies the configured jitter strategy to a computed delay.
|
|
20
|
+
*
|
|
21
|
+
* `full` spreads retries uniformly across `[0, delay]`; `equal` keeps half
|
|
22
|
+
* the delay fixed and randomises the other half. Both break up the
|
|
23
|
+
* synchronised retry waves that a purely deterministic backoff produces
|
|
24
|
+
* when many jobs fail against the same dependency at the same moment.
|
|
25
|
+
*/
|
|
26
|
+
export declare function applyJitter(delay: number, jitter: BackoffOptions["jitter"], random?: () => number): number;
|
|
3
27
|
/**
|
|
4
28
|
* Calculates the delay for the next retry attempt.
|
|
29
|
+
*
|
|
30
|
+
* The result is always a finite, non-negative number no greater than
|
|
31
|
+
* {@link MAX_TIMER_DELAY}. Jitter is applied only when the backoff opts
|
|
32
|
+
* into it, so a backoff without `jitter` stays deterministic.
|
|
5
33
|
*/
|
|
6
34
|
export declare function calculateRetryDelay(attempt: number, backoff?: BackoffOptions): number;
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the backoff to use for a job.
|
|
37
|
+
*
|
|
38
|
+
* Falls back to {@link DEFAULT_RETRY_BACKOFF} so that a retryable job
|
|
39
|
+
* never retries with a zero delay.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resolveBackoff(backoff?: BackoffOptions): BackoffOptions;
|
|
7
42
|
/**
|
|
8
43
|
* Checks if a job should be retried based on its state.
|
|
9
44
|
*/
|
|
@@ -11,19 +46,23 @@ export declare function shouldRetry(attempt: number, maxAttempts: number): boole
|
|
|
11
46
|
/**
|
|
12
47
|
* Creates a backoff options object.
|
|
13
48
|
*/
|
|
14
|
-
export declare function createBackoffOptions(type:
|
|
49
|
+
export declare function createBackoffOptions(type: BackoffStrategy, delay: number, options?: {
|
|
15
50
|
maxDelay?: number;
|
|
16
51
|
multiplier?: number;
|
|
52
|
+
jitter?: BackoffOptions["jitter"];
|
|
17
53
|
}): BackoffOptions;
|
|
18
54
|
/**
|
|
19
55
|
* Creates a fixed backoff options.
|
|
20
56
|
*/
|
|
21
|
-
export declare function createFixedBackoff(delay: number
|
|
57
|
+
export declare function createFixedBackoff(delay: number, options?: {
|
|
58
|
+
jitter?: BackoffOptions["jitter"];
|
|
59
|
+
}): BackoffOptions;
|
|
22
60
|
/**
|
|
23
61
|
* Creates an exponential backoff options.
|
|
24
62
|
*/
|
|
25
63
|
export declare function createExponentialBackoff(delay: number, options?: {
|
|
26
64
|
maxDelay?: number;
|
|
27
65
|
multiplier?: number;
|
|
66
|
+
jitter?: BackoffOptions["jitter"];
|
|
28
67
|
}): BackoffOptions;
|
|
29
68
|
//# sourceMappingURL=retryPolicy.core.d.ts.map
|
|
@@ -1,23 +1,91 @@
|
|
|
1
1
|
import { BackoffType } from "../jobTypes/jobTypes.type.js";
|
|
2
|
+
/**
|
|
3
|
+
* Largest delay Node's timer subsystem accepts.
|
|
4
|
+
*
|
|
5
|
+
* A `setTimeout` delay above this overflows a signed 32-bit integer; Node
|
|
6
|
+
* warns and clamps it to `1`, turning a long backoff into an immediate
|
|
7
|
+
* retry. Every computed delay is clamped to this ceiling instead.
|
|
8
|
+
*/
|
|
9
|
+
export const MAX_TIMER_DELAY = 2_147_483_647;
|
|
10
|
+
/**
|
|
11
|
+
* Backoff applied to a retryable job that declares no backoff of its own.
|
|
12
|
+
*
|
|
13
|
+
* Without this a job configured with `attempts > 1` and no `backoff` would
|
|
14
|
+
* retry with a zero delay, hammering whatever dependency just failed.
|
|
15
|
+
*/
|
|
16
|
+
export const DEFAULT_RETRY_BACKOFF = Object.freeze({
|
|
17
|
+
type: BackoffType.EXPONENTIAL,
|
|
18
|
+
delay: 1_000,
|
|
19
|
+
maxDelay: 30_000,
|
|
20
|
+
multiplier: 2,
|
|
21
|
+
jitter: "full",
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Clamps a delay to the range a timer can actually represent.
|
|
25
|
+
*/
|
|
26
|
+
function clampDelay(delay) {
|
|
27
|
+
if (!Number.isFinite(delay) || delay < 0) {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
return Math.min(delay, MAX_TIMER_DELAY);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Applies the configured jitter strategy to a computed delay.
|
|
34
|
+
*
|
|
35
|
+
* `full` spreads retries uniformly across `[0, delay]`; `equal` keeps half
|
|
36
|
+
* the delay fixed and randomises the other half. Both break up the
|
|
37
|
+
* synchronised retry waves that a purely deterministic backoff produces
|
|
38
|
+
* when many jobs fail against the same dependency at the same moment.
|
|
39
|
+
*/
|
|
40
|
+
export function applyJitter(delay, jitter, random = Math.random) {
|
|
41
|
+
if (delay <= 0) {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
switch (jitter) {
|
|
45
|
+
case "full":
|
|
46
|
+
return Math.round(random() * delay);
|
|
47
|
+
case "equal":
|
|
48
|
+
return Math.round(delay / 2 + random() * (delay / 2));
|
|
49
|
+
default:
|
|
50
|
+
return delay;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
2
53
|
/**
|
|
3
54
|
* Calculates the delay for the next retry attempt.
|
|
55
|
+
*
|
|
56
|
+
* The result is always a finite, non-negative number no greater than
|
|
57
|
+
* {@link MAX_TIMER_DELAY}. Jitter is applied only when the backoff opts
|
|
58
|
+
* into it, so a backoff without `jitter` stays deterministic.
|
|
4
59
|
*/
|
|
5
60
|
export function calculateRetryDelay(attempt, backoff) {
|
|
6
61
|
if (!backoff) {
|
|
7
62
|
return 0;
|
|
8
63
|
}
|
|
9
|
-
const { type, delay, maxDelay, multiplier } = backoff;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
64
|
+
const { type, delay, maxDelay, multiplier, jitter } = backoff;
|
|
65
|
+
const base = () => {
|
|
66
|
+
switch (type) {
|
|
67
|
+
case BackoffType.FIXED:
|
|
68
|
+
return delay;
|
|
69
|
+
case BackoffType.EXPONENTIAL: {
|
|
70
|
+
const multiplierValue = multiplier ?? 2;
|
|
71
|
+
const exponent = Math.max(0, attempt - 1);
|
|
72
|
+
const calculatedDelay = delay * Math.pow(multiplierValue, exponent);
|
|
73
|
+
return maxDelay ? Math.min(calculatedDelay, maxDelay) : calculatedDelay;
|
|
74
|
+
}
|
|
75
|
+
default:
|
|
76
|
+
return delay;
|
|
17
77
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
78
|
+
};
|
|
79
|
+
return clampDelay(applyJitter(clampDelay(base()), jitter));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Resolves the backoff to use for a job.
|
|
83
|
+
*
|
|
84
|
+
* Falls back to {@link DEFAULT_RETRY_BACKOFF} so that a retryable job
|
|
85
|
+
* never retries with a zero delay.
|
|
86
|
+
*/
|
|
87
|
+
export function resolveBackoff(backoff) {
|
|
88
|
+
return backoff ?? DEFAULT_RETRY_BACKOFF;
|
|
21
89
|
}
|
|
22
90
|
/**
|
|
23
91
|
* Checks if a job should be retried based on its state.
|
|
@@ -34,13 +102,14 @@ export function createBackoffOptions(type, delay, options) {
|
|
|
34
102
|
delay,
|
|
35
103
|
maxDelay: options?.maxDelay,
|
|
36
104
|
multiplier: options?.multiplier,
|
|
105
|
+
jitter: options?.jitter,
|
|
37
106
|
};
|
|
38
107
|
}
|
|
39
108
|
/**
|
|
40
109
|
* Creates a fixed backoff options.
|
|
41
110
|
*/
|
|
42
|
-
export function createFixedBackoff(delay) {
|
|
43
|
-
return createBackoffOptions(BackoffType.FIXED, delay);
|
|
111
|
+
export function createFixedBackoff(delay, options) {
|
|
112
|
+
return createBackoffOptions(BackoffType.FIXED, delay, options);
|
|
44
113
|
}
|
|
45
114
|
/**
|
|
46
115
|
* Creates an exponential backoff options.
|
|
@@ -10,8 +10,9 @@ export declare const JsonSerializer: Serializer;
|
|
|
10
10
|
* Creates a serializer with custom options.
|
|
11
11
|
*/
|
|
12
12
|
export declare function createJsonSerializer(options?: {
|
|
13
|
-
|
|
13
|
+
/** Indentation width. Any value turns on pretty-printing. */
|
|
14
14
|
space?: number;
|
|
15
|
+
/** Preserve BigInt, Date, Map, Set and Uint8Array across the round trip. */
|
|
15
16
|
preserveTypes?: boolean;
|
|
16
17
|
}): Serializer;
|
|
17
18
|
/** No-op serializer that passes data through unchanged. */
|
|
@@ -2,6 +2,10 @@ import type { Queue } from "../queue/queue.type.js";
|
|
|
2
2
|
import type { Worker, WorkerOptions } from "./worker.type.js";
|
|
3
3
|
/**
|
|
4
4
|
* Creates a new Worker.
|
|
5
|
+
*
|
|
6
|
+
* The worker claims each job before running it, so a job is never picked
|
|
7
|
+
* up twice — by this worker on its next poll, or by another worker on the
|
|
8
|
+
* same queue.
|
|
5
9
|
*/
|
|
6
10
|
export declare function createWorker<TData>(id: string, queue: Queue<TData>, options?: WorkerOptions): Worker<TData>;
|
|
7
11
|
/**
|