@zintrust/queue-monitor 0.5.8 → 0.7.7
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/dist/QueueMonitoringService.js +1 -0
- package/dist/build-manifest.json +17 -17
- package/dist/dashboard-ui.js +4 -1
- package/dist/driver.d.ts +21 -1
- package/dist/driver.js +32 -6
- package/dist/index.js +36 -13
- package/dist/metrics.d.ts +2 -1
- package/dist/metrics.js +1 -0
- package/package.json +2 -2
package/dist/build-manifest.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/queue-monitor",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"buildDate": "2026-04-
|
|
3
|
+
"version": "0.7.7",
|
|
4
|
+
"buildDate": "2026-04-19T14:20:25.049Z",
|
|
5
5
|
"buildEnvironment": {
|
|
6
6
|
"node": "v20.20.2",
|
|
7
7
|
"platform": "linux",
|
|
8
8
|
"arch": "x64"
|
|
9
9
|
},
|
|
10
10
|
"git": {
|
|
11
|
-
"commit": "
|
|
11
|
+
"commit": "3921a3ee",
|
|
12
12
|
"branch": "master"
|
|
13
13
|
},
|
|
14
14
|
"package": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"sha256": "26a91f1b41d8a976a9e8fabaedc3fe6dc955c700499f0afc1a16fb91c9848665"
|
|
38
38
|
},
|
|
39
39
|
"QueueMonitoringService.js": {
|
|
40
|
-
"size":
|
|
41
|
-
"sha256": "
|
|
40
|
+
"size": 8928,
|
|
41
|
+
"sha256": "fc408db720d5883821b169eabce66944295068cd6f2338f1884493c299f0a0f1"
|
|
42
42
|
},
|
|
43
43
|
"connection.d.ts": {
|
|
44
44
|
"size": 107,
|
|
@@ -53,32 +53,32 @@
|
|
|
53
53
|
"sha256": "5140191207b5620500ac5e1edacefed65956a1ad88739695d545ea7d2b405243"
|
|
54
54
|
},
|
|
55
55
|
"dashboard-ui.js": {
|
|
56
|
-
"size":
|
|
57
|
-
"sha256": "
|
|
56
|
+
"size": 54576,
|
|
57
|
+
"sha256": "b3ee7f13f8fa2fee8204316ac5fe4c9ee0d424131565a58de17061296c7c8e1b"
|
|
58
58
|
},
|
|
59
59
|
"driver.d.ts": {
|
|
60
|
-
"size":
|
|
61
|
-
"sha256": "
|
|
60
|
+
"size": 1102,
|
|
61
|
+
"sha256": "70b74e7a0489da3a5545dfd8458ab5b4def1f3733c291a782c033fed0afd0bcf"
|
|
62
62
|
},
|
|
63
63
|
"driver.js": {
|
|
64
|
-
"size":
|
|
65
|
-
"sha256": "
|
|
64
|
+
"size": 5165,
|
|
65
|
+
"sha256": "06a4aa4294ed0694146d184e8b0f1677ebe22a3d90bffa30b7c268657035c760"
|
|
66
66
|
},
|
|
67
67
|
"index.d.ts": {
|
|
68
68
|
"size": 2161,
|
|
69
69
|
"sha256": "2556c136641769f4e7cd81abd6ecae8663433aba74b09bc507b8fba01e3fc6f5"
|
|
70
70
|
},
|
|
71
71
|
"index.js": {
|
|
72
|
-
"size":
|
|
73
|
-
"sha256": "
|
|
72
|
+
"size": 13905,
|
|
73
|
+
"sha256": "707429961a88fe448506c3d3ca5c348cfa9305addd10bc081b1191ff141e8218"
|
|
74
74
|
},
|
|
75
75
|
"metrics.d.ts": {
|
|
76
|
-
"size":
|
|
77
|
-
"sha256": "
|
|
76
|
+
"size": 910,
|
|
77
|
+
"sha256": "6467795f6de818a1f295c42755005e394f25b145163c7678d6ac9ac1f9477fea"
|
|
78
78
|
},
|
|
79
79
|
"metrics.js": {
|
|
80
|
-
"size":
|
|
81
|
-
"sha256": "
|
|
80
|
+
"size": 3472,
|
|
81
|
+
"sha256": "fab0e6d62ed694dcf57904a68b683280e4063c2fdcf97c60fdf5d933ae2b8f4f"
|
|
82
82
|
},
|
|
83
83
|
"worker.d.ts": {
|
|
84
84
|
"size": 332,
|
package/dist/dashboard-ui.js
CHANGED
|
@@ -983,9 +983,12 @@ const getRetryJobFunction = () => `
|
|
|
983
983
|
const res = await fetch(API_BASE + '/api/retry/' + queueName + '/' + jobId, {
|
|
984
984
|
method: 'POST'
|
|
985
985
|
});
|
|
986
|
+
const payload = await res.json().catch(() => null);
|
|
986
987
|
|
|
987
988
|
if (res.ok) {
|
|
988
|
-
btn.textContent =
|
|
989
|
+
btn.textContent = payload && payload.status === 'requeued_from_snapshot'
|
|
990
|
+
? '✓ Requeued'
|
|
991
|
+
: '✓ Retried';
|
|
989
992
|
setTimeout(() => {
|
|
990
993
|
console.log('HTTP jobs polling disabled - using SSE only');
|
|
991
994
|
// fetchJobs(currentQueue);
|
package/dist/driver.d.ts
CHANGED
|
@@ -2,12 +2,32 @@ import type { Job, JobsOptions } from 'bullmq';
|
|
|
2
2
|
import { type RedisConfig } from './connection';
|
|
3
3
|
export type JobPayload<T = unknown> = T;
|
|
4
4
|
export type JobCounts = Record<string, number>;
|
|
5
|
+
export type RetrySnapshot = {
|
|
6
|
+
name?: string;
|
|
7
|
+
data: unknown;
|
|
8
|
+
opts?: JobsOptions;
|
|
9
|
+
};
|
|
10
|
+
export type RetryJobResult = {
|
|
11
|
+
ok: true;
|
|
12
|
+
status: 'retried';
|
|
13
|
+
} | {
|
|
14
|
+
ok: true;
|
|
15
|
+
status: 'requeued_from_snapshot';
|
|
16
|
+
newJobId?: string;
|
|
17
|
+
} | {
|
|
18
|
+
ok: false;
|
|
19
|
+
status: 'missing';
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
status: 'not_retryable';
|
|
23
|
+
reason?: string;
|
|
24
|
+
};
|
|
5
25
|
export type QueueDriver = {
|
|
6
26
|
enqueue<T>(name: string, payload: T, options?: JobsOptions): Promise<string>;
|
|
7
27
|
getJob(queueName: string, jobId: string): Promise<Job | undefined>;
|
|
8
28
|
getJobCounts(queueName: string): Promise<JobCounts>;
|
|
9
29
|
getRecentJobs(queueName: string, limit?: number): Promise<Job[]>;
|
|
10
|
-
retryJob(queueName: string, jobId: string): Promise<
|
|
30
|
+
retryJob(queueName: string, jobId: string, snapshot?: RetrySnapshot): Promise<RetryJobResult>;
|
|
11
31
|
getQueues(): Promise<string[]>;
|
|
12
32
|
close(): Promise<void>;
|
|
13
33
|
};
|
package/dist/driver.js
CHANGED
|
@@ -41,6 +41,7 @@ async function discoverQueuesFromRedis(redis, inMemoryQueues) {
|
|
|
41
41
|
}
|
|
42
42
|
return Array.from(found.values());
|
|
43
43
|
}
|
|
44
|
+
// eslint-disable-next-line max-lines-per-function
|
|
44
45
|
export const createBullMQDriver = (config) => {
|
|
45
46
|
const queues = new Map();
|
|
46
47
|
const redis = createRedisConnection(config, 3, { subsystem: 'queue-monitor' });
|
|
@@ -77,6 +78,23 @@ export const createBullMQDriver = (config) => {
|
|
|
77
78
|
const queue = getQueue(queueName);
|
|
78
79
|
return queue.getJobCounts();
|
|
79
80
|
};
|
|
81
|
+
const requeueFromSnapshot = async (queue, snapshot) => {
|
|
82
|
+
try {
|
|
83
|
+
const requeued = await queue.add(snapshot.name ?? 'default', snapshot.data, snapshot.opts);
|
|
84
|
+
return {
|
|
85
|
+
ok: true,
|
|
86
|
+
status: 'requeued_from_snapshot',
|
|
87
|
+
newJobId: requeued.id === undefined || requeued.id === null ? undefined : String(requeued.id),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
status: 'not_retryable',
|
|
94
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
80
98
|
const getRecentJobs = async (queueName, limit = 100) => {
|
|
81
99
|
const queue = getQueue(queueName);
|
|
82
100
|
const jobs = await queue.getJobs(['completed', 'failed', 'active', 'waiting', 'delayed', 'paused'], 0, Math.max(0, limit - 1), true);
|
|
@@ -84,16 +102,24 @@ export const createBullMQDriver = (config) => {
|
|
|
84
102
|
await enrichJobsWithState(jobs);
|
|
85
103
|
return jobs;
|
|
86
104
|
};
|
|
87
|
-
const retryJob = async (queueName, jobId) => {
|
|
105
|
+
const retryJob = async (queueName, jobId, snapshot) => {
|
|
106
|
+
const queue = getQueue(queueName);
|
|
88
107
|
const job = await getJob(queueName, jobId);
|
|
89
|
-
if (!job)
|
|
90
|
-
|
|
108
|
+
if (!job) {
|
|
109
|
+
if (snapshot)
|
|
110
|
+
return requeueFromSnapshot(queue, snapshot);
|
|
111
|
+
return { ok: false, status: 'missing' };
|
|
112
|
+
}
|
|
91
113
|
try {
|
|
92
114
|
await job.retry();
|
|
93
|
-
return true;
|
|
115
|
+
return { ok: true, status: 'retried' };
|
|
94
116
|
}
|
|
95
|
-
catch {
|
|
96
|
-
return
|
|
117
|
+
catch (error) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
status: 'not_retryable',
|
|
121
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
122
|
+
};
|
|
97
123
|
}
|
|
98
124
|
};
|
|
99
125
|
const getQueues = async () => {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Env,
|
|
1
|
+
import { Env, isNonEmptyString, Logger, queueConfig, resolveLockPrefix, Router, } from '@zintrust/core';
|
|
2
2
|
import { createRedisConnection } from './connection';
|
|
3
3
|
import { getDashboardHtml } from './dashboard-ui';
|
|
4
4
|
import { createBullMQDriver } from './driver';
|
|
@@ -37,8 +37,7 @@ const HISTOGRAM_BUCKETS = [
|
|
|
37
37
|
const MAX_LOCK_KEYS = 10_000;
|
|
38
38
|
function normalizeQueueNames(queueNames) {
|
|
39
39
|
return Array.from(new Set(queueNames
|
|
40
|
-
.
|
|
41
|
-
.filter(isNonEmptyString)
|
|
40
|
+
.filter((queueName) => typeof queueName === 'string' && isNonEmptyString(queueName))
|
|
42
41
|
.map((name) => name.trim())))
|
|
43
42
|
.filter((name) => name.length > 0)
|
|
44
43
|
.sort((left, right) => left.localeCompare(right));
|
|
@@ -47,7 +46,7 @@ async function resolveKnownQueues(knownQueues) {
|
|
|
47
46
|
if (typeof knownQueues === 'function') {
|
|
48
47
|
return normalizeQueueNames(await knownQueues());
|
|
49
48
|
}
|
|
50
|
-
if (isArray(knownQueues)) {
|
|
49
|
+
if (Array.isArray(knownQueues)) {
|
|
51
50
|
return normalizeQueueNames(knownQueues);
|
|
52
51
|
}
|
|
53
52
|
return [];
|
|
@@ -169,20 +168,44 @@ async function handleJobsEndpoint(req, res, metrics, driver) {
|
|
|
169
168
|
const jobs = await getRecentJobsForSelection(queueName, metrics, driver);
|
|
170
169
|
res.json(jobs);
|
|
171
170
|
}
|
|
172
|
-
async function handleRetryEndpoint(req, res, driver) {
|
|
171
|
+
async function handleRetryEndpoint(req, res, driver, metrics) {
|
|
173
172
|
const queueName = extractQueueParam(req);
|
|
174
173
|
const jobId = typeof req.getParam === 'function' ? req.getParam?.('jobId') : req.params?.['jobId'];
|
|
175
174
|
if (!queueName || !jobId) {
|
|
176
175
|
res.status(400).json(fieldError('queue_name,job_id', 'Queue name and job ID must be provided'));
|
|
177
176
|
return;
|
|
178
177
|
}
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
const recentJobs = await getRecentJobsForSelection(queueName, metrics, driver);
|
|
179
|
+
const snapshotCandidate = recentJobs.find((job) => String(job.id) === jobId);
|
|
180
|
+
const retrySnapshot = snapshotCandidate === undefined
|
|
181
|
+
? undefined
|
|
182
|
+
: {
|
|
183
|
+
name: snapshotCandidate.name,
|
|
184
|
+
data: snapshotCandidate.data,
|
|
185
|
+
opts: snapshotCandidate.opts,
|
|
186
|
+
};
|
|
187
|
+
const result = await driver.retryJob(queueName, jobId, retrySnapshot);
|
|
188
|
+
if (result.ok) {
|
|
189
|
+
res.json({
|
|
190
|
+
ok: true,
|
|
191
|
+
status: result.status,
|
|
192
|
+
message: result.status === 'requeued_from_snapshot'
|
|
193
|
+
? `Job ${jobId} re-queued from monitor snapshot`
|
|
194
|
+
: `Job ${jobId} queued for retry`,
|
|
195
|
+
...(result.status === 'requeued_from_snapshot' && result.newJobId
|
|
196
|
+
? { newJobId: result.newJobId }
|
|
197
|
+
: {}),
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
182
200
|
}
|
|
183
|
-
|
|
184
|
-
res.status(404).json({ error:
|
|
201
|
+
if (result.status === 'missing') {
|
|
202
|
+
res.status(404).json({ error: `Job ${jobId} no longer exists`, status: result.status });
|
|
203
|
+
return;
|
|
185
204
|
}
|
|
205
|
+
res.status(409).json({
|
|
206
|
+
error: result.reason ?? `Job ${jobId} cannot be retried in its current state`,
|
|
207
|
+
status: result.status,
|
|
208
|
+
});
|
|
186
209
|
}
|
|
187
210
|
function buildSettings(config) {
|
|
188
211
|
return {
|
|
@@ -241,7 +264,7 @@ function registerApiRoutes(router, settings, routeOptions, metrics, driver, getS
|
|
|
241
264
|
registerSnapshotApi(router, settings, routeOptions, getSnapshot);
|
|
242
265
|
registerJobsApi(router, settings, routeOptions, metrics, driver);
|
|
243
266
|
registerLocksApi(router, settings, routeOptions, getLocks);
|
|
244
|
-
registerRetryApi(router, settings, routeOptions, driver);
|
|
267
|
+
registerRetryApi(router, settings, routeOptions, driver, metrics);
|
|
245
268
|
registerEventsApi(router, settings, routeOptions, getSnapshot, getLocks, metrics, driver);
|
|
246
269
|
}
|
|
247
270
|
function registerSnapshotApi(router, settings, routeOptions, getSnapshot) {
|
|
@@ -265,9 +288,9 @@ function registerLocksApi(router, settings, routeOptions, getLocks) {
|
|
|
265
288
|
res.json(locks);
|
|
266
289
|
}, routeOptions);
|
|
267
290
|
}
|
|
268
|
-
function registerRetryApi(router, settings, routeOptions, driver) {
|
|
291
|
+
function registerRetryApi(router, settings, routeOptions, driver, metrics) {
|
|
269
292
|
Router.post(router, `${settings.basePath}/api/retry/:queue/:jobId`, async (req, res) => {
|
|
270
|
-
await handleRetryEndpoint(req, res, driver);
|
|
293
|
+
await handleRetryEndpoint(req, res, driver, metrics);
|
|
271
294
|
}, routeOptions);
|
|
272
295
|
}
|
|
273
296
|
function registerEventsApi(router, settings, routeOptions, getSnapshot, getLocks, metrics, driver) {
|
package/dist/metrics.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Job } from 'bullmq';
|
|
1
|
+
import { type Job, type JobsOptions } from 'bullmq';
|
|
2
2
|
import { type RedisConfig } from './connection';
|
|
3
3
|
export type JobStatus = 'completed' | 'failed';
|
|
4
4
|
export type JobSummary = {
|
|
@@ -6,6 +6,7 @@ export type JobSummary = {
|
|
|
6
6
|
name: string;
|
|
7
7
|
queue?: string;
|
|
8
8
|
data: unknown;
|
|
9
|
+
opts?: JobsOptions;
|
|
9
10
|
attempts: number;
|
|
10
11
|
status?: string;
|
|
11
12
|
failedReason?: string;
|
package/dist/metrics.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zintrust/queue-monitor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.7",
|
|
4
4
|
"description": "Queue monitoring package for ZinTrust with BullMQ and Redis.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"node": ">=20.0.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@zintrust/core": "^0.
|
|
23
|
+
"@zintrust/core": "^0.7.7"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|