@queuedash/sdk 0.2.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/README.md +54 -48
- package/dist/client.d.ts +43 -7
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +288 -48
- package/dist/client.spec.d.ts +2 -0
- package/dist/client.spec.d.ts.map +1 -0
- package/dist/client.spec.js +809 -0
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -8
- package/dist/types.d.ts +10 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +22 -7
- package/dist/integrations/bee.d.ts +0 -8
- package/dist/integrations/bee.d.ts.map +0 -1
- package/dist/integrations/bee.js +0 -43
- package/dist/integrations/bull.d.ts +0 -7
- package/dist/integrations/bull.d.ts.map +0 -1
- package/dist/integrations/bull.js +0 -46
- package/dist/integrations/bullmq.d.ts +0 -7
- package/dist/integrations/bullmq.d.ts.map +0 -1
- package/dist/integrations/bullmq.js +0 -47
package/dist/client.js
CHANGED
|
@@ -1,7 +1,40 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
class
|
|
36
|
+
exports.Queuedash = void 0;
|
|
37
|
+
class Queuedash {
|
|
5
38
|
constructor(options) {
|
|
6
39
|
this.jobQueue = [];
|
|
7
40
|
this.isFlushing = false;
|
|
@@ -9,29 +42,245 @@ class QueueDashClient {
|
|
|
9
42
|
this.circuitBreakerFailures = 0;
|
|
10
43
|
this.circuitBreakerOpen = false;
|
|
11
44
|
this.syncingJobIds = new Set();
|
|
45
|
+
// Track attached resources for cleanup
|
|
46
|
+
this.managedQueueEvents = [];
|
|
47
|
+
this.attachedQueues = new Set();
|
|
12
48
|
if (!options.apiKey) {
|
|
13
|
-
throw new Error("
|
|
49
|
+
throw new Error("Queuedash: apiKey is required");
|
|
14
50
|
}
|
|
15
51
|
this.apiKey = options.apiKey;
|
|
16
|
-
// Hardcoded SaaS API URL - can be overridden for staging only
|
|
17
52
|
this.apiUrl =
|
|
18
|
-
options.
|
|
53
|
+
options.baseUrl ||
|
|
19
54
|
(process.env.NODE_ENV === "production"
|
|
20
55
|
? "https://api.queuedash.com"
|
|
21
56
|
: "http://localhost:4001");
|
|
22
|
-
this.batchSize = options.batchSize
|
|
23
|
-
this.flushInterval = options.flushInterval
|
|
24
|
-
this.maxRetries = options.maxRetries
|
|
25
|
-
this.requestTimeout = options.requestTimeout
|
|
26
|
-
this.maxQueueSize = options.maxQueueSize
|
|
57
|
+
this.batchSize = options.batchSize ?? 50;
|
|
58
|
+
this.flushInterval = options.flushInterval ?? 100;
|
|
59
|
+
this.maxRetries = options.maxRetries ?? 5;
|
|
60
|
+
this.requestTimeout = options.requestTimeout ?? 30000;
|
|
61
|
+
this.maxQueueSize = options.maxQueueSize ?? 10000;
|
|
27
62
|
this.onError = options.onError;
|
|
28
|
-
// Start auto-flush timer
|
|
29
63
|
this.startFlushTimer();
|
|
30
|
-
// Graceful shutdown handlers
|
|
31
64
|
this.setupShutdownHandlers();
|
|
32
65
|
}
|
|
33
66
|
/**
|
|
34
|
-
*
|
|
67
|
+
* Attach a queue or worker for monitoring.
|
|
68
|
+
* Auto-detects the queue library (BullMQ, Bull, Bee-Queue).
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const qd = new Queuedash({ apiKey: 'qd_...' });
|
|
73
|
+
*
|
|
74
|
+
* // BullMQ - we create QueueEvents internally
|
|
75
|
+
* qd.attach(myBullMQQueue);
|
|
76
|
+
*
|
|
77
|
+
* // Bull
|
|
78
|
+
* qd.attach(myBullQueue);
|
|
79
|
+
*
|
|
80
|
+
* // Bee-Queue
|
|
81
|
+
* qd.attach(myBeeQueue);
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
attach(resource, options) {
|
|
85
|
+
const type = this.detectType(resource);
|
|
86
|
+
switch (type) {
|
|
87
|
+
case "bullmq-queue":
|
|
88
|
+
this.attachBullMQ(resource, options?.events);
|
|
89
|
+
break;
|
|
90
|
+
case "bullmq-worker":
|
|
91
|
+
this.attachBullMQWorker(resource);
|
|
92
|
+
break;
|
|
93
|
+
case "bull":
|
|
94
|
+
this.attachBull(resource);
|
|
95
|
+
break;
|
|
96
|
+
case "bee":
|
|
97
|
+
this.attachBee(resource);
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
throw new Error("Queuedash: Unknown queue type. Supported: BullMQ Queue/Worker, Bull, Bee-Queue");
|
|
101
|
+
}
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
detectType(resource) {
|
|
105
|
+
if (!resource || typeof resource !== "object") {
|
|
106
|
+
return "unknown";
|
|
107
|
+
}
|
|
108
|
+
// BullMQ Queue: has getJob method and opts.connection
|
|
109
|
+
if (typeof resource.getJob === "function" &&
|
|
110
|
+
resource.opts?.connection !== undefined) {
|
|
111
|
+
return "bullmq-queue";
|
|
112
|
+
}
|
|
113
|
+
// BullMQ Worker: has opts.connection but no getJob
|
|
114
|
+
if (resource.opts?.connection !== undefined &&
|
|
115
|
+
typeof resource.getJob !== "function" &&
|
|
116
|
+
resource.name) {
|
|
117
|
+
return "bullmq-worker";
|
|
118
|
+
}
|
|
119
|
+
// Bull: has clients array (redis clients)
|
|
120
|
+
if (Array.isArray(resource.clients)) {
|
|
121
|
+
return "bull";
|
|
122
|
+
}
|
|
123
|
+
// Bee-Queue: has settings object
|
|
124
|
+
if (resource.settings !== undefined && resource.name) {
|
|
125
|
+
return "bee";
|
|
126
|
+
}
|
|
127
|
+
return "unknown";
|
|
128
|
+
}
|
|
129
|
+
async attachBullMQ(queue, existingEvents) {
|
|
130
|
+
const queueName = queue.name;
|
|
131
|
+
if (this.attachedQueues.has(`bullmq:${queueName}`)) {
|
|
132
|
+
console.warn(`Queuedash: Queue "${queueName}" is already attached`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
let events;
|
|
136
|
+
// Create QueueEvents if not provided
|
|
137
|
+
if (existingEvents) {
|
|
138
|
+
events = existingEvents;
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
try {
|
|
142
|
+
// Dynamic import to avoid requiring bullmq as a direct dependency
|
|
143
|
+
const { QueueEvents } = await Promise.resolve().then(() => __importStar(require("bullmq")));
|
|
144
|
+
const queueEvents = new QueueEvents(queueName, {
|
|
145
|
+
connection: queue.opts?.connection,
|
|
146
|
+
prefix: queue.redisPrefix,
|
|
147
|
+
});
|
|
148
|
+
this.managedQueueEvents.push(queueEvents);
|
|
149
|
+
events = queueEvents;
|
|
150
|
+
}
|
|
151
|
+
catch (e) {
|
|
152
|
+
throw new Error("Queuedash: Failed to create QueueEvents. Make sure bullmq is installed.");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.attachedQueues.add(`bullmq:${queueName}`);
|
|
156
|
+
const syncJobById = (jobId) => {
|
|
157
|
+
queue.getJob(jobId).then((job) => {
|
|
158
|
+
if (job) {
|
|
159
|
+
this.syncJob(this.extractBullMQJobData(job, queueName));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
events.on("waiting", ({ jobId }) => syncJobById(jobId));
|
|
164
|
+
events.on("progress", ({ jobId }) => syncJobById(jobId));
|
|
165
|
+
events.on("completed", ({ jobId }) => syncJobById(jobId));
|
|
166
|
+
events.on("failed", ({ jobId }) => syncJobById(jobId));
|
|
167
|
+
events.on("waiting-children", ({ jobId }) => syncJobById(jobId));
|
|
168
|
+
events.on("added", ({ jobId }) => syncJobById(jobId));
|
|
169
|
+
events.on("active", ({ jobId }) => syncJobById(jobId));
|
|
170
|
+
events.on("delayed", ({ jobId }) => syncJobById(jobId));
|
|
171
|
+
events.on("deduplicated", ({ jobId }) => syncJobById(jobId));
|
|
172
|
+
events.on("removed", ({ jobId }) => this.deleteJob(jobId, queueName));
|
|
173
|
+
events.on("stalled", ({ jobId }) => syncJobById(jobId));
|
|
174
|
+
events.on("retries-exhausted", ({ jobId }) => syncJobById(jobId));
|
|
175
|
+
}
|
|
176
|
+
attachBullMQWorker(_worker) {
|
|
177
|
+
// TODO: Worker monitoring - track active jobs, stalled detection, etc.
|
|
178
|
+
console.warn("Queuedash: Worker monitoring coming soon");
|
|
179
|
+
}
|
|
180
|
+
attachBull(queue) {
|
|
181
|
+
const queueName = queue.name;
|
|
182
|
+
if (this.attachedQueues.has(`bull:${queueName}`)) {
|
|
183
|
+
console.warn(`Queuedash: Queue "${queueName}" is already attached`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.attachedQueues.add(`bull:${queueName}`);
|
|
187
|
+
queue.on("global:completed", (job) => {
|
|
188
|
+
this.syncJob(this.extractBullJobData(job, queueName));
|
|
189
|
+
});
|
|
190
|
+
queue.on("global:failed", (job) => {
|
|
191
|
+
if (job) {
|
|
192
|
+
this.syncJob(this.extractBullJobData(job, queueName));
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
queue.on("active", (job) => {
|
|
196
|
+
this.syncJob(this.extractBullJobData(job, queueName));
|
|
197
|
+
});
|
|
198
|
+
queue.on("progress", (job) => {
|
|
199
|
+
this.syncJob(this.extractBullJobData(job, queueName));
|
|
200
|
+
});
|
|
201
|
+
queue.on("removed", (job) => {
|
|
202
|
+
this.syncJob(this.extractBullJobData(job, queueName));
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
attachBee(queue) {
|
|
206
|
+
const queueName = queue.name;
|
|
207
|
+
if (this.attachedQueues.has(`bee:${queueName}`)) {
|
|
208
|
+
console.warn(`Queuedash: Queue "${queueName}" is already attached`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
this.attachedQueues.add(`bee:${queueName}`);
|
|
212
|
+
queue.on("succeeded", (job) => {
|
|
213
|
+
const jobData = this.extractBeeJobData(job, queueName);
|
|
214
|
+
jobData.finishedAt = new Date();
|
|
215
|
+
this.syncJob(jobData);
|
|
216
|
+
});
|
|
217
|
+
queue.on("failed", (job, err) => {
|
|
218
|
+
const jobData = this.extractBeeJobData(job, queueName);
|
|
219
|
+
jobData.failedReason = err.message;
|
|
220
|
+
jobData.stacktrace = err.stack ? [err.stack] : undefined;
|
|
221
|
+
jobData.finishedAt = new Date();
|
|
222
|
+
this.syncJob(jobData);
|
|
223
|
+
});
|
|
224
|
+
queue.on("retrying", (job) => {
|
|
225
|
+
const jobData = this.extractBeeJobData(job, queueName);
|
|
226
|
+
jobData.retriedAt = new Date();
|
|
227
|
+
this.syncJob(jobData);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
extractBullMQJobData(job, queueName) {
|
|
231
|
+
return {
|
|
232
|
+
jobId: job.id,
|
|
233
|
+
name: job.name,
|
|
234
|
+
queueName,
|
|
235
|
+
data: job.data,
|
|
236
|
+
opts: job.opts,
|
|
237
|
+
addedAt: new Date(job.timestamp),
|
|
238
|
+
processedAt: job.processedOn ? new Date(job.processedOn) : null,
|
|
239
|
+
finishedAt: job.finishedOn ? new Date(job.finishedOn) : null,
|
|
240
|
+
failedReason: job.failedReason,
|
|
241
|
+
stacktrace: job.stacktrace,
|
|
242
|
+
priority: job.opts?.priority,
|
|
243
|
+
delay: job.opts?.delay,
|
|
244
|
+
timestamp: job.timestamp,
|
|
245
|
+
progress: typeof job.progress === "number" ? job.progress : null,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
extractBullJobData(job, queueName) {
|
|
249
|
+
return {
|
|
250
|
+
jobId: job.id,
|
|
251
|
+
name: job.name || "default",
|
|
252
|
+
queueName,
|
|
253
|
+
data: job.data,
|
|
254
|
+
opts: job.opts,
|
|
255
|
+
addedAt: new Date(job.timestamp),
|
|
256
|
+
processedAt: job.processedOn ? new Date(job.processedOn) : null,
|
|
257
|
+
finishedAt: job.finishedOn ? new Date(job.finishedOn) : null,
|
|
258
|
+
failedReason: job.failedReason,
|
|
259
|
+
stacktrace: job.stacktrace,
|
|
260
|
+
priority: job.opts?.priority,
|
|
261
|
+
delay: job.opts?.delay,
|
|
262
|
+
timestamp: job.timestamp,
|
|
263
|
+
progress: job.progress(),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
extractBeeJobData(job, queueName) {
|
|
267
|
+
return {
|
|
268
|
+
jobId: String(job.id),
|
|
269
|
+
name: "default",
|
|
270
|
+
queueName,
|
|
271
|
+
data: job.data,
|
|
272
|
+
opts: job.options || {},
|
|
273
|
+
addedAt: job.options?.timestamp
|
|
274
|
+
? new Date(job.options.timestamp)
|
|
275
|
+
: new Date(),
|
|
276
|
+
processedAt: null,
|
|
277
|
+
finishedAt: null,
|
|
278
|
+
priority: null,
|
|
279
|
+
delay: job.options?.delay,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Delete a job (soft delete in Queuedash)
|
|
35
284
|
*/
|
|
36
285
|
async deleteJob(jobId, queueName) {
|
|
37
286
|
try {
|
|
@@ -56,32 +305,27 @@ class QueueDashClient {
|
|
|
56
305
|
this.onError(new Error(`Failed to delete job ${jobId}: ${err.message}`));
|
|
57
306
|
}
|
|
58
307
|
else {
|
|
59
|
-
console.error(`
|
|
308
|
+
console.error(`Queuedash: Failed to delete job ${jobId}`, err);
|
|
60
309
|
}
|
|
61
310
|
}
|
|
62
311
|
}
|
|
63
312
|
/**
|
|
64
|
-
* Queue a job for syncing
|
|
313
|
+
* Queue a job for syncing (internal use)
|
|
65
314
|
*/
|
|
66
315
|
syncJob(job) {
|
|
67
|
-
// Check circuit breaker
|
|
68
316
|
if (this.circuitBreakerOpen) {
|
|
69
|
-
this.logger("Circuit breaker open, dropping job");
|
|
70
317
|
return;
|
|
71
318
|
}
|
|
72
|
-
// Prevent duplicate syncs of same job
|
|
73
319
|
const jobKey = `${job.queueName}:${job.jobId}`;
|
|
74
320
|
if (this.syncingJobIds.has(jobKey)) {
|
|
75
321
|
return;
|
|
76
322
|
}
|
|
77
323
|
this.syncingJobIds.add(jobKey);
|
|
78
|
-
// Prevent memory leak - drop oldest jobs if queue too large
|
|
79
324
|
if (this.jobQueue.length >= this.maxQueueSize) {
|
|
80
325
|
const dropped = this.jobQueue.shift();
|
|
81
|
-
this.
|
|
326
|
+
this.log(`Queue size limit reached (${this.maxQueueSize}), dropping oldest job: ${dropped?.jobId}`);
|
|
82
327
|
}
|
|
83
328
|
this.jobQueue.push(job);
|
|
84
|
-
// Flush if batch size reached
|
|
85
329
|
if (this.jobQueue.length >= this.batchSize) {
|
|
86
330
|
this.flush();
|
|
87
331
|
}
|
|
@@ -99,7 +343,6 @@ class QueueDashClient {
|
|
|
99
343
|
const jobsToSync = [...this.jobQueue];
|
|
100
344
|
this.jobQueue = [];
|
|
101
345
|
try {
|
|
102
|
-
// Add timeout to fetch
|
|
103
346
|
const controller = new AbortController();
|
|
104
347
|
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
105
348
|
const response = await fetch(`${this.apiUrl}/api/v1/jobs/sync`, {
|
|
@@ -108,9 +351,7 @@ class QueueDashClient {
|
|
|
108
351
|
"Content-Type": "application/json",
|
|
109
352
|
Authorization: `Bearer ${this.apiKey}`,
|
|
110
353
|
},
|
|
111
|
-
body: JSON.stringify({
|
|
112
|
-
jobs: jobsToSync,
|
|
113
|
-
}),
|
|
354
|
+
body: JSON.stringify({ jobs: jobsToSync }),
|
|
114
355
|
signal: controller.signal,
|
|
115
356
|
});
|
|
116
357
|
clearTimeout(timeoutId);
|
|
@@ -120,9 +361,8 @@ class QueueDashClient {
|
|
|
120
361
|
}
|
|
121
362
|
const result = (await response.json());
|
|
122
363
|
if (!result.success && result.errors) {
|
|
123
|
-
console.warn("
|
|
364
|
+
console.warn("Queuedash: Some jobs failed to sync:", result.errors);
|
|
124
365
|
}
|
|
125
|
-
// Success - reset counters and clear syncing jobs
|
|
126
366
|
this.retryCount = 0;
|
|
127
367
|
this.circuitBreakerFailures = 0;
|
|
128
368
|
jobsToSync.forEach((job) => {
|
|
@@ -133,17 +373,13 @@ class QueueDashClient {
|
|
|
133
373
|
this.retryCount++;
|
|
134
374
|
this.circuitBreakerFailures++;
|
|
135
375
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
136
|
-
// Circuit breaker - open after 10 consecutive failures
|
|
137
376
|
if (this.circuitBreakerFailures >= 10) {
|
|
138
377
|
this.openCircuitBreaker();
|
|
139
378
|
}
|
|
140
|
-
// Check if we should retry
|
|
141
379
|
if (this.retryCount <= this.maxRetries) {
|
|
142
|
-
// Put jobs back in queue for retry with exponential backoff
|
|
143
380
|
this.jobQueue.unshift(...jobsToSync);
|
|
144
|
-
const backoffMs = Math.min(1000 * Math.pow(2, this.retryCount - 1), 30000);
|
|
145
|
-
this.
|
|
146
|
-
// Schedule retry
|
|
381
|
+
const backoffMs = Math.min(1000 * Math.pow(2, this.retryCount - 1), 30000);
|
|
382
|
+
this.log(`Retry ${this.retryCount}/${this.maxRetries} after ${backoffMs}ms`, err);
|
|
147
383
|
setTimeout(() => {
|
|
148
384
|
if (this.jobQueue.length > 0) {
|
|
149
385
|
this.flush();
|
|
@@ -151,7 +387,6 @@ class QueueDashClient {
|
|
|
151
387
|
}, backoffMs);
|
|
152
388
|
}
|
|
153
389
|
else {
|
|
154
|
-
// Max retries reached - clear syncing jobs and alert
|
|
155
390
|
this.retryCount = 0;
|
|
156
391
|
jobsToSync.forEach((job) => {
|
|
157
392
|
this.syncingJobIds.delete(`${job.queueName}:${job.jobId}`);
|
|
@@ -161,7 +396,7 @@ class QueueDashClient {
|
|
|
161
396
|
this.onError(new Error(`${errorMsg}: ${err.message}`));
|
|
162
397
|
}
|
|
163
398
|
else {
|
|
164
|
-
console.error(`
|
|
399
|
+
console.error(`Queuedash: ${errorMsg}`, err);
|
|
165
400
|
}
|
|
166
401
|
}
|
|
167
402
|
}
|
|
@@ -173,28 +408,26 @@ class QueueDashClient {
|
|
|
173
408
|
if (this.circuitBreakerOpen)
|
|
174
409
|
return;
|
|
175
410
|
this.circuitBreakerOpen = true;
|
|
176
|
-
console.error("
|
|
177
|
-
// Auto-reset after 60 seconds
|
|
411
|
+
console.error("Queuedash: Circuit breaker opened after 10 consecutive failures. Will retry in 60s.");
|
|
178
412
|
this.circuitBreakerResetTimer = setTimeout(() => {
|
|
179
413
|
this.circuitBreakerOpen = false;
|
|
180
414
|
this.circuitBreakerFailures = 0;
|
|
181
|
-
console.log("
|
|
415
|
+
console.log("Queuedash: Circuit breaker reset, resuming sync attempts");
|
|
182
416
|
}, 60000);
|
|
183
417
|
if (this.circuitBreakerResetTimer.unref) {
|
|
184
418
|
this.circuitBreakerResetTimer.unref();
|
|
185
419
|
}
|
|
186
420
|
}
|
|
187
|
-
|
|
421
|
+
log(message, error) {
|
|
188
422
|
if (this.onError && error) {
|
|
189
|
-
|
|
190
|
-
console.warn(`QueueDash: ${message}`, error);
|
|
423
|
+
console.warn(`Queuedash: ${message}`, error);
|
|
191
424
|
}
|
|
192
425
|
else {
|
|
193
|
-
console.warn(`
|
|
426
|
+
console.warn(`Queuedash: ${message}`);
|
|
194
427
|
}
|
|
195
428
|
}
|
|
196
429
|
/**
|
|
197
|
-
* Stop the client and flush remaining jobs
|
|
430
|
+
* Stop the client, close managed resources, and flush remaining jobs
|
|
198
431
|
*/
|
|
199
432
|
async stop() {
|
|
200
433
|
if (this.flushTimer) {
|
|
@@ -203,6 +436,15 @@ class QueueDashClient {
|
|
|
203
436
|
if (this.circuitBreakerResetTimer) {
|
|
204
437
|
clearTimeout(this.circuitBreakerResetTimer);
|
|
205
438
|
}
|
|
439
|
+
// Close any QueueEvents we created
|
|
440
|
+
for (const events of this.managedQueueEvents) {
|
|
441
|
+
try {
|
|
442
|
+
await events.close();
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// Ignore close errors
|
|
446
|
+
}
|
|
447
|
+
}
|
|
206
448
|
await this.flush();
|
|
207
449
|
}
|
|
208
450
|
startFlushTimer() {
|
|
@@ -211,25 +453,23 @@ class QueueDashClient {
|
|
|
211
453
|
this.flush();
|
|
212
454
|
}
|
|
213
455
|
}, this.flushInterval);
|
|
214
|
-
// Don't keep process alive
|
|
215
456
|
if (this.flushTimer.unref) {
|
|
216
457
|
this.flushTimer.unref();
|
|
217
458
|
}
|
|
218
459
|
}
|
|
219
460
|
setupShutdownHandlers() {
|
|
220
461
|
const shutdown = async () => {
|
|
221
|
-
console.log("
|
|
462
|
+
console.log("Queuedash: Graceful shutdown - flushing remaining jobs...");
|
|
222
463
|
await this.stop();
|
|
223
464
|
process.exit(0);
|
|
224
465
|
};
|
|
225
|
-
// Handle graceful shutdown
|
|
226
466
|
process.once("SIGTERM", shutdown);
|
|
227
467
|
process.once("SIGINT", shutdown);
|
|
228
468
|
process.once("beforeExit", () => {
|
|
229
469
|
if (this.jobQueue.length > 0) {
|
|
230
|
-
console.warn(`
|
|
470
|
+
console.warn(`Queuedash: Process exiting with ${this.jobQueue.length} unsync'd jobs`);
|
|
231
471
|
}
|
|
232
472
|
});
|
|
233
473
|
}
|
|
234
474
|
}
|
|
235
|
-
exports.
|
|
475
|
+
exports.Queuedash = Queuedash;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.spec.d.ts","sourceRoot":"","sources":["../src/client.spec.ts"],"names":[],"mappings":""}
|