@queuedash/sdk 0.1.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 +46 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +316 -46
- 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 -8
- package/dist/integrations/bull.d.ts.map +0 -1
- package/dist/integrations/bull.js +0 -45
- package/dist/integrations/bullmq.d.ts +0 -8
- package/dist/integrations/bullmq.d.ts.map +0 -1
- package/dist/integrations/bullmq.js +0 -46
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,49 +42,290 @@ 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)
|
|
284
|
+
*/
|
|
285
|
+
async deleteJob(jobId, queueName) {
|
|
286
|
+
try {
|
|
287
|
+
const controller = new AbortController();
|
|
288
|
+
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
289
|
+
const response = await fetch(`${this.apiUrl}/api/v1/jobs/${encodeURIComponent(jobId)}?queueName=${encodeURIComponent(queueName)}`, {
|
|
290
|
+
method: "DELETE",
|
|
291
|
+
headers: {
|
|
292
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
293
|
+
},
|
|
294
|
+
signal: controller.signal,
|
|
295
|
+
});
|
|
296
|
+
clearTimeout(timeoutId);
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
const errorText = await response.text();
|
|
299
|
+
throw new Error(`HTTP ${response.status}: ${errorText}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
304
|
+
if (this.onError) {
|
|
305
|
+
this.onError(new Error(`Failed to delete job ${jobId}: ${err.message}`));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
console.error(`Queuedash: Failed to delete job ${jobId}`, err);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Queue a job for syncing (internal use)
|
|
35
314
|
*/
|
|
36
315
|
syncJob(job) {
|
|
37
|
-
// Check circuit breaker
|
|
38
316
|
if (this.circuitBreakerOpen) {
|
|
39
|
-
this.logger("Circuit breaker open, dropping job");
|
|
40
317
|
return;
|
|
41
318
|
}
|
|
42
|
-
// Prevent duplicate syncs of same job
|
|
43
319
|
const jobKey = `${job.queueName}:${job.jobId}`;
|
|
44
320
|
if (this.syncingJobIds.has(jobKey)) {
|
|
45
321
|
return;
|
|
46
322
|
}
|
|
47
323
|
this.syncingJobIds.add(jobKey);
|
|
48
|
-
// Prevent memory leak - drop oldest jobs if queue too large
|
|
49
324
|
if (this.jobQueue.length >= this.maxQueueSize) {
|
|
50
325
|
const dropped = this.jobQueue.shift();
|
|
51
|
-
this.
|
|
326
|
+
this.log(`Queue size limit reached (${this.maxQueueSize}), dropping oldest job: ${dropped?.jobId}`);
|
|
52
327
|
}
|
|
53
328
|
this.jobQueue.push(job);
|
|
54
|
-
// Flush if batch size reached
|
|
55
329
|
if (this.jobQueue.length >= this.batchSize) {
|
|
56
330
|
this.flush();
|
|
57
331
|
}
|
|
@@ -69,7 +343,6 @@ class QueueDashClient {
|
|
|
69
343
|
const jobsToSync = [...this.jobQueue];
|
|
70
344
|
this.jobQueue = [];
|
|
71
345
|
try {
|
|
72
|
-
// Add timeout to fetch
|
|
73
346
|
const controller = new AbortController();
|
|
74
347
|
const timeoutId = setTimeout(() => controller.abort(), this.requestTimeout);
|
|
75
348
|
const response = await fetch(`${this.apiUrl}/api/v1/jobs/sync`, {
|
|
@@ -78,9 +351,7 @@ class QueueDashClient {
|
|
|
78
351
|
"Content-Type": "application/json",
|
|
79
352
|
Authorization: `Bearer ${this.apiKey}`,
|
|
80
353
|
},
|
|
81
|
-
body: JSON.stringify({
|
|
82
|
-
jobs: jobsToSync,
|
|
83
|
-
}),
|
|
354
|
+
body: JSON.stringify({ jobs: jobsToSync }),
|
|
84
355
|
signal: controller.signal,
|
|
85
356
|
});
|
|
86
357
|
clearTimeout(timeoutId);
|
|
@@ -90,9 +361,8 @@ class QueueDashClient {
|
|
|
90
361
|
}
|
|
91
362
|
const result = (await response.json());
|
|
92
363
|
if (!result.success && result.errors) {
|
|
93
|
-
console.warn("
|
|
364
|
+
console.warn("Queuedash: Some jobs failed to sync:", result.errors);
|
|
94
365
|
}
|
|
95
|
-
// Success - reset counters and clear syncing jobs
|
|
96
366
|
this.retryCount = 0;
|
|
97
367
|
this.circuitBreakerFailures = 0;
|
|
98
368
|
jobsToSync.forEach((job) => {
|
|
@@ -103,17 +373,13 @@ class QueueDashClient {
|
|
|
103
373
|
this.retryCount++;
|
|
104
374
|
this.circuitBreakerFailures++;
|
|
105
375
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
106
|
-
// Circuit breaker - open after 10 consecutive failures
|
|
107
376
|
if (this.circuitBreakerFailures >= 10) {
|
|
108
377
|
this.openCircuitBreaker();
|
|
109
378
|
}
|
|
110
|
-
// Check if we should retry
|
|
111
379
|
if (this.retryCount <= this.maxRetries) {
|
|
112
|
-
// Put jobs back in queue for retry with exponential backoff
|
|
113
380
|
this.jobQueue.unshift(...jobsToSync);
|
|
114
|
-
const backoffMs = Math.min(1000 * Math.pow(2, this.retryCount - 1), 30000);
|
|
115
|
-
this.
|
|
116
|
-
// 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);
|
|
117
383
|
setTimeout(() => {
|
|
118
384
|
if (this.jobQueue.length > 0) {
|
|
119
385
|
this.flush();
|
|
@@ -121,7 +387,6 @@ class QueueDashClient {
|
|
|
121
387
|
}, backoffMs);
|
|
122
388
|
}
|
|
123
389
|
else {
|
|
124
|
-
// Max retries reached - clear syncing jobs and alert
|
|
125
390
|
this.retryCount = 0;
|
|
126
391
|
jobsToSync.forEach((job) => {
|
|
127
392
|
this.syncingJobIds.delete(`${job.queueName}:${job.jobId}`);
|
|
@@ -131,7 +396,7 @@ class QueueDashClient {
|
|
|
131
396
|
this.onError(new Error(`${errorMsg}: ${err.message}`));
|
|
132
397
|
}
|
|
133
398
|
else {
|
|
134
|
-
console.error(`
|
|
399
|
+
console.error(`Queuedash: ${errorMsg}`, err);
|
|
135
400
|
}
|
|
136
401
|
}
|
|
137
402
|
}
|
|
@@ -143,28 +408,26 @@ class QueueDashClient {
|
|
|
143
408
|
if (this.circuitBreakerOpen)
|
|
144
409
|
return;
|
|
145
410
|
this.circuitBreakerOpen = true;
|
|
146
|
-
console.error("
|
|
147
|
-
// Auto-reset after 60 seconds
|
|
411
|
+
console.error("Queuedash: Circuit breaker opened after 10 consecutive failures. Will retry in 60s.");
|
|
148
412
|
this.circuitBreakerResetTimer = setTimeout(() => {
|
|
149
413
|
this.circuitBreakerOpen = false;
|
|
150
414
|
this.circuitBreakerFailures = 0;
|
|
151
|
-
console.log("
|
|
415
|
+
console.log("Queuedash: Circuit breaker reset, resuming sync attempts");
|
|
152
416
|
}, 60000);
|
|
153
417
|
if (this.circuitBreakerResetTimer.unref) {
|
|
154
418
|
this.circuitBreakerResetTimer.unref();
|
|
155
419
|
}
|
|
156
420
|
}
|
|
157
|
-
|
|
421
|
+
log(message, error) {
|
|
158
422
|
if (this.onError && error) {
|
|
159
|
-
|
|
160
|
-
console.warn(`QueueDash: ${message}`, error);
|
|
423
|
+
console.warn(`Queuedash: ${message}`, error);
|
|
161
424
|
}
|
|
162
425
|
else {
|
|
163
|
-
console.warn(`
|
|
426
|
+
console.warn(`Queuedash: ${message}`);
|
|
164
427
|
}
|
|
165
428
|
}
|
|
166
429
|
/**
|
|
167
|
-
* Stop the client and flush remaining jobs
|
|
430
|
+
* Stop the client, close managed resources, and flush remaining jobs
|
|
168
431
|
*/
|
|
169
432
|
async stop() {
|
|
170
433
|
if (this.flushTimer) {
|
|
@@ -173,6 +436,15 @@ class QueueDashClient {
|
|
|
173
436
|
if (this.circuitBreakerResetTimer) {
|
|
174
437
|
clearTimeout(this.circuitBreakerResetTimer);
|
|
175
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
|
+
}
|
|
176
448
|
await this.flush();
|
|
177
449
|
}
|
|
178
450
|
startFlushTimer() {
|
|
@@ -181,25 +453,23 @@ class QueueDashClient {
|
|
|
181
453
|
this.flush();
|
|
182
454
|
}
|
|
183
455
|
}, this.flushInterval);
|
|
184
|
-
// Don't keep process alive
|
|
185
456
|
if (this.flushTimer.unref) {
|
|
186
457
|
this.flushTimer.unref();
|
|
187
458
|
}
|
|
188
459
|
}
|
|
189
460
|
setupShutdownHandlers() {
|
|
190
461
|
const shutdown = async () => {
|
|
191
|
-
console.log("
|
|
462
|
+
console.log("Queuedash: Graceful shutdown - flushing remaining jobs...");
|
|
192
463
|
await this.stop();
|
|
193
464
|
process.exit(0);
|
|
194
465
|
};
|
|
195
|
-
// Handle graceful shutdown
|
|
196
466
|
process.once("SIGTERM", shutdown);
|
|
197
467
|
process.once("SIGINT", shutdown);
|
|
198
468
|
process.once("beforeExit", () => {
|
|
199
469
|
if (this.jobQueue.length > 0) {
|
|
200
|
-
console.warn(`
|
|
470
|
+
console.warn(`Queuedash: Process exiting with ${this.jobQueue.length} unsync'd jobs`);
|
|
201
471
|
}
|
|
202
472
|
});
|
|
203
473
|
}
|
|
204
474
|
}
|
|
205
|
-
exports.
|
|
475
|
+
exports.Queuedash = Queuedash;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.spec.d.ts","sourceRoot":"","sources":["../src/client.spec.ts"],"names":[],"mappings":""}
|