@basaltkit/queue 1.1.0 → 1.2.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/README.md +14 -0
- package/dist/index.d.ts +35 -1
- package/dist/index.js +81 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -184,6 +184,20 @@ queuePlugin({
|
|
|
184
184
|
|
|
185
185
|
If a job reaches a worker that hasn't registered it, `UnknownJobError` is thrown.
|
|
186
186
|
|
|
187
|
+
### CLI commands (`basalt queue:*`)
|
|
188
|
+
|
|
189
|
+
Registering `queuePlugin` also wires three CLI commands (run via the `@basaltkit/cli` runner):
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
basalt queue:work --queue=default --concurrency=5 # run a worker until Ctrl+C
|
|
193
|
+
basalt queue:stats --queue=billing # waiting/active/completed/failed/delayed
|
|
194
|
+
basalt queue:retry --queue=billing --limit=100 # re-enqueue failed jobs
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`queue:stats` and `queue:retry` need a driver that can introspect job state — the
|
|
198
|
+
**BullMQ** driver (a Redis `connection`). With the inline `sync` driver they
|
|
199
|
+
report the operation as unsupported (it keeps no job state), rather than guessing.
|
|
200
|
+
|
|
187
201
|
### Manual use without a plugin (e.g. in tests)
|
|
188
202
|
|
|
189
203
|
```ts
|
package/dist/index.d.ts
CHANGED
|
@@ -39,6 +39,14 @@ interface DriverCapabilities {
|
|
|
39
39
|
/** Waits `backoff` between retries (vs retrying immediately). */
|
|
40
40
|
backoff: boolean;
|
|
41
41
|
}
|
|
42
|
+
/** Job counts per state, for `basalt queue:stats`. */
|
|
43
|
+
interface QueueStats {
|
|
44
|
+
waiting: number;
|
|
45
|
+
active: number;
|
|
46
|
+
completed: number;
|
|
47
|
+
failed: number;
|
|
48
|
+
delayed: number;
|
|
49
|
+
}
|
|
42
50
|
/** Queue driver contract. BullMQ in production; sync in tests/dev. */
|
|
43
51
|
interface QueueDriver {
|
|
44
52
|
/** Short identifier used in diagnostics (e.g. 'bullmq', 'sync'). */
|
|
@@ -52,6 +60,19 @@ interface QueueDriver {
|
|
|
52
60
|
startWorker(queue: string, options?: {
|
|
53
61
|
concurrency?: number;
|
|
54
62
|
}): void;
|
|
63
|
+
/**
|
|
64
|
+
* Optional: job counts per state, for `basalt queue:stats`. Backends that
|
|
65
|
+
* cannot introspect (e.g. the inline sync driver) omit it — the CLI then
|
|
66
|
+
* reports the operation as unsupported rather than guessing.
|
|
67
|
+
*/
|
|
68
|
+
stats?(queue: string): Promise<QueueStats>;
|
|
69
|
+
/**
|
|
70
|
+
* Optional: re-enqueue failed jobs (`basalt queue:retry`). Returns how many
|
|
71
|
+
* were retried. `limit` caps how many are processed (default driver's choice).
|
|
72
|
+
*/
|
|
73
|
+
retryFailed?(queue: string, options?: {
|
|
74
|
+
limit?: number;
|
|
75
|
+
}): Promise<number>;
|
|
55
76
|
close(): Promise<void>;
|
|
56
77
|
}
|
|
57
78
|
|
|
@@ -77,6 +98,10 @@ declare class BullmqQueueDriver implements QueueDriver {
|
|
|
77
98
|
startWorker(queue: string, options?: {
|
|
78
99
|
concurrency?: number;
|
|
79
100
|
}): void;
|
|
101
|
+
stats(queue: string): Promise<QueueStats>;
|
|
102
|
+
retryFailed(queue: string, options?: {
|
|
103
|
+
limit?: number;
|
|
104
|
+
}): Promise<number>;
|
|
80
105
|
close(): Promise<void>;
|
|
81
106
|
private queue;
|
|
82
107
|
}
|
|
@@ -201,6 +226,15 @@ declare class QueueManager implements JobDispatcher {
|
|
|
201
226
|
work(queue?: string, options?: {
|
|
202
227
|
concurrency?: number;
|
|
203
228
|
}): void;
|
|
229
|
+
/** Job counts per state, or `undefined` if the driver can't introspect. */
|
|
230
|
+
stats(queue?: string): Promise<QueueStats | undefined>;
|
|
231
|
+
/**
|
|
232
|
+
* Re-enqueues failed jobs; returns the count, or `undefined` if the driver
|
|
233
|
+
* doesn't support retrying (e.g. the inline sync driver).
|
|
234
|
+
*/
|
|
235
|
+
retryFailed(queue?: string, options?: {
|
|
236
|
+
limit?: number;
|
|
237
|
+
}): Promise<number | undefined>;
|
|
204
238
|
close(): Promise<void>;
|
|
205
239
|
/** Executes a job received from the driver: validates, restores the context, runs the handler. */
|
|
206
240
|
private execute;
|
|
@@ -281,4 +315,4 @@ interface QueuePluginOptions {
|
|
|
281
315
|
}
|
|
282
316
|
declare function queuePlugin(options?: QueuePluginOptions): _basaltkit_core.BasaltPlugin<unknown>;
|
|
283
317
|
|
|
284
|
-
export { type AddJobOptions, type BullmqDriverOptions, BullmqQueueDriver, type DispatchOptions, type DriverCapabilities, type JobBackoff, type JobDefinition, type JobExecutor, JobNotRegisteredError, type JobRetention, type JobSchema, JobValidationError, QUEUE, type QueueDriver, QueueManager, type QueueManagerOptions, type QueuePluginOptions, type QueuedListenerOptions, SyncQueueDriver, UnknownJobError, UnsupportedJobOptionError, type UnsupportedPolicy, defineJob, queuePlugin, queuedOn };
|
|
318
|
+
export { type AddJobOptions, type BullmqDriverOptions, BullmqQueueDriver, type DispatchOptions, type DriverCapabilities, type JobBackoff, type JobDefinition, type JobExecutor, JobNotRegisteredError, type JobRetention, type JobSchema, JobValidationError, QUEUE, type QueueDriver, QueueManager, type QueueManagerOptions, type QueuePluginOptions, type QueueStats, type QueuedListenerOptions, SyncQueueDriver, UnknownJobError, UnsupportedJobOptionError, type UnsupportedPolicy, defineJob, queuePlugin, queuedOn };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { createToken, definePlugin } from "@basaltkit/core";
|
|
2
|
+
import { createToken, definePlugin, ensureMetadata } from "@basaltkit/core";
|
|
3
3
|
|
|
4
4
|
// src/drivers/bullmq.ts
|
|
5
5
|
import { Queue, Worker } from "bullmq";
|
|
@@ -42,6 +42,26 @@ var BullmqQueueDriver = class {
|
|
|
42
42
|
})
|
|
43
43
|
);
|
|
44
44
|
}
|
|
45
|
+
async stats(queue) {
|
|
46
|
+
const c = await this.queue(queue).getJobCounts("waiting", "active", "completed", "failed", "delayed");
|
|
47
|
+
return {
|
|
48
|
+
waiting: c["waiting"] ?? 0,
|
|
49
|
+
active: c["active"] ?? 0,
|
|
50
|
+
completed: c["completed"] ?? 0,
|
|
51
|
+
failed: c["failed"] ?? 0,
|
|
52
|
+
delayed: c["delayed"] ?? 0
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
async retryFailed(queue, options = {}) {
|
|
56
|
+
const limit = options.limit ?? 1e3;
|
|
57
|
+
const failed = await this.queue(queue).getFailed(0, limit - 1);
|
|
58
|
+
let retried = 0;
|
|
59
|
+
for (const job of failed) {
|
|
60
|
+
await job.retry();
|
|
61
|
+
retried++;
|
|
62
|
+
}
|
|
63
|
+
return retried;
|
|
64
|
+
}
|
|
45
65
|
async close() {
|
|
46
66
|
await Promise.all(this.workers.map((worker) => worker.close()));
|
|
47
67
|
await Promise.all([...this.queues.values()].map((queue) => queue.close()));
|
|
@@ -262,6 +282,17 @@ var QueueManager = class {
|
|
|
262
282
|
work(queue = "default", options = {}) {
|
|
263
283
|
this.driver.startWorker(queue, options);
|
|
264
284
|
}
|
|
285
|
+
/** Job counts per state, or `undefined` if the driver can't introspect. */
|
|
286
|
+
async stats(queue = "default") {
|
|
287
|
+
return this.driver.stats?.(queue);
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Re-enqueues failed jobs; returns the count, or `undefined` if the driver
|
|
291
|
+
* doesn't support retrying (e.g. the inline sync driver).
|
|
292
|
+
*/
|
|
293
|
+
async retryFailed(queue = "default", options = {}) {
|
|
294
|
+
return this.driver.retryFailed?.(queue, options);
|
|
295
|
+
}
|
|
265
296
|
async close() {
|
|
266
297
|
await this.driver.close();
|
|
267
298
|
}
|
|
@@ -310,6 +341,7 @@ function queuePlugin(options = {}) {
|
|
|
310
341
|
return definePlugin({
|
|
311
342
|
name: "basalt:queue",
|
|
312
343
|
register({ container }) {
|
|
344
|
+
registerQueueCommands(container);
|
|
313
345
|
container.singleton(QUEUE, () => {
|
|
314
346
|
const driver = options.driver ?? (options.connection ? new BullmqQueueDriver({ connection: options.connection }) : new SyncQueueDriver());
|
|
315
347
|
const manager = new QueueManager(driver, {
|
|
@@ -335,6 +367,54 @@ function queuePlugin(options = {}) {
|
|
|
335
367
|
}
|
|
336
368
|
});
|
|
337
369
|
}
|
|
370
|
+
function registerQueueCommands(container) {
|
|
371
|
+
const manager = () => container.get(QUEUE);
|
|
372
|
+
const unsupported = "Not supported by the active queue driver \u2014 the inline sync driver keeps no job state. Use the BullMQ driver (a Redis `connection`).";
|
|
373
|
+
ensureMetadata(container).add("commands", {
|
|
374
|
+
name: "queue:work",
|
|
375
|
+
description: "Run a worker that processes jobs for a queue (Ctrl+C to stop)",
|
|
376
|
+
async handle({ io, flags }) {
|
|
377
|
+
const queue = typeof flags["queue"] === "string" ? flags["queue"] : "default";
|
|
378
|
+
const concurrency = typeof flags["concurrency"] === "string" ? Number(flags["concurrency"]) : void 0;
|
|
379
|
+
manager().work(queue, concurrency !== void 0 ? { concurrency } : {});
|
|
380
|
+
io.log(`Worker started on queue "${queue}"${concurrency ? ` (concurrency ${concurrency})` : ""}. Ctrl+C to stop.`);
|
|
381
|
+
await new Promise((resolve) => process.once("SIGINT", resolve));
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
ensureMetadata(container).add("commands", {
|
|
385
|
+
name: "queue:stats",
|
|
386
|
+
description: "Show job counts (waiting/active/completed/failed/delayed) for a queue",
|
|
387
|
+
async handle({
|
|
388
|
+
io,
|
|
389
|
+
flags
|
|
390
|
+
}) {
|
|
391
|
+
const queue = typeof flags["queue"] === "string" ? flags["queue"] : "default";
|
|
392
|
+
const stats = await manager().stats(queue);
|
|
393
|
+
if (!stats) {
|
|
394
|
+
io.log(unsupported);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
io.table([{ queue, ...stats }]);
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
ensureMetadata(container).add("commands", {
|
|
401
|
+
name: "queue:retry",
|
|
402
|
+
description: "Re-enqueue failed jobs on a queue",
|
|
403
|
+
async handle({
|
|
404
|
+
io,
|
|
405
|
+
flags
|
|
406
|
+
}) {
|
|
407
|
+
const queue = typeof flags["queue"] === "string" ? flags["queue"] : "default";
|
|
408
|
+
const limit = typeof flags["limit"] === "string" ? Number(flags["limit"]) : void 0;
|
|
409
|
+
const retried = await manager().retryFailed(queue, limit !== void 0 ? { limit } : {});
|
|
410
|
+
if (retried === void 0) {
|
|
411
|
+
io.log(unsupported);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
io.log(`Re-enqueued ${retried} failed job(s) on "${queue}".`);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
338
418
|
export {
|
|
339
419
|
BullmqQueueDriver,
|
|
340
420
|
JobNotRegisteredError,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basaltkit/queue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Basalt queues on top of BullMQ: declarative jobs with Zod payloads, context propagation (tenant/requestId) to workers and a sync driver for tests.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|