queue-inspector-mcp 0.1.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/LICENSE +21 -0
  3. package/README.md +148 -0
  4. package/dist/backends/asynq-proto.d.ts +16 -0
  5. package/dist/backends/asynq-proto.js +144 -0
  6. package/dist/backends/asynq-proto.js.map +1 -0
  7. package/dist/backends/asynq.d.ts +45 -0
  8. package/dist/backends/asynq.js +183 -0
  9. package/dist/backends/asynq.js.map +1 -0
  10. package/dist/backends/bullmq.d.ts +46 -0
  11. package/dist/backends/bullmq.js +279 -0
  12. package/dist/backends/bullmq.js.map +1 -0
  13. package/dist/backends/index.d.ts +19 -0
  14. package/dist/backends/index.js +57 -0
  15. package/dist/backends/index.js.map +1 -0
  16. package/dist/backends/lua/deleteTask.lua +41 -0
  17. package/dist/backends/lua/removeJob.lua +346 -0
  18. package/dist/backends/lua/reprocessJob.lua +113 -0
  19. package/dist/backends/lua/runTask.lua +39 -0
  20. package/dist/backends/lua.d.ts +18 -0
  21. package/dist/backends/lua.js +18 -0
  22. package/dist/backends/lua.js.map +1 -0
  23. package/dist/backends/scripting.d.ts +14 -0
  24. package/dist/backends/scripting.js +34 -0
  25. package/dist/backends/scripting.js.map +1 -0
  26. package/dist/config.d.ts +10 -0
  27. package/dist/config.js +32 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/format.d.ts +16 -0
  30. package/dist/format.js +49 -0
  31. package/dist/format.js.map +1 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +32 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/redis.d.ts +13 -0
  36. package/dist/redis.js +30 -0
  37. package/dist/redis.js.map +1 -0
  38. package/dist/server.d.ts +7 -0
  39. package/dist/server.js +105 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/types.d.ts +64 -0
  42. package/dist/types.js +14 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +56 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.1.0] - 2026-07-08
