@spooled/sdk 1.0.34 → 1.0.35
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/{index-CYEamGDj.d.cts → index-B96n0WUe.d.cts} +6 -5
- package/dist/{index-CYEamGDj.d.ts → index-B96n0WUe.d.ts} +6 -5
- package/dist/index.cjs +24 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +24 -21
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +24 -20
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.d.cts +1 -1
- package/dist/worker/index.d.ts +1 -1
- package/dist/worker/index.js +24 -20
- package/dist/worker/index.js.map +1 -1
- package/package.json +1 -1
package/dist/worker/index.cjs
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var os = require('os');
|
|
4
4
|
|
|
5
|
-
// src/
|
|
5
|
+
// src/config.ts
|
|
6
|
+
var SDK_VERSION = "1.0.35";
|
|
6
7
|
var DEFAULT_OPTIONS = {
|
|
7
8
|
concurrency: 5,
|
|
8
9
|
pollInterval: 1e3,
|
|
@@ -30,7 +31,7 @@ var SpooledWorker = class {
|
|
|
30
31
|
...DEFAULT_OPTIONS,
|
|
31
32
|
hostname: os.hostname(),
|
|
32
33
|
workerType: "nodejs",
|
|
33
|
-
version:
|
|
34
|
+
version: SDK_VERSION,
|
|
34
35
|
metadata: {},
|
|
35
36
|
...options
|
|
36
37
|
};
|
|
@@ -137,11 +138,11 @@ var SpooledWorker = class {
|
|
|
137
138
|
this.waitForActiveJobs(),
|
|
138
139
|
this.sleep(this.options.shutdownTimeout)
|
|
139
140
|
]);
|
|
140
|
-
const remaining = Array.from(this.activeJobs.
|
|
141
|
-
for (const
|
|
142
|
-
this.debug(`Force-failing job ${
|
|
141
|
+
const remaining = Array.from(this.activeJobs.values());
|
|
142
|
+
for (const active of remaining) {
|
|
143
|
+
this.debug(`Force-failing job ${active.job.id} due to shutdown timeout`);
|
|
143
144
|
await this.failJob(active.job, "Worker shutdown timeout");
|
|
144
|
-
this.cleanupJob(
|
|
145
|
+
this.cleanupJob(active);
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
if (this.workerId) {
|
|
@@ -224,14 +225,15 @@ var SpooledWorker = class {
|
|
|
224
225
|
this.emit("job:claimed", { jobId: job.id, queueName: job.queueName });
|
|
225
226
|
const abortController = new AbortController();
|
|
226
227
|
const activeJob = {
|
|
228
|
+
executionId: Symbol(job.id),
|
|
227
229
|
job,
|
|
228
230
|
startedAt: /* @__PURE__ */ new Date(),
|
|
229
231
|
abortController
|
|
230
232
|
};
|
|
231
|
-
this.activeJobs.set(
|
|
233
|
+
this.activeJobs.set(activeJob.executionId, activeJob);
|
|
232
234
|
const heartbeatInterval = this.options.leaseDuration * this.options.heartbeatFraction * 1e3;
|
|
233
235
|
activeJob.heartbeatTimer = setInterval(() => {
|
|
234
|
-
this.sendJobHeartbeat(
|
|
236
|
+
this.sendJobHeartbeat(activeJob);
|
|
235
237
|
}, heartbeatInterval);
|
|
236
238
|
this.executeHandler(activeJob);
|
|
237
239
|
}
|
|
@@ -267,7 +269,7 @@ var SpooledWorker = class {
|
|
|
267
269
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
268
270
|
await this.failJob(job, errorMessage);
|
|
269
271
|
} finally {
|
|
270
|
-
this.cleanupJob(
|
|
272
|
+
this.cleanupJob(activeJob);
|
|
271
273
|
}
|
|
272
274
|
}
|
|
273
275
|
async completeJob(job, result) {
|
|
@@ -276,7 +278,7 @@ var SpooledWorker = class {
|
|
|
276
278
|
await this.client.jobs.complete(job.id, {
|
|
277
279
|
workerId: this.workerId,
|
|
278
280
|
result,
|
|
279
|
-
...job.leaseId
|
|
281
|
+
...job.leaseId != null && { leaseId: job.leaseId }
|
|
280
282
|
});
|
|
281
283
|
this.emit("job:completed", {
|
|
282
284
|
jobId: job.id,
|
|
@@ -294,7 +296,7 @@ var SpooledWorker = class {
|
|
|
294
296
|
await this.client.jobs.fail(job.id, {
|
|
295
297
|
workerId: this.workerId,
|
|
296
298
|
error: errorMessage,
|
|
297
|
-
...job.leaseId
|
|
299
|
+
...job.leaseId != null && { leaseId: job.leaseId }
|
|
298
300
|
});
|
|
299
301
|
this.emit("job:failed", {
|
|
300
302
|
jobId: job.id,
|
|
@@ -306,24 +308,26 @@ var SpooledWorker = class {
|
|
|
306
308
|
this.debug(`Failed to fail job ${job.id}`, error);
|
|
307
309
|
}
|
|
308
310
|
}
|
|
309
|
-
cleanupJob(
|
|
310
|
-
|
|
311
|
-
if (activeJob?.heartbeatTimer) {
|
|
311
|
+
cleanupJob(activeJob) {
|
|
312
|
+
if (activeJob.heartbeatTimer) {
|
|
312
313
|
clearInterval(activeJob.heartbeatTimer);
|
|
314
|
+
activeJob.heartbeatTimer = void 0;
|
|
315
|
+
}
|
|
316
|
+
if (this.activeJobs.get(activeJob.executionId) === activeJob) {
|
|
317
|
+
this.activeJobs.delete(activeJob.executionId);
|
|
313
318
|
}
|
|
314
|
-
this.activeJobs.delete(jobId);
|
|
315
319
|
}
|
|
316
|
-
async sendJobHeartbeat(
|
|
320
|
+
async sendJobHeartbeat(activeJob) {
|
|
317
321
|
if (!this.workerId) return;
|
|
318
|
-
const
|
|
322
|
+
const { job } = activeJob;
|
|
319
323
|
try {
|
|
320
|
-
await this.client.jobs.heartbeat(
|
|
324
|
+
await this.client.jobs.heartbeat(job.id, {
|
|
321
325
|
workerId: this.workerId,
|
|
322
326
|
leaseDurationSecs: this.options.leaseDuration,
|
|
323
|
-
...leaseId
|
|
327
|
+
...job.leaseId != null && { leaseId: job.leaseId }
|
|
324
328
|
});
|
|
325
329
|
} catch (error) {
|
|
326
|
-
this.debug(`Job heartbeat failed for ${
|
|
330
|
+
this.debug(`Job heartbeat failed for ${job.id}`, error);
|
|
327
331
|
}
|
|
328
332
|
}
|
|
329
333
|
async sendWorkerHeartbeat() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/worker/worker.ts"],"names":["osHostname"],"mappings":";;;;;AAqBA,IAAM,eAAA,GAAkB;AAAA,EACtB,WAAA,EAAa,CAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,aAAA,EAAe,EAAA;AAAA,EACf,iBAAA,EAAmB,GAAA;AAAA,EACnB,SAAA,EAAW,KAAA;AAAA,EACX,eAAA,EAAiB;AACnB,CAAA;AA2BO,IAAM,gBAAN,MAAoB;AAAA,EACR,MAAA;AAAA,EACA,OAAA;AAAA,EACA,KAAA;AAAA,EAET,KAAA,GAAqB,MAAA;AAAA,EACrB,QAAA,GAA0B,IAAA;AAAA,EAC1B,OAAA,GAA6B,IAAA;AAAA,EAC7B,UAAA,uBAAyC,GAAA,EAAI;AAAA,EAC7C,SAAA,GAAkD,IAAA;AAAA,EAClD,oBAAA,GAA8D,IAAA;AAAA,EAC9D,eAAA,GAAwC,IAAA;AAAA;AAAA,EAGxC,aAAA,uBAAsE,GAAA,EAAI;AAAA,EAElF,WAAA,CAAY,QAAuB,OAAA,EAA+B;AAChE,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AACd,IAAA,IAAA,CAAK,OAAA,GAAU;AAAA,MACb,GAAG,eAAA;AAAA,MACH,UAAUA,WAAA,EAAW;AAAA,MACrB,UAAA,EAAY,QAAA;AAAA,MACZ,OAAA,EAAS,QAAA;AAAA,MACT,UAAU,EAAC;AAAA,MACX,GAAG;AAAA,KACL;AAGA,IAAA,MAAM,MAAA,GAAS,OAAO,SAAA,EAAU;AAChC,IAAA,IAAA,CAAK,KAAA,GAAQ,MAAA,CAAO,KAAA,KAAU,MAAM;AAAA,IAAC,CAAA,CAAA;AAErC,IAAA,IAAI,IAAA,CAAK,QAAQ,SAAA,EAAW;AAE1B,MAAA,YAAA,CAAa,MAAM,IAAA,CAAK,KAAA,EAAO,CAAA;AAAA,IACjC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAwB;AACtB,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,WAAA,GAA6B;AAC3B,IAAA,OAAO,IAAA,CAAK,QAAA;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,iBAAA,GAA4B;AAC1B,IAAA,OAAO,KAAK,UAAA,CAAW,IAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,OAAA,EAA2B;AACjC,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAQ;AACzB,MAAA,MAAM,IAAI,MAAM,6CAA6C,CAAA;AAAA,IAC/D;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAA,GAAuB;AAC3B,IAAA,IAAI,IAAA,CAAK,UAAU,MAAA,EAAQ;AACzB,MAAA,MAAM,IAAI,KAAA,CAAM,CAAA,8BAAA,EAAiC,IAAA,CAAK,KAAK,CAAA,CAAE,CAAA;AAAA,IAC/D;AAEA,IAAA,IAAI,CAAC,KAAK,OAAA,EAAS;AACjB,MAAA,MAAM,IAAI,MAAM,kDAAkD,CAAA;AAAA,IACpE;AAEA,IAAA,IAAA,CAAK,KAAA,GAAQ,UAAA;AACb,IAAA,IAAA,CAAK,KAAA,CAAM,CAAA,2BAAA,EAA8B,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA,CAAE,CAAA;AAEjE,IAAA,IAAI;AAEF,MAAA,MAAM,YAAA,GAAe,MAAM,IAAA,CAAK,MAAA,CAAO,QAAQ,QAAA,CAAS;AAAA,QACtD,SAAA,EAAW,KAAK,OAAA,CAAQ,SAAA;AAAA,QACxB,QAAA,EAAU,KAAK,OAAA,CAAQ,QAAA;AAAA,QACvB,UAAA,EAAY,KAAK,OAAA,CAAQ,UAAA;AAAA,QACzB,cAAA,EAAgB,KAAK,OAAA,CAAQ,WAAA;AAAA,QAC7B,QAAA,EAAU,KAAK,OAAA,CAAQ,QAAA;AAAA,QACvB,OAAA,EAAS,KAAK,OAAA,CAAQ;AAAA,OACvB,CAAA;AAED,MAAA,IAAA,CAAK,WAAW,YAAA,CAAa,EAAA;AAC7B,MAAA,IAAA,CAAK,KAAA,CAAM,CAAA,mBAAA,EAAsB,IAAA,CAAK,QAAQ,IAAI,YAAY,CAAA;AAG9D,MAAA,MAAM,iBAAA,GAAoB,aAAa,qBAAA,GAAwB,GAAA;AAC/D,MAAA,IAAA,CAAK,oBAAA,GAAuB,YAAY,MAAM;AAC5C,QAAA,IAAA,CAAK,mBAAA,EAAoB;AAAA,MAC3B,GAAG,iBAAiB,CAAA;AAGpB,MAAA,IAAA,CAAK,KAAA,GAAQ,SAAA;AACb,MAAA,IAAA,CAAK,IAAA,CAAK,SAAA,EAAW,EAAE,QAAA,EAAU,IAAA,CAAK,UAAU,SAAA,EAAW,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,CAAA;AACnF,MAAA,IAAA,CAAK,YAAA,EAAa;AAAA,IACpB,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,KAAA,GAAQ,OAAA;AACb,MAAA,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,EAAE,KAAA,EAAuB,CAAA;AAC5C,MAAA,MAAM,KAAA;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAA,GAAsB;AAC1B,IAAA,IAAI,IAAA,CAAK,UAAU,SAAA,EAAW;AAC5B,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,KAAK,eAAA,EAAiB;AACxB,MAAA,OAAO,IAAA,CAAK,eAAA;AAAA,IACd;AAEA,IAAA,IAAA,CAAK,eAAA,GAAkB,KAAK,MAAA,EAAO;AACnC,IAAA,OAAO,IAAA,CAAK,eAAA;AAAA,EACd;AAAA,EAEA,MAAc,MAAA,GAAwB;AACpC,IAAA,IAAA,CAAK,KAAA,GAAQ,UAAA;AACb,IAAA,IAAA,CAAK,MAAM,oBAAoB,CAAA;AAG/B,IAAA,IAAI,KAAK,SAAA,EAAW;AAClB,MAAA,YAAA,CAAa,KAAK,SAAS,CAAA;AAC3B,MAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AAAA,IACnB;AAGA,IAAA,IAAI,KAAK,oBAAA,EAAsB;AAC7B,MAAA,aAAA,CAAc,KAAK,oBAAoB,CAAA;AACvC,MAAA,IAAA,CAAK,oBAAA,GAAuB,IAAA;AAAA,IAC9B;AAGA,IAAA,KAAA,MAAW,MAAA,IAAU,IAAA,CAAK,UAAA,CAAW,MAAA,EAAO,EAAG;AAC7C,MAAA,MAAA,CAAO,gBAAgB,KAAA,EAAM;AAAA,IAC/B;AAGA,IAAA,IAAI,IAAA,CAAK,UAAA,CAAW,IAAA,GAAO,CAAA,EAAG;AAC5B,MAAA,IAAA,CAAK,KAAA,CAAM,CAAA,YAAA,EAAe,IAAA,CAAK,UAAA,CAAW,IAAI,CAAA,2BAAA,CAA6B,CAAA;AAE3E,MAAA,MAAM,QAAQ,IAAA,CAAK;AAAA,QACjB,KAAK,iBAAA,EAAkB;AAAA,QACvB,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,OAAA,CAAQ,eAAe;AAAA,OACxC,CAAA;AAID,MAAA,MAAM,YAAY,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,UAAA,CAAW,SAAS,CAAA;AACtD,MAAA,KAAA,MAAW,CAAC,KAAA,EAAO,MAAM,CAAA,IAAK,SAAA,EAAW;AACvC,QAAA,IAAA,CAAK,KAAA,CAAM,CAAA,kBAAA,EAAqB,KAAK,CAAA,wBAAA,CAA0B,CAAA;AAC/D,QAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,GAAA,EAAK,yBAAyB,CAAA;AAIxD,QAAA,IAAA,CAAK,WAAW,KAAK,CAAA;AAAA,MACvB;AAAA,IACF;AAGA,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,IAAI;AACF,QAAA,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,UAAA,CAAW,KAAK,QAAQ,CAAA;AAClD,QAAA,IAAA,CAAK,MAAM,qBAAqB,CAAA;AAAA,MAClC,SAAS,KAAA,EAAO;AACd,QAAA,IAAA,CAAK,KAAA,CAAM,+BAA+B,KAAK,CAAA;AAAA,MACjD;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,KAAA,GAAQ,SAAA;AACb,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,IAAA,CAAK,WAAW,EAAE,QAAA,EAAU,KAAK,QAAA,IAAY,EAAA,EAAI,MAAA,EAAQ,UAAA,EAAY,CAAA;AAC1E,IAAA,IAAA,CAAK,eAAA,GAAkB,IAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,EAAA,CAA0B,OAAU,OAAA,EAAsC;AACxE,IAAA,IAAI,CAAC,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA,EAAG;AAClC,MAAA,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,KAAA,kBAAO,IAAI,KAAK,CAAA;AAAA,IACzC;AACA,IAAA,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA,CAAG,IAAI,OAAoC,CAAA;AACvE,IAAA,OAAO,MAAM,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,OAAO,CAAA;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA,EAKA,GAAA,CAA2B,OAAU,OAAA,EAAgC;AACnE,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA;AAC7C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,OAAO,OAAoC,CAAA;AAAA,IACtD;AAAA,EACF;AAAA;AAAA,EAIQ,IAAA,CAA4B,OAAU,IAAA,EAAgC;AAC5E,IAAA,MAAM,QAAA,GAAW,IAAA,CAAK,aAAA,CAAc,GAAA,CAAI,KAAK,CAAA;AAC7C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,QAAA,CAAS,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC5B,QAAA,IAAI;AACF,UAAA,OAAA,CAAQ,IAAI,CAAA;AAAA,QACd,SAAS,KAAA,EAAO;AACd,UAAA,IAAA,CAAK,KAAA,CAAM,qBAAA,EAAuB,EAAE,KAAA,EAAO,OAAO,CAAA;AAAA,QACpD;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,YAAA,GAAqB;AAC3B,IAAA,IAAI,IAAA,CAAK,UAAU,SAAA,EAAW;AAC5B,MAAA;AAAA,IACF;AAEA,IAAA,IAAA,CAAK,SAAA,GAAY,WAAW,MAAM,IAAA,CAAK,MAAK,EAAG,IAAA,CAAK,QAAQ,YAAY,CAAA;AAAA,EAC1E;AAAA,EAEA,MAAc,IAAA,GAAsB;AAClC,IAAA,IAAI,IAAA,CAAK,KAAA,KAAU,SAAA,IAAa,CAAC,KAAK,QAAA,EAAU;AAC9C,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,cAAA,GAAiB,IAAA,CAAK,OAAA,CAAQ,WAAA,GAAc,KAAK,UAAA,CAAW,IAAA;AAClE,IAAA,IAAI,kBAAkB,CAAA,EAAG;AACvB,MAAA,IAAA,CAAK,YAAA,EAAa;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI;AAEF,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,MAAA,CAAO,KAAK,KAAA,CAAM;AAAA,QAC1C,SAAA,EAAW,KAAK,OAAA,CAAQ,SAAA;AAAA,QACxB,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,KAAA,EAAO,cAAA;AAAA,QACP,iBAAA,EAAmB,KAAK,OAAA,CAAQ;AAAA,OACjC,CAAA;AAGD,MAAA,KAAA,MAAW,GAAA,IAAO,OAAO,IAAA,EAAM;AAC7B,QAAA,IAAA,CAAK,WAAW,GAAG,CAAA;AAAA,MACrB;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,KAAA,CAAM,eAAe,KAAK,CAAA;AAC/B,MAAA,IAAA,CAAK,IAAA,CAAK,OAAA,EAAS,EAAE,KAAA,EAAuB,CAAA;AAAA,IAC9C;AAGA,IAAA,IAAA,CAAK,YAAA,EAAa;AAAA,EACpB;AAAA,EAEQ,WAAW,GAAA,EAAuB;AACxC,IAAA,IAAA,CAAK,IAAA,CAAK,eAAe,EAAE,KAAA,EAAO,IAAI,EAAA,EAAI,SAAA,EAAW,GAAA,CAAI,SAAA,EAAW,CAAA;AAEpE,IAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,IAAA,MAAM,SAAA,GAAuB;AAAA,MAC3B,GAAA;AAAA,MACA,SAAA,sBAAe,IAAA,EAAK;AAAA,MACpB;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,GAAA,CAAI,EAAA,EAAI,SAAS,CAAA;AAGrC,IAAA,MAAM,oBAAoB,IAAA,CAAK,OAAA,CAAQ,aAAA,GAAgB,IAAA,CAAK,QAAQ,iBAAA,GAAoB,GAAA;AACxF,IAAA,SAAA,CAAU,cAAA,GAAiB,YAAY,MAAM;AAC3C,MAAA,IAAA,CAAK,gBAAA,CAAiB,IAAI,EAAE,CAAA;AAAA,IAC9B,GAAG,iBAAiB,CAAA;AAGpB,IAAA,IAAA,CAAK,eAAe,SAAS,CAAA;AAAA,EAC/B;AAAA,EAEA,MAAc,eAAe,SAAA,EAAqC;AAChE,IAAA,MAAM,EAAE,GAAA,EAAK,eAAA,EAAgB,GAAI,SAAA;AACjC,IAAA,MAAM,EAAE,QAAO,GAAI,eAAA;AAEnB,IAAA,IAAA,CAAK,IAAA,CAAK,eAAe,EAAE,KAAA,EAAO,IAAI,EAAA,EAAI,SAAA,EAAW,GAAA,CAAI,SAAA,EAAW,CAAA;AAEpE,IAAA,MAAM,OAAA,GAAsB;AAAA,MAC1B,OAAO,GAAA,CAAI,EAAA;AAAA,MACX,WAAW,GAAA,CAAI,SAAA;AAAA,MACf,SAAS,GAAA,CAAI,OAAA;AAAA,MACb,YAAY,GAAA,CAAI,UAAA;AAAA,MAChB,YAAY,GAAA,CAAI,UAAA;AAAA,MAChB,MAAA;AAAA,MACA,QAAA,EAAU,OAAO,QAAA,EAAkB,QAAA,KAAsB;AAAA,MAGzD,CAAA;AAAA,MACA,GAAA,EAAK,CAAC,KAAA,EAAO,OAAA,EAAS,IAAA,KAAS;AAC7B,QAAA,IAAA,CAAK,KAAA,CAAM,IAAI,GAAA,CAAI,EAAE,MAAM,KAAK,CAAA,EAAA,EAAK,OAAO,CAAA,CAAA,EAAI,IAAI,CAAA;AAAA,MACtD;AAAA,KACF;AAEA,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,OAAA,CAAS,OAAO,CAAA;AAG1C,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,IAAA,CAAK,KAAA,CAAM,CAAA,IAAA,EAAO,GAAA,CAAI,EAAE,CAAA,YAAA,CAAc,CAAA;AACtC,QAAA;AAAA,MACF;AAGA,MAAA,MAAM,IAAA,CAAK,WAAA,CAAY,GAAA,EAAK,MAAA,IAAU,KAAA,CAAS,CAAA;AAAA,IACjD,SAAS,KAAA,EAAO;AAEd,MAAA,IAAI,OAAO,OAAA,EAAS;AAClB,QAAA,IAAA,CAAK,KAAA,CAAM,CAAA,IAAA,EAAO,GAAA,CAAI,EAAE,CAAA,YAAA,CAAc,CAAA;AACtC,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,eAAe,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU,OAAO,KAAK,CAAA;AAC1E,MAAA,MAAM,IAAA,CAAK,OAAA,CAAQ,GAAA,EAAK,YAAY,CAAA;AAAA,IACtC,CAAA,SAAE;AACA,MAAA,IAAA,CAAK,UAAA,CAAW,IAAI,EAAE,CAAA;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,MAAc,WAAA,CAAY,GAAA,EAAiB,MAAA,EAAoC;AAC7E,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAEpB,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,IAAI,EAAA,EAAI;AAAA,QACtC,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,MAAA;AAAA,QACA,GAAI,GAAA,CAAI,OAAA,KAAY,UAAa,EAAE,OAAA,EAAS,IAAI,OAAA;AAAQ,OACzD,CAAA;AAED,MAAA,IAAA,CAAK,KAAK,eAAA,EAAiB;AAAA,QACzB,OAAO,GAAA,CAAI,EAAA;AAAA,QACX,WAAW,GAAA,CAAI,SAAA;AAAA,QACf;AAAA,OACD,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,KAAA,CAAM,CAAA,uBAAA,EAA0B,GAAA,CAAI,EAAE,IAAI,KAAK,CAAA;AAAA,IACtD;AAAA,EACF;AAAA,EAEA,MAAc,OAAA,CAAQ,GAAA,EAAiB,YAAA,EAAqC;AAC1E,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAEpB,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,UAAA,GAAa,GAAA,CAAI,UAAA;AAEvC,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,IAAI,EAAA,EAAI;AAAA,QAClC,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,KAAA,EAAO,YAAA;AAAA,QACP,GAAI,GAAA,CAAI,OAAA,KAAY,UAAa,EAAE,OAAA,EAAS,IAAI,OAAA;AAAQ,OACzD,CAAA;AAED,MAAA,IAAA,CAAK,KAAK,YAAA,EAAc;AAAA,QACtB,OAAO,GAAA,CAAI,EAAA;AAAA,QACX,WAAW,GAAA,CAAI,SAAA;AAAA,QACf,KAAA,EAAO,YAAA;AAAA,QACP;AAAA,OACD,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,KAAA,CAAM,CAAA,mBAAA,EAAsB,GAAA,CAAI,EAAE,IAAI,KAAK,CAAA;AAAA,IAClD;AAAA,EACF;AAAA,EAEQ,WAAW,KAAA,EAAqB;AACtC,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,KAAK,CAAA;AAC3C,IAAA,IAAI,WAAW,cAAA,EAAgB;AAC7B,MAAA,aAAA,CAAc,UAAU,cAAc,CAAA;AAAA,IACxC;AACA,IAAA,IAAA,CAAK,UAAA,CAAW,OAAO,KAAK,CAAA;AAAA,EAC9B;AAAA,EAEA,MAAc,iBAAiB,KAAA,EAA8B;AAC3D,IAAA,IAAI,CAAC,KAAK,QAAA,EAAU;AAEpB,IAAA,MAAM,UAAU,IAAA,CAAK,UAAA,CAAW,GAAA,CAAI,KAAK,GAAG,GAAA,CAAI,OAAA;AAEhD,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,IAAA,CAAK,SAAA,CAAU,KAAA,EAAO;AAAA,QACtC,UAAU,IAAA,CAAK,QAAA;AAAA,QACf,iBAAA,EAAmB,KAAK,OAAA,CAAQ,aAAA;AAAA,QAChC,GAAI,OAAA,KAAY,KAAA,CAAA,IAAa,EAAE,OAAA;AAAQ,OACxC,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,KAAA,CAAM,CAAA,yBAAA,EAA4B,KAAK,CAAA,CAAA,EAAI,KAAK,CAAA;AAAA,IACvD;AAAA,EACF;AAAA,EAEA,MAAc,mBAAA,GAAqC;AACjD,IAAA,IAAI,CAAC,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,UAAU,SAAA,EAAW;AAEhD,IAAA,IAAI;AACF,MAAA,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,SAAA,CAAU,KAAK,QAAA,EAAU;AAAA,QACjD,WAAA,EAAa,KAAK,UAAA,CAAW,IAAA;AAAA,QAC7B,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH,SAAS,KAAA,EAAO;AACd,MAAA,IAAA,CAAK,KAAA,CAAM,2BAA2B,KAAK,CAAA;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAc,iBAAA,GAAmC;AAC/C,IAAA,OAAO,IAAA,CAAK,UAAA,CAAW,IAAA,GAAO,CAAA,EAAG;AAC/B,MAAA,MAAM,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,IACtB;AAAA,EACF;AAAA,EAEQ,MAAM,EAAA,EAA2B;AACvC,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,YAAY,UAAA,CAAW,OAAA,EAAS,EAAE,CAAC,CAAA;AAAA,EACzD;AACF","file":"index.cjs","sourcesContent":["/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from '../client.js';\nimport type { JsonObject } from '../types/common.js';\nimport type { ClaimedJob } from '../types/jobs.js';\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from './types.js';\nimport { hostname as osHostname } from 'os';\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n private readonly options: Required<SpooledWorkerOptions>;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = 'idle';\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<string, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> = new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: 'nodejs',\n version: '1.0.34',\n metadata: {},\n ...options,\n } as Required<SpooledWorkerOptions>;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called\n setImmediate(() => this.start());\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== 'idle') {\n throw new Error('Cannot set handler after worker has started');\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== 'idle') {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error('No job handler registered. Call process() first.');\n }\n\n this.state = 'starting';\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = 'running';\n this.emit('started', { workerId: this.workerId, queueName: this.options.queueName });\n this.schedulePoll();\n } catch (error) {\n this.state = 'error';\n this.emit('error', { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully\n */\n async stop(): Promise<void> {\n if (this.state !== 'running') {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n this.state = 'stopping';\n this.debug('Stopping worker...');\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Signal all active jobs to stop\n for (const active of this.activeJobs.values()) {\n active.abortController.abort();\n }\n\n // Wait for active jobs to complete (with timeout)\n if (this.activeJobs.size > 0) {\n this.debug(`Waiting for ${this.activeJobs.size} active jobs to complete...`);\n\n await Promise.race([\n this.waitForActiveJobs(),\n this.sleep(this.options.shutdownTimeout),\n ]);\n\n // Force-fail any remaining jobs. Snapshot the entries first because\n // cleanupJob() mutates the activeJobs map as we go.\n const remaining = Array.from(this.activeJobs.entries());\n for (const [jobId, active] of remaining) {\n this.debug(`Force-failing job ${jobId} due to shutdown timeout`);\n await this.failJob(active.job, 'Worker shutdown timeout');\n // Clear the per-job heartbeat interval and drop the job from the map.\n // The handler's finally (which normally does this) may never run if\n // user code ignores the abort signal, so the timer would leak forever.\n this.cleanupJob(jobId);\n }\n }\n\n // Deregister worker\n if (this.workerId) {\n try {\n await this.client.workers.deregister(this.workerId);\n this.debug('Worker deregistered');\n } catch (error) {\n this.debug('Failed to deregister worker', error);\n }\n }\n\n this.state = 'stopped';\n this.workerId = null;\n this.emit('stopped', { workerId: this.workerId ?? '', reason: 'graceful' });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(event: E, data: WorkerEventData[E]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug('Event handler error', { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== 'running') {\n return;\n }\n\n this.pollTimer = setTimeout(() => this.poll(), this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== 'running' || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug('Poll failed', error);\n this.emit('error', { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit('job:claimed', { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n job,\n startedAt: new Date(),\n abortController,\n };\n\n this.activeJobs.set(job.id, activeJob);\n\n // Start per-job heartbeat\n const heartbeatInterval = this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(job.id);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit('job:started', { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (_percent: number, _message?: string) => {\n // Progress tracking could be implemented via heartbeat metadata\n // For now, this is a no-op\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const result = await this.handler!(context);\n\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n // Complete the job\n await this.completeJob(job, result ?? undefined);\n } catch (error) {\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.failJob(job, errorMessage);\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n private async completeJob(job: ClaimedJob, result?: JsonObject): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId !== undefined && { leaseId: job.leaseId }),\n });\n\n this.emit('job:completed', {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n this.debug(`Failed to complete job ${job.id}`, error);\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: errorMessage,\n ...(job.leaseId !== undefined && { leaseId: job.leaseId }),\n });\n\n this.emit('job:failed', {\n jobId: job.id,\n queueName: job.queueName,\n error: errorMessage,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n }\n }\n\n private cleanupJob(jobId: string): void {\n const activeJob = this.activeJobs.get(jobId);\n if (activeJob?.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n }\n this.activeJobs.delete(jobId);\n }\n\n private async sendJobHeartbeat(jobId: string): Promise<void> {\n if (!this.workerId) return;\n\n const leaseId = this.activeJobs.get(jobId)?.job.leaseId;\n\n try {\n await this.client.jobs.heartbeat(jobId, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(leaseId !== undefined && { leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${jobId}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n if (!this.workerId || this.state !== 'running') return;\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: 'healthy',\n });\n } catch (error) {\n this.debug('Worker heartbeat failed', error);\n }\n }\n\n private async waitForActiveJobs(): Promise<void> {\n while (this.activeJobs.size > 0) {\n await this.sleep(100);\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * SDK Configuration\n *\n * Defines configuration types and defaults for the Spooled SDK.\n */\n\n/** Retry configuration options */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Base delay in milliseconds for exponential backoff (default: 1000) */\n baseDelay: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay: number;\n /** Exponential backoff factor (default: 2) */\n factor: number;\n /** Whether to add jitter to delays (default: true) */\n jitter: boolean;\n /** Custom function to determine if an error is retryable */\n retryOn?: (error: Error, attempt: number) => boolean;\n}\n\n/** Circuit breaker configuration options */\nexport interface CircuitBreakerConfig {\n /** Whether circuit breaker is enabled (default: true) */\n enabled: boolean;\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold: number;\n /** Number of successes in half-open state to close circuit (default: 3) */\n successThreshold: number;\n /** Time in milliseconds to wait before trying again after opening (default: 30000) */\n timeout: number;\n}\n\n/** Debug logging function type */\nexport type DebugFn = (message: string, meta?: unknown) => void;\n\n/** Main SDK configuration options */\nexport interface SpooledClientConfig {\n /** API key for authentication (starts with sp_live_, sp_test_, sk_live_, or sk_test_) */\n apiKey?: string;\n /** JWT access token (alternative to API key) */\n accessToken?: string;\n /** JWT refresh token for automatic token renewal */\n refreshToken?: string;\n /** Admin API key (for /api/v1/admin/* endpoints; uses X-Admin-Key header) */\n adminKey?: string;\n /** Base URL for the REST API (default: https://api.spooled.cloud) */\n baseUrl?: string;\n /** WebSocket URL for realtime events (default: derived from baseUrl, e.g., wss://api.spooled.cloud) */\n wsUrl?: string;\n /** gRPC server address (default: grpc.spooled.cloud:443) */\n grpcAddress?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (shorthand for retry.maxRetries) */\n retries?: number;\n /** Base retry delay in milliseconds (shorthand for retry.baseDelay) */\n retryDelay?: number;\n /** Full retry configuration */\n retry?: Partial<RetryConfig>;\n /** Circuit breaker configuration */\n circuitBreaker?: Partial<CircuitBreakerConfig>;\n /** Additional headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or special runtimes) */\n fetch?: typeof fetch;\n /** Custom user agent string */\n userAgent?: string;\n /** Enable debug logging */\n debug?: boolean | DebugFn;\n /** Whether to automatically refresh tokens when they expire (default: true) */\n autoRefreshToken?: boolean;\n}\n\n/** Resolved configuration with all defaults applied */\nexport interface ResolvedConfig {\n apiKey?: string;\n accessToken?: string;\n refreshToken?: string;\n adminKey?: string;\n baseUrl: string;\n wsUrl: string;\n grpcAddress: string;\n timeout: number;\n retry: RetryConfig;\n circuitBreaker: CircuitBreakerConfig;\n headers: Record<string, string>;\n fetch: typeof fetch;\n userAgent: string;\n debug: DebugFn | null;\n autoRefreshToken: boolean;\n}\n\n/** Default configuration values */\nexport const DEFAULT_CONFIG = {\n baseUrl: 'https://api.spooled.cloud',\n wsUrl: 'wss://api.spooled.cloud',\n grpcAddress: 'grpc.spooled.cloud:443',\n timeout: 30000,\n retry: {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 30000,\n factor: 2,\n jitter: true,\n } satisfies RetryConfig,\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n successThreshold: 3,\n timeout: 30000,\n } satisfies CircuitBreakerConfig,\n userAgent: '@spooled/sdk-nodejs/1.0.35',\n autoRefreshToken: true,\n} as const;\n\n/** SDK version */\nexport const SDK_VERSION = '1.0.35';\n\n/** API version prefix */\nexport const API_VERSION = 'v1';\n\n/** API base path */\nexport const API_BASE_PATH = `/api/${API_VERSION}`;\n\n/**\n * Trim surrounding whitespace from a credential string and treat an\n * all-whitespace input as unset. Mirrors the trim policy in the Go, PHP,\n * and Python SDKs so behavior is consistent across languages.\n */\nfunction normalizeCredential(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed;\n}\n\n/**\n * Resolve configuration by merging user options with defaults\n */\nexport function resolveConfig(options: SpooledClientConfig): ResolvedConfig {\n // Create debug function\n let debugFn: DebugFn | null = null;\n if (options.debug === true) {\n debugFn = (message: string, meta?: unknown) => {\n // eslint-disable-next-line no-console\n console.log(`[spooled-sdk] ${message}`, meta !== undefined ? meta : '');\n };\n } else if (typeof options.debug === 'function') {\n debugFn = options.debug;\n }\n\n // Merge retry config\n const retry: RetryConfig = {\n ...DEFAULT_CONFIG.retry,\n ...options.retry,\n // Allow shorthand overrides\n ...(options.retries !== undefined && { maxRetries: options.retries }),\n ...(options.retryDelay !== undefined && { baseDelay: options.retryDelay }),\n };\n\n // Merge circuit breaker config\n const circuitBreaker: CircuitBreakerConfig = {\n ...DEFAULT_CONFIG.circuitBreaker,\n ...options.circuitBreaker,\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...options.headers,\n };\n\n // Resolve base URL first\n const baseUrl = options.baseUrl ?? DEFAULT_CONFIG.baseUrl;\n \n // Derive WebSocket URL from base URL if not explicitly set\n // Replace http(s):// with ws(s)://\n const derivedWsUrl = baseUrl.replace(/^http/, 'ws');\n const wsUrl = options.wsUrl ?? derivedWsUrl;\n\n // Trim whitespace (including a stray trailing '\\n') from every credential.\n // Node's `undici` fetch rejects `Authorization: Bearer <key>\\n` with an\n // opaque `TypeError: HeaderValue` and users routinely paste keys from\n // `.env` files that include a newline; normalizing here keeps behavior\n // consistent with the Go, PHP, and Python SDKs.\n return {\n apiKey: normalizeCredential(options.apiKey),\n accessToken: normalizeCredential(options.accessToken),\n refreshToken: normalizeCredential(options.refreshToken),\n adminKey: normalizeCredential(options.adminKey),\n baseUrl,\n wsUrl,\n grpcAddress: options.grpcAddress ?? DEFAULT_CONFIG.grpcAddress,\n timeout: options.timeout ?? DEFAULT_CONFIG.timeout,\n retry,\n circuitBreaker,\n headers,\n fetch: options.fetch ?? globalThis.fetch,\n userAgent: options.userAgent ?? DEFAULT_CONFIG.userAgent,\n debug: debugFn,\n autoRefreshToken: options.autoRefreshToken ?? DEFAULT_CONFIG.autoRefreshToken,\n };\n}\n\n/**\n * Validate configuration options\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: ResolvedConfig): void {\n // Must have at least one auth method\n if (!config.apiKey && !config.accessToken) {\n throw new Error(\n 'SpooledClient requires either apiKey or accessToken for authentication'\n );\n }\n\n // Validate API key format if provided\n // Accept both sp_ (new standard) and sk_ (legacy) prefixes\n if (config.apiKey && !config.apiKey.startsWith('sp_') && !config.apiKey.startsWith('sk_')) {\n throw new Error(\n 'Invalid API key format. API keys should start with sp_live_, sp_test_, sk_live_, or sk_test_'\n );\n }\n\n // Validate baseUrl\n if (!config.baseUrl.startsWith('http://') && !config.baseUrl.startsWith('https://')) {\n throw new Error('baseUrl must start with http:// or https://');\n }\n\n // Validate timeout\n if (config.timeout <= 0) {\n throw new Error('timeout must be a positive number');\n }\n\n // Validate retry config\n if (config.retry.maxRetries < 0) {\n throw new Error('retry.maxRetries must be non-negative');\n }\n if (config.retry.baseDelay <= 0) {\n throw new Error('retry.baseDelay must be positive');\n }\n if (config.retry.maxDelay < config.retry.baseDelay) {\n throw new Error('retry.maxDelay must be >= retry.baseDelay');\n }\n if (config.retry.factor < 1) {\n throw new Error('retry.factor must be >= 1');\n }\n\n // Validate circuit breaker config\n if (config.circuitBreaker.enabled) {\n if (config.circuitBreaker.failureThreshold <= 0) {\n throw new Error('circuitBreaker.failureThreshold must be positive');\n }\n if (config.circuitBreaker.successThreshold <= 0) {\n throw new Error('circuitBreaker.successThreshold must be positive');\n }\n if (config.circuitBreaker.timeout <= 0) {\n throw new Error('circuitBreaker.timeout must be positive');\n }\n }\n}\n","/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from '../client.js';\nimport { SDK_VERSION } from '../config.js';\nimport type { JsonObject } from '../types/common.js';\nimport type { ClaimedJob } from '../types/jobs.js';\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from './types.js';\nimport { hostname as osHostname } from 'os';\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n private readonly options: Required<SpooledWorkerOptions>;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = 'idle';\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<symbol, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> = new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: 'nodejs',\n version: SDK_VERSION,\n metadata: {},\n ...options,\n } as Required<SpooledWorkerOptions>;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called\n setImmediate(() => this.start());\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== 'idle') {\n throw new Error('Cannot set handler after worker has started');\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== 'idle') {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error('No job handler registered. Call process() first.');\n }\n\n this.state = 'starting';\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = 'running';\n this.emit('started', { workerId: this.workerId, queueName: this.options.queueName });\n this.schedulePoll();\n } catch (error) {\n this.state = 'error';\n this.emit('error', { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully\n */\n async stop(): Promise<void> {\n if (this.state !== 'running') {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n this.state = 'stopping';\n this.debug('Stopping worker...');\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Signal all active jobs to stop\n for (const active of this.activeJobs.values()) {\n active.abortController.abort();\n }\n\n // Wait for active jobs to complete (with timeout)\n if (this.activeJobs.size > 0) {\n this.debug(`Waiting for ${this.activeJobs.size} active jobs to complete...`);\n\n await Promise.race([\n this.waitForActiveJobs(),\n this.sleep(this.options.shutdownTimeout),\n ]);\n\n // Force-fail any remaining jobs. Snapshot the entries first because\n // cleanupJob() mutates the activeJobs map as we go.\n const remaining = Array.from(this.activeJobs.values());\n for (const active of remaining) {\n this.debug(`Force-failing job ${active.job.id} due to shutdown timeout`);\n await this.failJob(active.job, 'Worker shutdown timeout');\n // The handler's finally may never run if user code ignores the abort\n // signal, so explicitly clear this exact execution's timer.\n this.cleanupJob(active);\n }\n }\n\n // Deregister worker\n if (this.workerId) {\n try {\n await this.client.workers.deregister(this.workerId);\n this.debug('Worker deregistered');\n } catch (error) {\n this.debug('Failed to deregister worker', error);\n }\n }\n\n this.state = 'stopped';\n this.workerId = null;\n this.emit('stopped', { workerId: this.workerId ?? '', reason: 'graceful' });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(event: E, data: WorkerEventData[E]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug('Event handler error', { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== 'running') {\n return;\n }\n\n this.pollTimer = setTimeout(() => this.poll(), this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== 'running' || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug('Poll failed', error);\n this.emit('error', { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit('job:claimed', { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n executionId: Symbol(job.id),\n job,\n startedAt: new Date(),\n abortController,\n };\n\n this.activeJobs.set(activeJob.executionId, activeJob);\n\n // Capture the immutable execution so a replacement lease cannot be borrowed.\n const heartbeatInterval = this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(activeJob);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit('job:started', { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (_percent: number, _message?: string) => {\n // Progress tracking could be implemented via heartbeat metadata\n // For now, this is a no-op\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const result = await this.handler!(context);\n\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n // Complete the job\n await this.completeJob(job, result ?? undefined);\n } catch (error) {\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.failJob(job, errorMessage);\n } finally {\n this.cleanupJob(activeJob);\n }\n }\n\n private async completeJob(job: ClaimedJob, result?: JsonObject): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit('job:completed', {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n this.debug(`Failed to complete job ${job.id}`, error);\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: errorMessage,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit('job:failed', {\n jobId: job.id,\n queueName: job.queueName,\n error: errorMessage,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n }\n }\n\n private cleanupJob(activeJob: ActiveJob): void {\n if (activeJob.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n activeJob.heartbeatTimer = undefined;\n }\n\n if (this.activeJobs.get(activeJob.executionId) === activeJob) {\n this.activeJobs.delete(activeJob.executionId);\n }\n }\n\n private async sendJobHeartbeat(activeJob: ActiveJob): Promise<void> {\n if (!this.workerId) return;\n\n const { job } = activeJob;\n\n try {\n await this.client.jobs.heartbeat(job.id, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${job.id}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n if (!this.workerId || this.state !== 'running') return;\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: 'healthy',\n });\n } catch (error) {\n this.debug('Worker heartbeat failed', error);\n }\n }\n\n private async waitForActiveJobs(): Promise<void> {\n while (this.activeJobs.size > 0) {\n await this.sleep(100);\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
package/dist/worker/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { cV as ActiveJob, b1 as JobContext, b4 as JobHandler, b6 as JobResult, c9 as SpooledWorker, ca as SpooledWorkerOptions, cv as WorkerEvent, cw as WorkerEventData, cz as WorkerState } from '../index-
|
|
1
|
+
export { cV as ActiveJob, b1 as JobContext, b4 as JobHandler, b6 as JobResult, c9 as SpooledWorker, ca as SpooledWorkerOptions, cv as WorkerEvent, cw as WorkerEventData, cz as WorkerState } from '../index-B96n0WUe.cjs';
|
|
2
2
|
import '@grpc/grpc-js';
|
package/dist/worker/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { cV as ActiveJob, b1 as JobContext, b4 as JobHandler, b6 as JobResult, c9 as SpooledWorker, ca as SpooledWorkerOptions, cv as WorkerEvent, cw as WorkerEventData, cz as WorkerState } from '../index-
|
|
1
|
+
export { cV as ActiveJob, b1 as JobContext, b4 as JobHandler, b6 as JobResult, c9 as SpooledWorker, ca as SpooledWorkerOptions, cv as WorkerEvent, cw as WorkerEventData, cz as WorkerState } from '../index-B96n0WUe.js';
|
|
2
2
|
import '@grpc/grpc-js';
|
package/dist/worker/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { hostname } from 'os';
|
|
2
2
|
|
|
3
|
-
// src/
|
|
3
|
+
// src/config.ts
|
|
4
|
+
var SDK_VERSION = "1.0.35";
|
|
4
5
|
var DEFAULT_OPTIONS = {
|
|
5
6
|
concurrency: 5,
|
|
6
7
|
pollInterval: 1e3,
|
|
@@ -28,7 +29,7 @@ var SpooledWorker = class {
|
|
|
28
29
|
...DEFAULT_OPTIONS,
|
|
29
30
|
hostname: hostname(),
|
|
30
31
|
workerType: "nodejs",
|
|
31
|
-
version:
|
|
32
|
+
version: SDK_VERSION,
|
|
32
33
|
metadata: {},
|
|
33
34
|
...options
|
|
34
35
|
};
|
|
@@ -135,11 +136,11 @@ var SpooledWorker = class {
|
|
|
135
136
|
this.waitForActiveJobs(),
|
|
136
137
|
this.sleep(this.options.shutdownTimeout)
|
|
137
138
|
]);
|
|
138
|
-
const remaining = Array.from(this.activeJobs.
|
|
139
|
-
for (const
|
|
140
|
-
this.debug(`Force-failing job ${
|
|
139
|
+
const remaining = Array.from(this.activeJobs.values());
|
|
140
|
+
for (const active of remaining) {
|
|
141
|
+
this.debug(`Force-failing job ${active.job.id} due to shutdown timeout`);
|
|
141
142
|
await this.failJob(active.job, "Worker shutdown timeout");
|
|
142
|
-
this.cleanupJob(
|
|
143
|
+
this.cleanupJob(active);
|
|
143
144
|
}
|
|
144
145
|
}
|
|
145
146
|
if (this.workerId) {
|
|
@@ -222,14 +223,15 @@ var SpooledWorker = class {
|
|
|
222
223
|
this.emit("job:claimed", { jobId: job.id, queueName: job.queueName });
|
|
223
224
|
const abortController = new AbortController();
|
|
224
225
|
const activeJob = {
|
|
226
|
+
executionId: Symbol(job.id),
|
|
225
227
|
job,
|
|
226
228
|
startedAt: /* @__PURE__ */ new Date(),
|
|
227
229
|
abortController
|
|
228
230
|
};
|
|
229
|
-
this.activeJobs.set(
|
|
231
|
+
this.activeJobs.set(activeJob.executionId, activeJob);
|
|
230
232
|
const heartbeatInterval = this.options.leaseDuration * this.options.heartbeatFraction * 1e3;
|
|
231
233
|
activeJob.heartbeatTimer = setInterval(() => {
|
|
232
|
-
this.sendJobHeartbeat(
|
|
234
|
+
this.sendJobHeartbeat(activeJob);
|
|
233
235
|
}, heartbeatInterval);
|
|
234
236
|
this.executeHandler(activeJob);
|
|
235
237
|
}
|
|
@@ -265,7 +267,7 @@ var SpooledWorker = class {
|
|
|
265
267
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
266
268
|
await this.failJob(job, errorMessage);
|
|
267
269
|
} finally {
|
|
268
|
-
this.cleanupJob(
|
|
270
|
+
this.cleanupJob(activeJob);
|
|
269
271
|
}
|
|
270
272
|
}
|
|
271
273
|
async completeJob(job, result) {
|
|
@@ -274,7 +276,7 @@ var SpooledWorker = class {
|
|
|
274
276
|
await this.client.jobs.complete(job.id, {
|
|
275
277
|
workerId: this.workerId,
|
|
276
278
|
result,
|
|
277
|
-
...job.leaseId
|
|
279
|
+
...job.leaseId != null && { leaseId: job.leaseId }
|
|
278
280
|
});
|
|
279
281
|
this.emit("job:completed", {
|
|
280
282
|
jobId: job.id,
|
|
@@ -292,7 +294,7 @@ var SpooledWorker = class {
|
|
|
292
294
|
await this.client.jobs.fail(job.id, {
|
|
293
295
|
workerId: this.workerId,
|
|
294
296
|
error: errorMessage,
|
|
295
|
-
...job.leaseId
|
|
297
|
+
...job.leaseId != null && { leaseId: job.leaseId }
|
|
296
298
|
});
|
|
297
299
|
this.emit("job:failed", {
|
|
298
300
|
jobId: job.id,
|
|
@@ -304,24 +306,26 @@ var SpooledWorker = class {
|
|
|
304
306
|
this.debug(`Failed to fail job ${job.id}`, error);
|
|
305
307
|
}
|
|
306
308
|
}
|
|
307
|
-
cleanupJob(
|
|
308
|
-
|
|
309
|
-
if (activeJob?.heartbeatTimer) {
|
|
309
|
+
cleanupJob(activeJob) {
|
|
310
|
+
if (activeJob.heartbeatTimer) {
|
|
310
311
|
clearInterval(activeJob.heartbeatTimer);
|
|
312
|
+
activeJob.heartbeatTimer = void 0;
|
|
313
|
+
}
|
|
314
|
+
if (this.activeJobs.get(activeJob.executionId) === activeJob) {
|
|
315
|
+
this.activeJobs.delete(activeJob.executionId);
|
|
311
316
|
}
|
|
312
|
-
this.activeJobs.delete(jobId);
|
|
313
317
|
}
|
|
314
|
-
async sendJobHeartbeat(
|
|
318
|
+
async sendJobHeartbeat(activeJob) {
|
|
315
319
|
if (!this.workerId) return;
|
|
316
|
-
const
|
|
320
|
+
const { job } = activeJob;
|
|
317
321
|
try {
|
|
318
|
-
await this.client.jobs.heartbeat(
|
|
322
|
+
await this.client.jobs.heartbeat(job.id, {
|
|
319
323
|
workerId: this.workerId,
|
|
320
324
|
leaseDurationSecs: this.options.leaseDuration,
|
|
321
|
-
...leaseId
|
|
325
|
+
...job.leaseId != null && { leaseId: job.leaseId }
|
|
322
326
|
});
|
|
323
327
|
} catch (error) {
|
|
324
|
-
this.debug(`Job heartbeat failed for ${
|
|
328
|
+
this.debug(`Job heartbeat failed for ${job.id}`, error);
|
|
325
329
|
}
|
|
326
330
|
}
|
|
327
331
|
async sendWorkerHeartbeat() {
|
package/dist/worker/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from '../client.js';\nimport type { JsonObject } from '../types/common.js';\nimport type { ClaimedJob } from '../types/jobs.js';\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from './types.js';\nimport { hostname as osHostname } from 'os';\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n private readonly options: Required<SpooledWorkerOptions>;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = 'idle';\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<string, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> = new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: 'nodejs',\n version: '1.0.34',\n metadata: {},\n ...options,\n } as Required<SpooledWorkerOptions>;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called\n setImmediate(() => this.start());\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== 'idle') {\n throw new Error('Cannot set handler after worker has started');\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== 'idle') {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error('No job handler registered. Call process() first.');\n }\n\n this.state = 'starting';\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = 'running';\n this.emit('started', { workerId: this.workerId, queueName: this.options.queueName });\n this.schedulePoll();\n } catch (error) {\n this.state = 'error';\n this.emit('error', { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully\n */\n async stop(): Promise<void> {\n if (this.state !== 'running') {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n this.state = 'stopping';\n this.debug('Stopping worker...');\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Signal all active jobs to stop\n for (const active of this.activeJobs.values()) {\n active.abortController.abort();\n }\n\n // Wait for active jobs to complete (with timeout)\n if (this.activeJobs.size > 0) {\n this.debug(`Waiting for ${this.activeJobs.size} active jobs to complete...`);\n\n await Promise.race([\n this.waitForActiveJobs(),\n this.sleep(this.options.shutdownTimeout),\n ]);\n\n // Force-fail any remaining jobs. Snapshot the entries first because\n // cleanupJob() mutates the activeJobs map as we go.\n const remaining = Array.from(this.activeJobs.entries());\n for (const [jobId, active] of remaining) {\n this.debug(`Force-failing job ${jobId} due to shutdown timeout`);\n await this.failJob(active.job, 'Worker shutdown timeout');\n // Clear the per-job heartbeat interval and drop the job from the map.\n // The handler's finally (which normally does this) may never run if\n // user code ignores the abort signal, so the timer would leak forever.\n this.cleanupJob(jobId);\n }\n }\n\n // Deregister worker\n if (this.workerId) {\n try {\n await this.client.workers.deregister(this.workerId);\n this.debug('Worker deregistered');\n } catch (error) {\n this.debug('Failed to deregister worker', error);\n }\n }\n\n this.state = 'stopped';\n this.workerId = null;\n this.emit('stopped', { workerId: this.workerId ?? '', reason: 'graceful' });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(event: E, data: WorkerEventData[E]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug('Event handler error', { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== 'running') {\n return;\n }\n\n this.pollTimer = setTimeout(() => this.poll(), this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== 'running' || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug('Poll failed', error);\n this.emit('error', { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit('job:claimed', { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n job,\n startedAt: new Date(),\n abortController,\n };\n\n this.activeJobs.set(job.id, activeJob);\n\n // Start per-job heartbeat\n const heartbeatInterval = this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(job.id);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit('job:started', { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (_percent: number, _message?: string) => {\n // Progress tracking could be implemented via heartbeat metadata\n // For now, this is a no-op\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const result = await this.handler!(context);\n\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n // Complete the job\n await this.completeJob(job, result ?? undefined);\n } catch (error) {\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.failJob(job, errorMessage);\n } finally {\n this.cleanupJob(job.id);\n }\n }\n\n private async completeJob(job: ClaimedJob, result?: JsonObject): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId !== undefined && { leaseId: job.leaseId }),\n });\n\n this.emit('job:completed', {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n this.debug(`Failed to complete job ${job.id}`, error);\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: errorMessage,\n ...(job.leaseId !== undefined && { leaseId: job.leaseId }),\n });\n\n this.emit('job:failed', {\n jobId: job.id,\n queueName: job.queueName,\n error: errorMessage,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n }\n }\n\n private cleanupJob(jobId: string): void {\n const activeJob = this.activeJobs.get(jobId);\n if (activeJob?.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n }\n this.activeJobs.delete(jobId);\n }\n\n private async sendJobHeartbeat(jobId: string): Promise<void> {\n if (!this.workerId) return;\n\n const leaseId = this.activeJobs.get(jobId)?.job.leaseId;\n\n try {\n await this.client.jobs.heartbeat(jobId, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(leaseId !== undefined && { leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${jobId}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n if (!this.workerId || this.state !== 'running') return;\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: 'healthy',\n });\n } catch (error) {\n this.debug('Worker heartbeat failed', error);\n }\n }\n\n private async waitForActiveJobs(): Promise<void> {\n while (this.activeJobs.size > 0) {\n await this.sleep(100);\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/config.ts","../../src/worker/worker.ts"],"names":["osHostname"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * SDK Configuration\n *\n * Defines configuration types and defaults for the Spooled SDK.\n */\n\n/** Retry configuration options */\nexport interface RetryConfig {\n /** Maximum number of retry attempts (default: 3) */\n maxRetries: number;\n /** Base delay in milliseconds for exponential backoff (default: 1000) */\n baseDelay: number;\n /** Maximum delay in milliseconds (default: 30000) */\n maxDelay: number;\n /** Exponential backoff factor (default: 2) */\n factor: number;\n /** Whether to add jitter to delays (default: true) */\n jitter: boolean;\n /** Custom function to determine if an error is retryable */\n retryOn?: (error: Error, attempt: number) => boolean;\n}\n\n/** Circuit breaker configuration options */\nexport interface CircuitBreakerConfig {\n /** Whether circuit breaker is enabled (default: true) */\n enabled: boolean;\n /** Number of failures before opening the circuit (default: 5) */\n failureThreshold: number;\n /** Number of successes in half-open state to close circuit (default: 3) */\n successThreshold: number;\n /** Time in milliseconds to wait before trying again after opening (default: 30000) */\n timeout: number;\n}\n\n/** Debug logging function type */\nexport type DebugFn = (message: string, meta?: unknown) => void;\n\n/** Main SDK configuration options */\nexport interface SpooledClientConfig {\n /** API key for authentication (starts with sp_live_, sp_test_, sk_live_, or sk_test_) */\n apiKey?: string;\n /** JWT access token (alternative to API key) */\n accessToken?: string;\n /** JWT refresh token for automatic token renewal */\n refreshToken?: string;\n /** Admin API key (for /api/v1/admin/* endpoints; uses X-Admin-Key header) */\n adminKey?: string;\n /** Base URL for the REST API (default: https://api.spooled.cloud) */\n baseUrl?: string;\n /** WebSocket URL for realtime events (default: derived from baseUrl, e.g., wss://api.spooled.cloud) */\n wsUrl?: string;\n /** gRPC server address (default: grpc.spooled.cloud:443) */\n grpcAddress?: string;\n /** Request timeout in milliseconds (default: 30000) */\n timeout?: number;\n /** Number of retry attempts (shorthand for retry.maxRetries) */\n retries?: number;\n /** Base retry delay in milliseconds (shorthand for retry.baseDelay) */\n retryDelay?: number;\n /** Full retry configuration */\n retry?: Partial<RetryConfig>;\n /** Circuit breaker configuration */\n circuitBreaker?: Partial<CircuitBreakerConfig>;\n /** Additional headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or special runtimes) */\n fetch?: typeof fetch;\n /** Custom user agent string */\n userAgent?: string;\n /** Enable debug logging */\n debug?: boolean | DebugFn;\n /** Whether to automatically refresh tokens when they expire (default: true) */\n autoRefreshToken?: boolean;\n}\n\n/** Resolved configuration with all defaults applied */\nexport interface ResolvedConfig {\n apiKey?: string;\n accessToken?: string;\n refreshToken?: string;\n adminKey?: string;\n baseUrl: string;\n wsUrl: string;\n grpcAddress: string;\n timeout: number;\n retry: RetryConfig;\n circuitBreaker: CircuitBreakerConfig;\n headers: Record<string, string>;\n fetch: typeof fetch;\n userAgent: string;\n debug: DebugFn | null;\n autoRefreshToken: boolean;\n}\n\n/** Default configuration values */\nexport const DEFAULT_CONFIG = {\n baseUrl: 'https://api.spooled.cloud',\n wsUrl: 'wss://api.spooled.cloud',\n grpcAddress: 'grpc.spooled.cloud:443',\n timeout: 30000,\n retry: {\n maxRetries: 3,\n baseDelay: 1000,\n maxDelay: 30000,\n factor: 2,\n jitter: true,\n } satisfies RetryConfig,\n circuitBreaker: {\n enabled: true,\n failureThreshold: 5,\n successThreshold: 3,\n timeout: 30000,\n } satisfies CircuitBreakerConfig,\n userAgent: '@spooled/sdk-nodejs/1.0.35',\n autoRefreshToken: true,\n} as const;\n\n/** SDK version */\nexport const SDK_VERSION = '1.0.35';\n\n/** API version prefix */\nexport const API_VERSION = 'v1';\n\n/** API base path */\nexport const API_BASE_PATH = `/api/${API_VERSION}`;\n\n/**\n * Trim surrounding whitespace from a credential string and treat an\n * all-whitespace input as unset. Mirrors the trim policy in the Go, PHP,\n * and Python SDKs so behavior is consistent across languages.\n */\nfunction normalizeCredential(value: string | undefined): string | undefined {\n if (value === undefined) {\n return undefined;\n }\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed;\n}\n\n/**\n * Resolve configuration by merging user options with defaults\n */\nexport function resolveConfig(options: SpooledClientConfig): ResolvedConfig {\n // Create debug function\n let debugFn: DebugFn | null = null;\n if (options.debug === true) {\n debugFn = (message: string, meta?: unknown) => {\n // eslint-disable-next-line no-console\n console.log(`[spooled-sdk] ${message}`, meta !== undefined ? meta : '');\n };\n } else if (typeof options.debug === 'function') {\n debugFn = options.debug;\n }\n\n // Merge retry config\n const retry: RetryConfig = {\n ...DEFAULT_CONFIG.retry,\n ...options.retry,\n // Allow shorthand overrides\n ...(options.retries !== undefined && { maxRetries: options.retries }),\n ...(options.retryDelay !== undefined && { baseDelay: options.retryDelay }),\n };\n\n // Merge circuit breaker config\n const circuitBreaker: CircuitBreakerConfig = {\n ...DEFAULT_CONFIG.circuitBreaker,\n ...options.circuitBreaker,\n };\n\n // Build headers\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n ...options.headers,\n };\n\n // Resolve base URL first\n const baseUrl = options.baseUrl ?? DEFAULT_CONFIG.baseUrl;\n \n // Derive WebSocket URL from base URL if not explicitly set\n // Replace http(s):// with ws(s)://\n const derivedWsUrl = baseUrl.replace(/^http/, 'ws');\n const wsUrl = options.wsUrl ?? derivedWsUrl;\n\n // Trim whitespace (including a stray trailing '\\n') from every credential.\n // Node's `undici` fetch rejects `Authorization: Bearer <key>\\n` with an\n // opaque `TypeError: HeaderValue` and users routinely paste keys from\n // `.env` files that include a newline; normalizing here keeps behavior\n // consistent with the Go, PHP, and Python SDKs.\n return {\n apiKey: normalizeCredential(options.apiKey),\n accessToken: normalizeCredential(options.accessToken),\n refreshToken: normalizeCredential(options.refreshToken),\n adminKey: normalizeCredential(options.adminKey),\n baseUrl,\n wsUrl,\n grpcAddress: options.grpcAddress ?? DEFAULT_CONFIG.grpcAddress,\n timeout: options.timeout ?? DEFAULT_CONFIG.timeout,\n retry,\n circuitBreaker,\n headers,\n fetch: options.fetch ?? globalThis.fetch,\n userAgent: options.userAgent ?? DEFAULT_CONFIG.userAgent,\n debug: debugFn,\n autoRefreshToken: options.autoRefreshToken ?? DEFAULT_CONFIG.autoRefreshToken,\n };\n}\n\n/**\n * Validate configuration options\n * @throws Error if configuration is invalid\n */\nexport function validateConfig(config: ResolvedConfig): void {\n // Must have at least one auth method\n if (!config.apiKey && !config.accessToken) {\n throw new Error(\n 'SpooledClient requires either apiKey or accessToken for authentication'\n );\n }\n\n // Validate API key format if provided\n // Accept both sp_ (new standard) and sk_ (legacy) prefixes\n if (config.apiKey && !config.apiKey.startsWith('sp_') && !config.apiKey.startsWith('sk_')) {\n throw new Error(\n 'Invalid API key format. API keys should start with sp_live_, sp_test_, sk_live_, or sk_test_'\n );\n }\n\n // Validate baseUrl\n if (!config.baseUrl.startsWith('http://') && !config.baseUrl.startsWith('https://')) {\n throw new Error('baseUrl must start with http:// or https://');\n }\n\n // Validate timeout\n if (config.timeout <= 0) {\n throw new Error('timeout must be a positive number');\n }\n\n // Validate retry config\n if (config.retry.maxRetries < 0) {\n throw new Error('retry.maxRetries must be non-negative');\n }\n if (config.retry.baseDelay <= 0) {\n throw new Error('retry.baseDelay must be positive');\n }\n if (config.retry.maxDelay < config.retry.baseDelay) {\n throw new Error('retry.maxDelay must be >= retry.baseDelay');\n }\n if (config.retry.factor < 1) {\n throw new Error('retry.factor must be >= 1');\n }\n\n // Validate circuit breaker config\n if (config.circuitBreaker.enabled) {\n if (config.circuitBreaker.failureThreshold <= 0) {\n throw new Error('circuitBreaker.failureThreshold must be positive');\n }\n if (config.circuitBreaker.successThreshold <= 0) {\n throw new Error('circuitBreaker.successThreshold must be positive');\n }\n if (config.circuitBreaker.timeout <= 0) {\n throw new Error('circuitBreaker.timeout must be positive');\n }\n }\n}\n","/**\n * Spooled Worker\n *\n * Worker runtime for processing jobs from a queue.\n */\n\nimport type { SpooledClient } from '../client.js';\nimport { SDK_VERSION } from '../config.js';\nimport type { JsonObject } from '../types/common.js';\nimport type { ClaimedJob } from '../types/jobs.js';\nimport type {\n SpooledWorkerOptions,\n WorkerState,\n JobHandler,\n JobContext,\n WorkerEvent,\n WorkerEventData,\n ActiveJob,\n} from './types.js';\nimport { hostname as osHostname } from 'os';\n\n/** Default options */\nconst DEFAULT_OPTIONS = {\n concurrency: 5,\n pollInterval: 1000,\n leaseDuration: 30,\n heartbeatFraction: 0.5,\n autoStart: false,\n shutdownTimeout: 30000,\n} as const;\n\n/** Event handler type */\ntype EventHandler<E extends WorkerEvent> = (data: WorkerEventData[E]) => void;\n\n/**\n * Spooled Worker\n *\n * @example\n * ```typescript\n * const worker = new SpooledWorker(client, {\n * queueName: 'my-queue',\n * concurrency: 10,\n * });\n *\n * worker.process(async (ctx) => {\n * console.log('Processing job:', ctx.jobId);\n * await doSomeWork(ctx.payload);\n * return { success: true };\n * });\n *\n * await worker.start();\n *\n * // Graceful shutdown\n * process.on('SIGTERM', () => worker.stop());\n * ```\n */\nexport class SpooledWorker {\n private readonly client: SpooledClient;\n private readonly options: Required<SpooledWorkerOptions>;\n private readonly debug: (msg: string, meta?: unknown) => void;\n\n private state: WorkerState = 'idle';\n private workerId: string | null = null;\n private handler: JobHandler | null = null;\n private activeJobs: Map<symbol, ActiveJob> = new Map();\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private workerHeartbeatTimer: ReturnType<typeof setInterval> | null = null;\n private shutdownPromise: Promise<void> | null = null;\n\n // Event handlers\n private eventHandlers: Map<WorkerEvent, Set<EventHandler<WorkerEvent>>> = new Map();\n\n constructor(client: SpooledClient, options: SpooledWorkerOptions) {\n this.client = client;\n this.options = {\n ...DEFAULT_OPTIONS,\n hostname: osHostname(),\n workerType: 'nodejs',\n version: SDK_VERSION,\n metadata: {},\n ...options,\n } as Required<SpooledWorkerOptions>;\n\n // Get debug from client config\n const config = client.getConfig();\n this.debug = config.debug ?? (() => {});\n\n if (this.options.autoStart) {\n // Defer to next tick to allow process() to be called\n setImmediate(() => this.start());\n }\n }\n\n /**\n * Get current worker state\n */\n getState(): WorkerState {\n return this.state;\n }\n\n /**\n * Get worker ID (available after start)\n */\n getWorkerId(): string | null {\n return this.workerId;\n }\n\n /**\n * Get number of active jobs\n */\n getActiveJobCount(): number {\n return this.activeJobs.size;\n }\n\n /**\n * Register job handler\n */\n process(handler: JobHandler): void {\n if (this.state !== 'idle') {\n throw new Error('Cannot set handler after worker has started');\n }\n this.handler = handler;\n }\n\n /**\n * Start the worker\n */\n async start(): Promise<void> {\n if (this.state !== 'idle') {\n throw new Error(`Cannot start worker in state: ${this.state}`);\n }\n\n if (!this.handler) {\n throw new Error('No job handler registered. Call process() first.');\n }\n\n this.state = 'starting';\n this.debug(`Starting worker for queue: ${this.options.queueName}`);\n\n try {\n // Register with the API\n const registration = await this.client.workers.register({\n queueName: this.options.queueName,\n hostname: this.options.hostname,\n workerType: this.options.workerType,\n maxConcurrency: this.options.concurrency,\n metadata: this.options.metadata,\n version: this.options.version,\n });\n\n this.workerId = registration.id;\n this.debug(`Worker registered: ${this.workerId}`, registration);\n\n // Start worker heartbeat\n const heartbeatInterval = registration.heartbeatIntervalSecs * 1000;\n this.workerHeartbeatTimer = setInterval(() => {\n this.sendWorkerHeartbeat();\n }, heartbeatInterval);\n\n // Start polling\n this.state = 'running';\n this.emit('started', { workerId: this.workerId, queueName: this.options.queueName });\n this.schedulePoll();\n } catch (error) {\n this.state = 'error';\n this.emit('error', { error: error as Error });\n throw error;\n }\n }\n\n /**\n * Stop the worker gracefully\n */\n async stop(): Promise<void> {\n if (this.state !== 'running') {\n return;\n }\n\n // Deduplicate concurrent stop calls\n if (this.shutdownPromise) {\n return this.shutdownPromise;\n }\n\n this.shutdownPromise = this.doStop();\n return this.shutdownPromise;\n }\n\n private async doStop(): Promise<void> {\n this.state = 'stopping';\n this.debug('Stopping worker...');\n\n // Stop polling\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n\n // Stop worker heartbeat\n if (this.workerHeartbeatTimer) {\n clearInterval(this.workerHeartbeatTimer);\n this.workerHeartbeatTimer = null;\n }\n\n // Signal all active jobs to stop\n for (const active of this.activeJobs.values()) {\n active.abortController.abort();\n }\n\n // Wait for active jobs to complete (with timeout)\n if (this.activeJobs.size > 0) {\n this.debug(`Waiting for ${this.activeJobs.size} active jobs to complete...`);\n\n await Promise.race([\n this.waitForActiveJobs(),\n this.sleep(this.options.shutdownTimeout),\n ]);\n\n // Force-fail any remaining jobs. Snapshot the entries first because\n // cleanupJob() mutates the activeJobs map as we go.\n const remaining = Array.from(this.activeJobs.values());\n for (const active of remaining) {\n this.debug(`Force-failing job ${active.job.id} due to shutdown timeout`);\n await this.failJob(active.job, 'Worker shutdown timeout');\n // The handler's finally may never run if user code ignores the abort\n // signal, so explicitly clear this exact execution's timer.\n this.cleanupJob(active);\n }\n }\n\n // Deregister worker\n if (this.workerId) {\n try {\n await this.client.workers.deregister(this.workerId);\n this.debug('Worker deregistered');\n } catch (error) {\n this.debug('Failed to deregister worker', error);\n }\n }\n\n this.state = 'stopped';\n this.workerId = null;\n this.emit('stopped', { workerId: this.workerId ?? '', reason: 'graceful' });\n this.shutdownPromise = null;\n }\n\n /**\n * Add event listener\n */\n on<E extends WorkerEvent>(event: E, handler: EventHandler<E>): () => void {\n if (!this.eventHandlers.has(event)) {\n this.eventHandlers.set(event, new Set());\n }\n this.eventHandlers.get(event)!.add(handler as EventHandler<WorkerEvent>);\n return () => this.off(event, handler);\n }\n\n /**\n * Remove event listener\n */\n off<E extends WorkerEvent>(event: E, handler: EventHandler<E>): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.delete(handler as EventHandler<WorkerEvent>);\n }\n }\n\n // Private methods\n\n private emit<E extends WorkerEvent>(event: E, data: WorkerEventData[E]): void {\n const handlers = this.eventHandlers.get(event);\n if (handlers) {\n handlers.forEach((handler) => {\n try {\n handler(data);\n } catch (error) {\n this.debug('Event handler error', { event, error });\n }\n });\n }\n }\n\n private schedulePoll(): void {\n if (this.state !== 'running') {\n return;\n }\n\n this.pollTimer = setTimeout(() => this.poll(), this.options.pollInterval);\n }\n\n private async poll(): Promise<void> {\n if (this.state !== 'running' || !this.workerId) {\n return;\n }\n\n // Check if we have capacity\n const availableSlots = this.options.concurrency - this.activeJobs.size;\n if (availableSlots <= 0) {\n this.schedulePoll();\n return;\n }\n\n try {\n // Claim jobs\n const result = await this.client.jobs.claim({\n queueName: this.options.queueName,\n workerId: this.workerId,\n limit: availableSlots,\n leaseDurationSecs: this.options.leaseDuration,\n });\n\n // Process claimed jobs\n for (const job of result.jobs) {\n this.processJob(job);\n }\n } catch (error) {\n this.debug('Poll failed', error);\n this.emit('error', { error: error as Error });\n }\n\n // Schedule next poll\n this.schedulePoll();\n }\n\n private processJob(job: ClaimedJob): void {\n this.emit('job:claimed', { jobId: job.id, queueName: job.queueName });\n\n const abortController = new AbortController();\n const activeJob: ActiveJob = {\n executionId: Symbol(job.id),\n job,\n startedAt: new Date(),\n abortController,\n };\n\n this.activeJobs.set(activeJob.executionId, activeJob);\n\n // Capture the immutable execution so a replacement lease cannot be borrowed.\n const heartbeatInterval = this.options.leaseDuration * this.options.heartbeatFraction * 1000;\n activeJob.heartbeatTimer = setInterval(() => {\n this.sendJobHeartbeat(activeJob);\n }, heartbeatInterval);\n\n // Execute handler\n this.executeHandler(activeJob);\n }\n\n private async executeHandler(activeJob: ActiveJob): Promise<void> {\n const { job, abortController } = activeJob;\n const { signal } = abortController;\n\n this.emit('job:started', { jobId: job.id, queueName: job.queueName });\n\n const context: JobContext = {\n jobId: job.id,\n queueName: job.queueName,\n payload: job.payload,\n retryCount: job.retryCount,\n maxRetries: job.maxRetries,\n signal,\n progress: async (_percent: number, _message?: string) => {\n // Progress tracking could be implemented via heartbeat metadata\n // For now, this is a no-op\n },\n log: (level, message, meta) => {\n this.debug(`[${job.id}] [${level}] ${message}`, meta);\n },\n };\n\n try {\n const result = await this.handler!(context);\n\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n // Complete the job\n await this.completeJob(job, result ?? undefined);\n } catch (error) {\n // Check if aborted\n if (signal.aborted) {\n this.debug(`Job ${job.id} was aborted`);\n return;\n }\n\n const errorMessage = error instanceof Error ? error.message : String(error);\n await this.failJob(job, errorMessage);\n } finally {\n this.cleanupJob(activeJob);\n }\n }\n\n private async completeJob(job: ClaimedJob, result?: JsonObject): Promise<void> {\n if (!this.workerId) return;\n\n try {\n await this.client.jobs.complete(job.id, {\n workerId: this.workerId,\n result,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit('job:completed', {\n jobId: job.id,\n queueName: job.queueName,\n result,\n });\n } catch (error) {\n this.debug(`Failed to complete job ${job.id}`, error);\n }\n }\n\n private async failJob(job: ClaimedJob, errorMessage: string): Promise<void> {\n if (!this.workerId) return;\n\n const willRetry = job.retryCount < job.maxRetries;\n\n try {\n await this.client.jobs.fail(job.id, {\n workerId: this.workerId,\n error: errorMessage,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n\n this.emit('job:failed', {\n jobId: job.id,\n queueName: job.queueName,\n error: errorMessage,\n willRetry,\n });\n } catch (error) {\n this.debug(`Failed to fail job ${job.id}`, error);\n }\n }\n\n private cleanupJob(activeJob: ActiveJob): void {\n if (activeJob.heartbeatTimer) {\n clearInterval(activeJob.heartbeatTimer);\n activeJob.heartbeatTimer = undefined;\n }\n\n if (this.activeJobs.get(activeJob.executionId) === activeJob) {\n this.activeJobs.delete(activeJob.executionId);\n }\n }\n\n private async sendJobHeartbeat(activeJob: ActiveJob): Promise<void> {\n if (!this.workerId) return;\n\n const { job } = activeJob;\n\n try {\n await this.client.jobs.heartbeat(job.id, {\n workerId: this.workerId,\n leaseDurationSecs: this.options.leaseDuration,\n ...(job.leaseId != null && { leaseId: job.leaseId }),\n });\n } catch (error) {\n this.debug(`Job heartbeat failed for ${job.id}`, error);\n }\n }\n\n private async sendWorkerHeartbeat(): Promise<void> {\n if (!this.workerId || this.state !== 'running') return;\n\n try {\n await this.client.workers.heartbeat(this.workerId, {\n currentJobs: this.activeJobs.size,\n status: 'healthy',\n });\n } catch (error) {\n this.debug('Worker heartbeat failed', error);\n }\n }\n\n private async waitForActiveJobs(): Promise<void> {\n while (this.activeJobs.size > 0) {\n await this.sleep(100);\n }\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|