8
+
9
+ Initial release.
10
+
11
+ ### Added
12
+
13
+ - MCP server (`stdio`) exposing six tools: `list_queues`, `queue_stats`,
14
+ `list_jobs`, `get_job`, `retry_job`, `delete_job`.
15
+ - Asynq backend. Reads the protobuf `TaskMessage` stored in each task hash and
16
+ reports the pending / active / scheduled / retry / archived / completed states.
17
+ Retry and delete run Asynq's own `RunTask` and `DeleteTask` scripts.
18
+ - BullMQ backend. Reports the waiting / active / delayed / prioritized /
19
+ waiting-children / paused / completed / failed states. Retry and delete run
20
+ BullMQ's own `reprocessJob` and `removeJob` scripts.
21
+ - Read-only mode via `--read-only` or `QUEUE_INSPECTOR_READ_ONLY=1`, which skips
22
+ the mutating tools.
23
+ - Configurable Redis URL and key prefixes (`REDIS_URL`, `ASYNQ_PREFIX`,
24
+ `BULL_PREFIX`) and backend selection (`QUEUE_INSPECTOR_BACKENDS`).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yusuf İhsan Görgel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,148 @@
1
+ # queue-inspector-mcp
2
+
3
+ An MCP server that lets an agent inspect and operate Redis-backed job queues. It
4
+ speaks to two backends today, [Asynq](https://github.com/hibiken/asynq) (Go) and
5
+ [BullMQ](https://github.com/taskforcesh/bullmq) (Node), reporting per-state
6
+ counts, individual job detail, and moving jobs between states.
7
+
8
+ ## Why
9
+
10
+ When a queue misbehaves in production, the useful questions are about jobs, not
11
+ keys: how many tasks are stuck in retry, what error a specific job failed with,
12
+ how many attempts it has left, whether a dead job can be requeued. A plain Redis
13
+ MCP server can only show you keys and raw values; it does not know that an Asynq
14
+ task message is protobuf, that a BullMQ job's state is decided by which sorted
15
+ set it sits in, or how either library moves a job back to the front of the line.
16
+ This server encodes that knowledge so an agent can reason about queue state and
17
+ take safe, state-aware actions.
18
+
19
+ ## Install
20
+
21
+ Requires Node.js 18 or newer and a reachable Redis.
22
+
23
+ ```bash
24
+ npm install -g queue-inspector-mcp
25
+ # or run without installing:
26
+ npx queue-inspector-mcp
27
+ ```
28
+
29
+ ## Configure
30
+
31
+ The server talks MCP over stdio, so it works with any MCP client. Point your
32
+ client at the `queue-inspector-mcp` binary and set `REDIS_URL`.
33
+
34
+ Claude Desktop (`claude_desktop_config.json`):
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "queues": {
40
+ "command": "npx",
41
+ "args": ["-y", "queue-inspector-mcp"],
42
+ "env": { "REDIS_URL": "redis://localhost:6379" }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ Claude Code (project `.mcp.json`, or `claude mcp add`):
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "queues": {
54
+ "command": "npx",
55
+ "args": ["-y", "queue-inspector-mcp"],
56
+ "env": { "REDIS_URL": "redis://localhost:6379", "QUEUE_INSPECTOR_READ_ONLY": "1" }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ### Configuration
63
+
64
+ | Variable | Default | Purpose |
65
+ | --- | --- | --- |
66
+ | `REDIS_URL` | `redis://localhost:6379` | Redis connection string. Include a database number, e.g. `redis://localhost:6379/2`. |
67
+ | `ASYNQ_PREFIX` | `asynq` | Key prefix Asynq was configured with. |
68
+ | `BULL_PREFIX` | `bull` | Key prefix BullMQ was configured with. |
69
+ | `QUEUE_INSPECTOR_BACKENDS` | `asynq,bullmq` | Restrict which backends are scanned. |
70
+ | `QUEUE_INSPECTOR_READ_ONLY` | unset | Set to `1` (or pass `--read-only`) to omit the mutating tools. |
71
+
72
+ ## Tools
73
+
74
+ | Tool | Mutating | Behavior |
75
+ | --- | --- | --- |
76
+ | `list_queues` | no | List every detected queue, tagged with its backend. |
77
+ | `queue_stats` | no | Count jobs per state for a queue, using the backend's own state names. |
78
+ | `list_jobs` | no | Page through jobs in one state; returns id, type, attempts, and a truncated last error. |
79
+ | `get_job` | no | Full detail for one job: payload, attempts, retry ceiling, last error, timestamps. |
80
+ | `retry_job` | yes | Move a failed or dead job back to pending/wait so it runs again. |
81
+ | `delete_job` | yes | Permanently delete a job. Active jobs are refused. |
82
+
83
+ When a queue name is unique across the enabled backends, the `backend` argument
84
+ is optional; the server resolves it. If the same name exists in both backends,
85
+ pass `backend` explicitly.
86
+
87
+ ## Read-only mode
88
+
89
+ With `--read-only` or `QUEUE_INSPECTOR_READ_ONLY=1`, the server never registers
90
+ `retry_job` or `delete_job`. The mutating tools are absent from `tools/list`
91
+ entirely, so a client cannot call them by mistake. This is the recommended
92
+ configuration for pointing an agent at a production database.
93
+
94
+ ## Backend state names
95
+
96
+ The two libraries model job lifecycles differently, so this server does not
97
+ invent a shared vocabulary. It reports each backend's own state names, and each
98
+ state maps to a specific Redis structure.
99
+
100
+ Asynq:
101
+
102
+ | State | Meaning | Redis structure |
103
+ | --- | --- | --- |
104
+ | `pending` | Ready to run, waiting for a worker | list `asynq:{q}:pending` |
105
+ | `active` | Currently being processed | list `asynq:{q}:active` |
106
+ | `scheduled` | Enqueued for a future time | zset `asynq:{q}:scheduled` |
107
+ | `retry` | Failed, waiting to be retried | zset `asynq:{q}:retry` |
108
+ | `archived` | Retries exhausted (the "dead" state) | zset `asynq:{q}:archived` |
109
+ | `completed` | Finished, kept for its retention window | zset `asynq:{q}:completed` |
110
+
111
+ BullMQ:
112
+
113
+ | State | Meaning | Redis structure |
114
+ | --- | --- | --- |
115
+ | `waiting` | Ready to run | list `bull:q:wait` |
116
+ | `active` | Currently being processed | list `bull:q:active` |
117
+ | `delayed` | Scheduled for a future time | zset `bull:q:delayed` |
118
+ | `prioritized` | Waiting, ordered by priority | zset `bull:q:prioritized` |
119
+ | `waiting-children` | Blocked on child jobs (flows) | zset `bull:q:waiting-children` |
120
+ | `paused` | Held while the queue is paused | list `bull:q:paused` |
121
+ | `completed` | Finished successfully | zset `bull:q:completed` |
122
+ | `failed` | Failed after exhausting attempts | zset `bull:q:failed` |
123
+
124
+ Asynq's `archived` is what most people mean by a "dead" job. `list_jobs` returns
125
+ Asynq's terminal sets in Redis (score) order and BullMQ's `completed`/`failed`
126
+ sets most-recent-first.
127
+
128
+ ## What this doesn't do
129
+
130
+ - Only Asynq and BullMQ are supported. Sidekiq, Celery, RQ and others are not.
131
+ - No web UI. This is an MCP server for programmatic use; it is not a dashboard.
132
+ - No streaming or watch. Each tool call is a point-in-time read; there is no
133
+ subscription to queue events.
134
+ - `retry_job` and `delete_job` faithfully replicate each library's own mechanism
135
+ rather than reimplementing it. Retry runs Asynq's `Inspector.RunTask` script
136
+ and BullMQ's `Job.retry` (`reprocessJob`) script; delete runs Asynq's
137
+ `Inspector.DeleteTask` script and BullMQ's `Job.remove` (`removeJob`) script.
138
+ As a result the semantics match the libraries: retrying a BullMQ job applies
139
+ only to `failed`/`completed` jobs and does not reset `attemptsMade` (matching
140
+ `Job.retry()`); neither backend can retry or delete an `active` job.
141
+ - `delete_job` removes a single BullMQ job and does not cascade into a flow's
142
+ children.
143
+ - Asynq group aggregation (the `aggregating` state) is not surfaced in this
144
+ release.
145
+
146
+ ## License
147
+
148
+ MIT © Yusuf İhsan Görgel
@@ -0,0 +1,16 @@
1
+ export interface AsynqTaskMessage {
2
+ type: string;
3
+ payload: Buffer;
4
+ id: string;
5
+ queue: string;
6
+ maxRetry: number;
7
+ retried: number;
8
+ errorMsg: string;
9
+ timeoutSecs: number;
10
+ deadlineUnix: number;
11
+ lastFailedAtUnix: number;
12
+ retentionSecs: number;
13
+ completedAtUnix: number;
14
+ groupKey: string;
15
+ }
16
+ export declare function decodeTaskMessage(buf: Buffer): AsynqTaskMessage;
@@ -0,0 +1,144 @@
1
+ // Asynq stores each task's metadata as a protobuf-encoded TaskMessage in the
2
+ // `msg` field of its task hash. Rather than pull in a full protobuf runtime for
3
+ // one flat message, this reads the handful of scalar fields the inspector needs
4
+ // straight off the wire. Field numbers come from the vendored schema in
5
+ // ./proto/asynq_task.proto (from hibiken/asynq, MIT). They are deliberately
6
+ // non-sequential upstream (last_failed_at is 11, not 8), so they are pinned by
7
+ // number here, not inferred from order.
8
+ const FIELD = {
9
+ type: 1,
10
+ payload: 2,
11
+ id: 3,
12
+ queue: 4,
13
+ retry: 5,
14
+ retried: 6,
15
+ errorMsg: 7,
16
+ timeout: 8,
17
+ deadline: 9,
18
+ uniqueKey: 10,
19
+ lastFailedAt: 11,
20
+ retention: 12,
21
+ completedAt: 13,
22
+ groupKey: 14,
23
+ };
24
+ class Reader {
25
+ buf;
26
+ pos = 0;
27
+ constructor(buf) {
28
+ this.buf = buf;
29
+ }
30
+ get done() {
31
+ return this.pos >= this.buf.length;
32
+ }
33
+ /** Reads a base-128 varint. Values used here (ids, counts, unix seconds) stay
34
+ * well inside Number.MAX_SAFE_INTEGER, so a plain number is safe. */
35
+ varint() {
36
+ let result = 0;
37
+ let shift = 0;
38
+ for (;;) {
39
+ if (this.pos >= this.buf.length)
40
+ throw new Error("truncated varint");
41
+ const byte = this.buf[this.pos++];
42
+ result += (byte & 0x7f) * 2 ** shift;
43
+ if ((byte & 0x80) === 0)
44
+ return result;
45
+ shift += 7;
46
+ }
47
+ }
48
+ bytes() {
49
+ const len = this.varint();
50
+ const end = this.pos + len;
51
+ if (end > this.buf.length)
52
+ throw new Error("length-delimited field runs past end of buffer");
53
+ const out = this.buf.subarray(this.pos, end);
54
+ this.pos = end;
55
+ return Buffer.from(out);
56
+ }
57
+ /** Advances past a field whose number we do not care about. */
58
+ skip(wireType) {
59
+ switch (wireType) {
60
+ case 0:
61
+ this.varint();
62
+ return;
63
+ case 1:
64
+ this.pos += 8;
65
+ return;
66
+ case 2:
67
+ this.pos += this.varint();
68
+ return;
69
+ case 5:
70
+ this.pos += 4;
71
+ return;
72
+ default:
73
+ throw new Error(`unsupported protobuf wire type ${wireType}`);
74
+ }
75
+ }
76
+ }
77
+ export function decodeTaskMessage(buf) {
78
+ const msg = {
79
+ type: "",
80
+ payload: Buffer.alloc(0),
81
+ id: "",
82
+ queue: "",
83
+ maxRetry: 0,
84
+ retried: 0,
85
+ errorMsg: "",
86
+ timeoutSecs: 0,
87
+ deadlineUnix: 0,
88
+ lastFailedAtUnix: 0,
89
+ retentionSecs: 0,
90
+ completedAtUnix: 0,
91
+ groupKey: "",
92
+ };
93
+ const r = new Reader(buf);
94
+ while (!r.done) {
95
+ const tag = r.varint();
96
+ const field = tag >> 3;
97
+ const wireType = tag & 0x07;
98
+ switch (field) {
99
+ case FIELD.type:
100
+ msg.type = r.bytes().toString("utf8");
101
+ break;
102
+ case FIELD.payload:
103
+ msg.payload = r.bytes();
104
+ break;
105
+ case FIELD.id:
106
+ msg.id = r.bytes().toString("utf8");
107
+ break;
108
+ case FIELD.queue:
109
+ msg.queue = r.bytes().toString("utf8");
110
+ break;
111
+ case FIELD.retry:
112
+ msg.maxRetry = r.varint();
113
+ break;
114
+ case FIELD.retried:
115
+ msg.retried = r.varint();
116
+ break;
117
+ case FIELD.errorMsg:
118
+ msg.errorMsg = r.bytes().toString("utf8");
119
+ break;
120
+ case FIELD.timeout:
121
+ msg.timeoutSecs = r.varint();
122
+ break;
123
+ case FIELD.deadline:
124
+ msg.deadlineUnix = r.varint();
125
+ break;
126
+ case FIELD.lastFailedAt:
127
+ msg.lastFailedAtUnix = r.varint();
128
+ break;
129
+ case FIELD.retention:
130
+ msg.retentionSecs = r.varint();
131
+ break;
132
+ case FIELD.completedAt:
133
+ msg.completedAtUnix = r.varint();
134
+ break;
135
+ case FIELD.groupKey:
136
+ msg.groupKey = r.bytes().toString("utf8");
137
+ break;
138
+ default:
139
+ r.skip(wireType);
140
+ }
141
+ }
142
+ return msg;
143
+ }
144
+ //# sourceMappingURL=asynq-proto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asynq-proto.js","sourceRoot":"","sources":["../../src/backends/asynq-proto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { Redis } from "ioredis";
2
+ import { type JobDetail, type JobSummary, type PageOpts, type QueueBackend, type StateCounts } from "../types.js";
3
+ /**
4
+ * Adapter for queues managed by Asynq (github.com/hibiken/asynq).
5
+ *
6
+ * Key layout, verified against a running Asynq worker:
7
+ * asynq:queues SET of queue names
8
+ * asynq:{<q>}:pending / :active LIST of task ids
9
+ * asynq:{<q>}:scheduled/:retry/ ZSET task id -> unix seconds (process-at,
10
+ * :archived/:completed next retry, last failure, or expiry)
11
+ * asynq:{<q>}:t:<id> HASH { msg: protobuf TaskMessage, state,
12
+ * pending_since }
13
+ * Task metadata is a protobuf TaskMessage; see ./asynq-proto.ts.
14
+ */
15
+ export declare class AsynqBackend implements QueueBackend {
16
+ private readonly redis;
17
+ private readonly prefix;
18
+ readonly name: "asynq";
19
+ readonly states: readonly ["pending", "active", "scheduled", "retry", "archived", "completed"];
20
+ private readonly scripts;
21
+ constructor(redis: Redis, prefix?: string);
22
+ private queuesKey;
23
+ private queuePrefix;
24
+ private taskKey;
25
+ private stateKey;
26
+ detectQueues(): Promise<string[]>;
27
+ private assertQueue;
28
+ stats(queue: string): Promise<StateCounts>;
29
+ private assertState;
30
+ listJobs(queue: string, rawState: string, page: PageOpts): Promise<JobSummary[]>;
31
+ getJob(queue: string, id: string): Promise<JobDetail | null>;
32
+ private readTask;
33
+ private readTaskDetail;
34
+ /** Asynq's TaskMessage carries no enqueue time; a pending task does record a
35
+ * `pending_since` field in nanoseconds, which is the closest equivalent. */
36
+ private enqueuedAt;
37
+ retryJob(queue: string, id: string): Promise<{
38
+ ok: true;
39
+ message: string;
40
+ }>;
41
+ deleteJob(queue: string, id: string): Promise<{
42
+ ok: true;
43
+ message: string;
44
+ }>;
45
+ }
@@ -0,0 +1,183 @@
1
+ import { BackendError, } from "../types.js";
2
+ import { decodePayload, isoFromUnixSeconds, truncate } from "../format.js";
3
+ import { decodeTaskMessage } from "./asynq-proto.js";
4
+ import { attachScripts } from "./scripting.js";
5
+ // State names as Asynq itself uses them, in a natural lifecycle order. Group
6
+ // aggregation ("aggregating") is an advanced feature not surfaced in v0.1.
7
+ const STATES = ["pending", "active", "scheduled", "retry", "archived", "completed"];
8
+ const LIST_STATES = new Set(["pending", "active"]);
9
+ /**
10
+ * Adapter for queues managed by Asynq (github.com/hibiken/asynq).
11
+ *
12
+ * Key layout, verified against a running Asynq worker:
13
+ * asynq:queues SET of queue names
14
+ * asynq:{<q>}:pending / :active LIST of task ids
15
+ * asynq:{<q>}:scheduled/:retry/ ZSET task id -> unix seconds (process-at,
16
+ * :archived/:completed next retry, last failure, or expiry)
17
+ * asynq:{<q>}:t:<id> HASH { msg: protobuf TaskMessage, state,
18
+ * pending_since }
19
+ * Task metadata is a protobuf TaskMessage; see ./asynq-proto.ts.
20
+ */
21
+ export class AsynqBackend {
22
+ redis;
23
+ prefix;
24
+ name = "asynq";
25
+ states = STATES;
26
+ scripts;
27
+ constructor(redis, prefix = "asynq") {
28
+ this.redis = redis;
29
+ this.prefix = prefix;
30
+ this.scripts = attachScripts(redis);
31
+ }
32
+ queuesKey() {
33
+ return `${this.prefix}:queues`;
34
+ }
35
+ queuePrefix(queue) {
36
+ return `${this.prefix}:{${queue}}:`;
37
+ }
38
+ taskKey(queue, id) {
39
+ return `${this.queuePrefix(queue)}t:${id}`;
40
+ }
41
+ stateKey(queue, state) {
42
+ return `${this.queuePrefix(queue)}${state}`;
43
+ }
44
+ async detectQueues() {
45
+ const queues = await this.redis.smembers(this.queuesKey());
46
+ return queues.sort();
47
+ }
48
+ async assertQueue(queue) {
49
+ const known = await this.redis.sismember(this.queuesKey(), queue);
50
+ if (!known) {
51
+ throw new BackendError(`asynq queue "${queue}" was not found`, "queue_not_found");
52
+ }
53
+ }
54
+ async stats(queue) {
55
+ await this.assertQueue(queue);
56
+ const counts = {};
57
+ for (const state of STATES) {
58
+ const key = this.stateKey(queue, state);
59
+ counts[state] = LIST_STATES.has(state)
60
+ ? await this.redis.llen(key)
61
+ : await this.redis.zcard(key);
62
+ }
63
+ return counts;
64
+ }
65
+ assertState(state) {
66
+ if (!STATES.includes(state)) {
67
+ throw new BackendError(`unknown asynq state "${state}"; expected one of ${STATES.join(", ")}`, "invalid_state");
68
+ }
69
+ return state;
70
+ }
71
+ async listJobs(queue, rawState, page) {
72
+ await this.assertQueue(queue);
73
+ const state = this.assertState(rawState);
74
+ const key = this.stateKey(queue, state);
75
+ const stop = page.offset + page.limit - 1;
76
+ const ids = LIST_STATES.has(state)
77
+ ? await this.redis.lrange(key, page.offset, stop)
78
+ : await this.redis.zrange(key, page.offset, stop);
79
+ const summaries = [];
80
+ for (const id of ids) {
81
+ const detail = await this.readTask(queue, id, state);
82
+ if (detail)
83
+ summaries.push(detail);
84
+ }
85
+ return summaries;
86
+ }
87
+ async getJob(queue, id) {
88
+ await this.assertQueue(queue);
89
+ const state = (await this.redis.hget(this.taskKey(queue, id), "state"));
90
+ if (state === null)
91
+ return null;
92
+ return this.readTaskDetail(queue, id, state);
93
+ }
94
+ async readTask(queue, id, state) {
95
+ const raw = await this.redis.hgetBuffer(this.taskKey(queue, id), "msg");
96
+ if (raw === null)
97
+ return null;
98
+ const msg = decodeTaskMessage(raw);
99
+ return {
100
+ id,
101
+ type: msg.type,
102
+ state,
103
+ enqueuedAt: await this.enqueuedAt(queue, id, state),
104
+ attempts: msg.retried,
105
+ maxRetries: msg.maxRetry,
106
+ lastError: msg.errorMsg ? truncate(msg.errorMsg) : null,
107
+ };
108
+ }
109
+ async readTaskDetail(queue, id, state) {
110
+ const raw = await this.redis.hgetBuffer(this.taskKey(queue, id), "msg");
111
+ if (raw === null)
112
+ return null;
113
+ const msg = decodeTaskMessage(raw);
114
+ const decoded = decodePayload(msg.payload);
115
+ const timestamps = {
116
+ enqueuedAt: await this.enqueuedAt(queue, id, state),
117
+ lastFailedAt: isoFromUnixSeconds(msg.lastFailedAtUnix),
118
+ completedAt: isoFromUnixSeconds(msg.completedAtUnix),
119
+ deadline: isoFromUnixSeconds(msg.deadlineUnix),
120
+ };
121
+ if (state === "scheduled" || state === "retry") {
122
+ const score = await this.redis.zscore(this.stateKey(queue, state), id);
123
+ timestamps.nextProcessAt = score ? isoFromUnixSeconds(Number(score)) : null;
124
+ }
125
+ return {
126
+ id,
127
+ queue,
128
+ backend: this.name,
129
+ type: msg.type,
130
+ state,
131
+ enqueuedAt: timestamps.enqueuedAt ?? null,
132
+ attempts: msg.retried,
133
+ maxRetries: msg.maxRetry,
134
+ lastError: msg.errorMsg ? truncate(msg.errorMsg) : null,
135
+ fullError: msg.errorMsg || null,
136
+ timestamps,
137
+ ...decoded,
138
+ };
139
+ }
140
+ /** Asynq's TaskMessage carries no enqueue time; a pending task does record a
141
+ * `pending_since` field in nanoseconds, which is the closest equivalent. */
142
+ async enqueuedAt(queue, id, state) {
143
+ if (state !== "pending")
144
+ return null;
145
+ const ns = await this.redis.hget(this.taskKey(queue, id), "pending_since");
146
+ if (!ns)
147
+ return null;
148
+ return new Date(Number(ns) / 1e6).toISOString();
149
+ }
150
+ async retryJob(queue, id) {
151
+ await this.assertQueue(queue);
152
+ const prefix = this.queuePrefix(queue);
153
+ const result = await this.scripts.runTask([this.taskKey(queue, id), `${prefix}pending`, `${prefix}groups`], [id, prefix, `${prefix}g:`]);
154
+ switch (result) {
155
+ case 1:
156
+ return { ok: true, message: `asynq task ${id} moved to pending in queue "${queue}"` };
157
+ case 0:
158
+ throw new BackendError(`asynq task "${id}" was not found in queue "${queue}"`, "job_not_found");
159
+ case -1:
160
+ throw new BackendError(`asynq task "${id}" is currently active and cannot be retried`, "invalid_state");
161
+ case -2:
162
+ throw new BackendError(`asynq task "${id}" is already pending`, "invalid_state");
163
+ default:
164
+ throw new BackendError(`asynq retry returned unexpected code ${result}`, "invalid_state");
165
+ }
166
+ }
167
+ async deleteJob(queue, id) {
168
+ await this.assertQueue(queue);
169
+ const prefix = this.queuePrefix(queue);
170
+ const result = await this.scripts.deleteTask([this.taskKey(queue, id), `${prefix}groups`], [id, prefix, `${prefix}g:`]);
171
+ switch (result) {
172
+ case 1:
173
+ return { ok: true, message: `asynq task ${id} deleted from queue "${queue}"` };
174
+ case 0:
175
+ throw new BackendError(`asynq task "${id}" was not found in queue "${queue}"`, "job_not_found");
176
+ case -1:
177
+ throw new BackendError(`asynq task "${id}" is currently active and cannot be deleted`, "invalid_state");
178
+ default:
179
+ throw new BackendError(`asynq delete returned unexpected code ${result}`, "invalid_state");
180
+ }
181
+ }
182
+ }
183
+ //# sourceMappingURL=asynq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asynq.js","sourceRoot":"","sources":["../../src/backends/asynq.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { Redis } from "ioredis";
2
+ import { type JobDetail, type JobSummary, type PageOpts, type QueueBackend, type StateCounts } from "../types.js";
3
+ /**
4
+ * Adapter for queues managed by BullMQ (github.com/taskforcesh/bullmq).
5
+ *
6
+ * Key layout, verified against a running BullMQ worker:
7
+ * bull:<q>:meta HASH queue metadata (version, paused)
8
+ * bull:<q>:wait / :active / :paused LIST of job ids
9
+ * bull:<q>:delayed / :prioritized / ZSET of job ids
10
+ * :completed / :failed
11
+ * bull:<q>:<id> HASH { name, data, opts, timestamp,
12
+ * atm, failedReason, ... }
13
+ * Mutations run BullMQ's own vendored scripts; see ./lua and ./scripting.ts.
14
+ */
15
+ export declare class BullmqBackend implements QueueBackend {
16
+ private readonly redis;
17
+ private readonly prefix;
18
+ readonly name: "bullmq";
19
+ readonly states: readonly ["waiting", "active", "delayed", "prioritized", "waiting-children", "paused", "completed", "failed"];
20
+ private readonly scripts;
21
+ constructor(redis: Redis, prefix?: string);
22
+ private key;
23
+ private jobKey;
24
+ detectQueues(): Promise<string[]>;
25
+ private assertQueue;
26
+ stats(queue: string): Promise<StateCounts>;
27
+ private assertState;
28
+ private idsForState;
29
+ listJobs(queue: string, rawState: string, page: PageOpts): Promise<JobSummary[]>;
30
+ getJob(queue: string, id: string): Promise<JobDetail | null>;
31
+ private readHash;
32
+ private toSummary;
33
+ private maxRetries;
34
+ private isLifo;
35
+ /** Determines a job's current state by membership, mirroring BullMQ's own
36
+ * getState: check the terminal and scheduling sets, then the lists. */
37
+ private resolveState;
38
+ retryJob(queue: string, id: string): Promise<{
39
+ ok: true;
40
+ message: string;
41
+ }>;
42
+ deleteJob(queue: string, id: string): Promise<{
43
+ ok: true;
44
+ message: string;
45
+ }>;
46
+ }