workmatic 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -30,12 +30,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ DEFAULT_WORKER_TIMEOUT_MS: () => DEFAULT_WORKER_TIMEOUT_MS,
33
34
  createClient: () => createClient,
34
35
  createDashboard: () => createDashboard,
35
36
  createDashboardMiddleware: () => createDashboardMiddleware,
36
37
  createDatabase: () => createDatabase,
38
+ createOrchestrator: () => createOrchestrator,
37
39
  createWorker: () => createWorker,
38
40
  defaultBackoff: () => defaultBackoff,
41
+ getUnderlyingDb: () => getUnderlyingDb,
39
42
  validatePayload: () => validatePayload
40
43
  });
41
44
  module.exports = __toCommonJS(index_exports);
@@ -47,6 +50,7 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
47
50
  // src/database.ts
48
51
  var import_better_sqlite3 = __toESM(require("better-sqlite3"), 1);
49
52
  var import_kysely = require("kysely");
53
+ var kyselyToSqlite = /* @__PURE__ */ new WeakMap();
50
54
  function createDatabase(options = {}) {
51
55
  let sqliteDb;
52
56
  if (options.db) {
@@ -64,6 +68,7 @@ function createDatabase(options = {}) {
64
68
  })
65
69
  });
66
70
  createSchema(sqliteDb);
71
+ kyselyToSqlite.set(db, sqliteDb);
67
72
  return db;
68
73
  }
69
74
  function createSchema(db) {
@@ -99,6 +104,26 @@ function createSchema(db) {
99
104
  updated_at INTEGER NOT NULL
100
105
  )
101
106
  `);
107
+ db.exec(`
108
+ UPDATE workmatic_jobs SET status = 'dead' WHERE status = 'failed'
109
+ `);
110
+ }
111
+ function getUnderlyingDb(db) {
112
+ const mapped = kyselyToSqlite.get(db);
113
+ if (mapped) {
114
+ return mapped;
115
+ }
116
+ try {
117
+ const ex = db.getExecutor?.();
118
+ const dialect = ex?.adapter?.db;
119
+ if (dialect) {
120
+ return dialect;
121
+ }
122
+ } catch {
123
+ }
124
+ throw new Error(
125
+ "getUnderlyingDb: could not resolve better-sqlite3 instance (use createDatabase() or pass db from it)"
126
+ );
102
127
  }
103
128
 
104
129
  // src/client.ts
@@ -167,6 +192,42 @@ function createClient(options) {
167
192
  }).execute();
168
193
  return { ok: true, id: publicId };
169
194
  },
195
+ async addMany(payloads, opts = {}) {
196
+ const {
197
+ priority = 0,
198
+ delayMs = 0,
199
+ maxAttempts = 3
200
+ } = opts;
201
+ if (payloads.length === 0) {
202
+ return { ok: true, ids: [] };
203
+ }
204
+ const timestamp = now();
205
+ const runAt = timestamp + delayMs;
206
+ return await db.transaction().execute(async (trx) => {
207
+ const ids = [];
208
+ const rows = payloads.map((payload) => {
209
+ const payloadJson = validatePayload(payload);
210
+ const publicId = (0, import_nanoid.nanoid)();
211
+ ids.push(publicId);
212
+ return {
213
+ public_id: publicId,
214
+ queue,
215
+ payload: payloadJson,
216
+ status: "ready",
217
+ priority,
218
+ run_at: runAt,
219
+ attempts: 0,
220
+ max_attempts: maxAttempts,
221
+ lease_until: 0,
222
+ created_at: timestamp,
223
+ updated_at: timestamp,
224
+ last_error: null
225
+ };
226
+ });
227
+ await trx.insertInto("workmatic_jobs").values(rows).execute();
228
+ return { ok: true, ids };
229
+ });
230
+ },
170
231
  /**
171
232
  * Get job statistics for the queue
172
233
  */
@@ -179,7 +240,6 @@ function createClient(options) {
179
240
  ready: 0,
180
241
  running: 0,
181
242
  done: 0,
182
- failed: 0,
183
243
  dead: 0,
184
244
  total: 0
185
245
  };
@@ -210,6 +270,30 @@ function createClient(options) {
210
270
  // src/worker.ts
211
271
  var import_fastq = __toESM(require("fastq"), 1);
212
272
  var import_kysely3 = require("kysely");
273
+ var DEFAULT_WORKER_TIMEOUT_MS = 6e4;
274
+ function parseClaimedRows(result) {
275
+ const rows = result.rows;
276
+ if (!rows || !Array.isArray(rows)) {
277
+ return [];
278
+ }
279
+ return rows.map((row) => ({
280
+ id: row.id,
281
+ public_id: row.public_id,
282
+ queue: row.queue,
283
+ payload: row.payload,
284
+ attempts: row.attempts,
285
+ max_attempts: row.max_attempts,
286
+ priority: row.priority,
287
+ created_at: row.created_at,
288
+ last_error: row.last_error
289
+ }));
290
+ }
291
+ function requireProcessor(processor) {
292
+ if (!processor) {
293
+ throw new Error("No processor set");
294
+ }
295
+ return processor;
296
+ }
213
297
  function createWorker(options) {
214
298
  const {
215
299
  db,
@@ -217,10 +301,13 @@ function createWorker(options) {
217
301
  concurrency = 1,
218
302
  leaseMs = 3e4,
219
303
  pollMs = 1e3,
220
- timeoutMs,
304
+ timeoutMs = DEFAULT_WORKER_TIMEOUT_MS,
221
305
  backoff = defaultBackoff,
222
306
  persistState = false,
223
- autoRestore = true
307
+ autoRestore = true,
308
+ pauseCheckIntervalMs = 300,
309
+ requeueExpiredIntervalMs = 0,
310
+ onPumpError
224
311
  } = options;
225
312
  if (!db) {
226
313
  throw new Error("Database instance is required");
@@ -230,6 +317,13 @@ function createWorker(options) {
230
317
  let processor = null;
231
318
  let pumpTimeout = null;
232
319
  let fastqQueue = null;
320
+ let lastPauseCheckAt = 0;
321
+ let cachedDbPaused = false;
322
+ let lastRequeueAt = 0;
323
+ function notifyPumpError(error) {
324
+ console.error("[workmatic] Pump error:", error);
325
+ onPumpError?.(error);
326
+ }
233
327
  function getStateKey() {
234
328
  return `worker_state_${queue}`;
235
329
  }
@@ -237,8 +331,6 @@ function createWorker(options) {
237
331
  if (!persistState) return;
238
332
  const timestamp = now();
239
333
  const key = getStateKey();
240
- await db.schema.createTable("workmatic_settings").ifNotExists().addColumn("queue", "text", (col) => col.primaryKey()).addColumn("paused", "integer", (col) => col.notNull().defaultTo(0)).addColumn("updated_at", "integer", (col) => col.notNull()).execute().catch(() => {
241
- });
242
334
  await import_kysely3.sql`
243
335
  INSERT INTO workmatic_settings (queue, paused, updated_at)
244
336
  VALUES (${key}, ${state === "paused" ? 1 : state === "running" ? 2 : 0}, ${timestamp})
@@ -273,17 +365,20 @@ function createWorker(options) {
273
365
  const timestamp = now();
274
366
  const leaseUntil = timestamp + leaseMs;
275
367
  return await db.transaction().execute(async (trx) => {
276
- const jobs = await trx.selectFrom("workmatic_jobs").select(["id", "public_id", "queue", "payload", "attempts", "max_attempts"]).where("queue", "=", queue).where("status", "=", "ready").where("run_at", "<=", timestamp).orderBy("priority", "asc").orderBy("id", "asc").limit(limit).execute();
277
- if (jobs.length === 0) {
278
- return [];
279
- }
280
- const jobIds = jobs.map((j) => j.id);
281
- await trx.updateTable("workmatic_jobs").set({
282
- status: "running",
283
- lease_until: leaseUntil,
284
- updated_at: timestamp
285
- }).where("id", "in", jobIds).execute();
286
- return jobs;
368
+ const result = await import_kysely3.sql`
369
+ UPDATE workmatic_jobs
370
+ SET status = 'running', lease_until = ${leaseUntil}, updated_at = ${timestamp}
371
+ WHERE rowid IN (
372
+ SELECT rowid FROM workmatic_jobs
373
+ WHERE queue = ${queue}
374
+ AND status = 'ready'
375
+ AND run_at <= ${timestamp}
376
+ ORDER BY priority ASC, id ASC
377
+ LIMIT ${limit}
378
+ )
379
+ RETURNING id, public_id, queue, payload, attempts, max_attempts, priority, created_at, last_error
380
+ `.execute(trx);
381
+ return parseClaimedRows(result);
287
382
  });
288
383
  }
289
384
  async function markDone(jobId) {
@@ -331,28 +426,24 @@ function createWorker(options) {
331
426
  }
332
427
  }
333
428
  async function processJob(claimedJob) {
334
- if (!processor) {
335
- throw new Error("No processor set");
336
- }
429
+ const fn = requireProcessor(processor);
337
430
  const payload = parsePayload(claimedJob.payload);
338
431
  const job = {
339
432
  id: claimedJob.public_id,
340
433
  queue: claimedJob.queue,
341
434
  payload,
342
435
  status: "running",
343
- priority: 0,
344
- // Not needed for processing
436
+ priority: claimedJob.priority,
345
437
  attempts: claimedJob.attempts,
346
438
  maxAttempts: claimedJob.max_attempts,
347
- createdAt: 0,
348
- // Not needed for processing
349
- lastError: null
439
+ createdAt: claimedJob.created_at,
440
+ lastError: claimedJob.last_error
350
441
  };
351
442
  try {
352
443
  if (timeoutMs) {
353
- await withTimeout(processor(job), timeoutMs, job.id);
444
+ await withTimeout(fn(job), timeoutMs, job.id);
354
445
  } else {
355
- await processor(job);
446
+ await fn(job);
356
447
  }
357
448
  await markDone(claimedJob.id);
358
449
  } catch (error) {
@@ -377,12 +468,21 @@ function createWorker(options) {
377
468
  return;
378
469
  }
379
470
  try {
380
- const dbPaused = await isQueuePausedInDb();
381
- if (dbPaused) {
471
+ const t = now();
472
+ if (t - lastPauseCheckAt >= pauseCheckIntervalMs) {
473
+ lastPauseCheckAt = t;
474
+ cachedDbPaused = await isQueuePausedInDb();
475
+ }
476
+ if (cachedDbPaused) {
382
477
  pumpTimeout = setTimeout(pump, pollMs);
383
478
  return;
384
479
  }
385
- await requeueExpiredLeases();
480
+ if (requeueExpiredIntervalMs <= 0 || t - lastRequeueAt >= requeueExpiredIntervalMs) {
481
+ if (requeueExpiredIntervalMs > 0) {
482
+ lastRequeueAt = t;
483
+ }
484
+ await requeueExpiredLeases();
485
+ }
386
486
  const batchSize = concurrency * 2;
387
487
  const jobs = await claimBatch(batchSize);
388
488
  if (jobs.length > 0) {
@@ -394,7 +494,7 @@ function createWorker(options) {
394
494
  pumpTimeout = setTimeout(pump, pollMs);
395
495
  }
396
496
  } catch (error) {
397
- console.error("[workmatic] Pump error:", error);
497
+ notifyPumpError(error);
398
498
  pumpTimeout = setTimeout(pump, pollMs);
399
499
  }
400
500
  }
@@ -450,7 +550,6 @@ function createWorker(options) {
450
550
  ready: 0,
451
551
  running: 0,
452
552
  done: 0,
453
- failed: 0,
454
553
  dead: 0,
455
554
  total: 0
456
555
  };
@@ -502,12 +601,211 @@ function createWorker(options) {
502
601
  return worker;
503
602
  }
504
603
 
604
+ // src/orchestrator.ts
605
+ var import_kysely4 = require("kysely");
606
+ var DEFAULT_TRANSFER_STATUSES = ["ready", "dead"];
607
+ var DEFAULT_TRANSFER_LIMIT = 1e4;
608
+ function rowsUpdated(result) {
609
+ const row = result[0];
610
+ return Number(row?.numUpdatedRows ?? 0);
611
+ }
612
+ function normalizeStatuses(status) {
613
+ if (!status) return [...DEFAULT_TRANSFER_STATUSES];
614
+ return Array.isArray(status) ? status : [status];
615
+ }
616
+ function createOrchestrator(options) {
617
+ const { db } = options;
618
+ if (!db) {
619
+ throw new Error("Database instance is required");
620
+ }
621
+ const registry = /* @__PURE__ */ new Map();
622
+ function ensureEntry(queue) {
623
+ let entry = registry.get(queue);
624
+ if (!entry) {
625
+ entry = { client: createClient({ db, queue }) };
626
+ registry.set(queue, entry);
627
+ }
628
+ return entry;
629
+ }
630
+ async function setQueuePaused(queue, paused) {
631
+ const timestamp = now();
632
+ const value = paused ? 1 : 0;
633
+ await import_kysely4.sql`
634
+ INSERT INTO workmatic_settings (queue, paused, updated_at)
635
+ VALUES (${queue}, ${value}, ${timestamp})
636
+ ON CONFLICT(queue) DO UPDATE SET
637
+ paused = ${value},
638
+ updated_at = ${timestamp}
639
+ `.execute(db);
640
+ }
641
+ async function selectJobIds(from, status, limit) {
642
+ const rows = await db.selectFrom("workmatic_jobs").select("id").where("queue", "=", from).where("status", "=", status).orderBy("priority", "asc").orderBy("id", "asc").limit(limit).execute();
643
+ return rows.map((r) => r.id);
644
+ }
645
+ async function transferReady(from, to, limit) {
646
+ const ids = await selectJobIds(from, "ready", limit);
647
+ if (ids.length === 0) return 0;
648
+ const timestamp = now();
649
+ const result = await db.updateTable("workmatic_jobs").set({ queue: to, updated_at: timestamp }).where("id", "in", ids).execute();
650
+ return rowsUpdated(result);
651
+ }
652
+ async function transferDead(from, to, limit, resetForRetry) {
653
+ const ids = await selectJobIds(from, "dead", limit);
654
+ if (ids.length === 0) return 0;
655
+ const timestamp = now();
656
+ if (resetForRetry) {
657
+ const result2 = await db.updateTable("workmatic_jobs").set({
658
+ queue: to,
659
+ status: "ready",
660
+ attempts: 0,
661
+ lease_until: 0,
662
+ last_error: null,
663
+ updated_at: timestamp,
664
+ run_at: timestamp
665
+ }).where("id", "in", ids).execute();
666
+ return rowsUpdated(result2);
667
+ }
668
+ const result = await db.updateTable("workmatic_jobs").set({ queue: to, updated_at: timestamp }).where("id", "in", ids).execute();
669
+ return rowsUpdated(result);
670
+ }
671
+ async function transferOtherStatus(from, to, status, limit) {
672
+ const ids = await selectJobIds(from, status, limit);
673
+ if (ids.length === 0) return 0;
674
+ const timestamp = now();
675
+ const result = await db.updateTable("workmatic_jobs").set({ queue: to, updated_at: timestamp }).where("id", "in", ids).execute();
676
+ return rowsUpdated(result);
677
+ }
678
+ const orchestrator = {
679
+ register(queue, opts = {}) {
680
+ const client = createClient({ db, queue });
681
+ let worker;
682
+ if (opts.worker) {
683
+ worker = createWorker({ db, queue, ...opts.worker });
684
+ }
685
+ registry.set(queue, { client, worker });
686
+ return client;
687
+ },
688
+ client(queue) {
689
+ return ensureEntry(queue).client;
690
+ },
691
+ worker(queue) {
692
+ const entry = registry.get(queue);
693
+ if (!entry?.worker) {
694
+ throw new Error(`No worker registered for queue "${queue}"`);
695
+ }
696
+ return entry.worker;
697
+ },
698
+ workers() {
699
+ return [...registry.values()].map((e) => e.worker).filter((w) => w !== void 0);
700
+ },
701
+ async queues() {
702
+ const rows = await db.selectFrom("workmatic_jobs").select("queue").distinct().execute();
703
+ const names = new Set(rows.map((r) => r.queue));
704
+ for (const name of registry.keys()) {
705
+ names.add(name);
706
+ }
707
+ return [...names].sort();
708
+ },
709
+ process(queue, fn) {
710
+ this.worker(queue).process(fn);
711
+ },
712
+ startAll() {
713
+ for (const entry of registry.values()) {
714
+ entry.worker?.start();
715
+ }
716
+ },
717
+ async stopAll() {
718
+ const stops = [...registry.values()].map((e) => e.worker?.stop()).filter((p) => p !== void 0);
719
+ await Promise.all(stops);
720
+ },
721
+ async pause(queue) {
722
+ const entry = registry.get(queue);
723
+ entry?.worker?.pause();
724
+ await setQueuePaused(queue, true);
725
+ },
726
+ async resume(queue) {
727
+ const entry = registry.get(queue);
728
+ entry?.worker?.resume();
729
+ await setQueuePaused(queue, false);
730
+ },
731
+ async isPaused(queue) {
732
+ const setting = await db.selectFrom("workmatic_settings").select("paused").where("queue", "=", queue).executeTakeFirst();
733
+ return setting?.paused === 1;
734
+ },
735
+ async stats(queueName) {
736
+ const names = queueName ? [queueName] : await this.queues();
737
+ const result = {};
738
+ for (const name of names) {
739
+ result[name] = await ensureEntry(name).client.stats();
740
+ }
741
+ return result;
742
+ },
743
+ async transfer(opts) {
744
+ const { from, to, resetForRetry = false } = opts;
745
+ if (from === to) {
746
+ return { moved: 0 };
747
+ }
748
+ const statuses = normalizeStatuses(opts.status);
749
+ let remaining = opts.limit ?? DEFAULT_TRANSFER_LIMIT;
750
+ let moved = 0;
751
+ if (statuses.includes("ready") && remaining > 0) {
752
+ const n = await transferReady(from, to, remaining);
753
+ moved += n;
754
+ remaining -= n;
755
+ }
756
+ if (statuses.includes("dead") && remaining > 0) {
757
+ const n = await transferDead(from, to, remaining, resetForRetry);
758
+ moved += n;
759
+ remaining -= n;
760
+ }
761
+ for (const status of statuses) {
762
+ if (status === "ready" || status === "dead" || remaining <= 0) continue;
763
+ const n = await transferOtherStatus(from, to, status, remaining);
764
+ moved += n;
765
+ remaining -= n;
766
+ }
767
+ return { moved };
768
+ },
769
+ async moveJob(publicId, toQueue, opts = {}) {
770
+ const allowed = normalizeStatuses(opts.status);
771
+ const row = await db.selectFrom("workmatic_jobs").select(["id", "queue", "status"]).where("public_id", "=", publicId).executeTakeFirst();
772
+ if (!row) {
773
+ throw new Error(`Job not found: ${publicId}`);
774
+ }
775
+ if (row.queue === toQueue) {
776
+ return;
777
+ }
778
+ if (!allowed.includes(row.status)) {
779
+ throw new Error(
780
+ `Job ${publicId} has status "${row.status}" and cannot be moved (allowed: ${allowed.join(", ")})`
781
+ );
782
+ }
783
+ const timestamp = now();
784
+ const resetForRetry = opts.resetForRetry ?? false;
785
+ if (row.status === "dead" && resetForRetry) {
786
+ await db.updateTable("workmatic_jobs").set({
787
+ queue: toQueue,
788
+ status: "ready",
789
+ attempts: 0,
790
+ lease_until: 0,
791
+ last_error: null,
792
+ updated_at: timestamp,
793
+ run_at: timestamp
794
+ }).where("public_id", "=", publicId).execute();
795
+ return;
796
+ }
797
+ await db.updateTable("workmatic_jobs").set({ queue: toQueue, updated_at: timestamp }).where("public_id", "=", publicId).execute();
798
+ }
799
+ };
800
+ return orchestrator;
801
+ }
802
+
505
803
  // src/dashboard.ts
506
804
  var import_http = require("http");
507
805
  var import_url = require("url");
508
806
  var import_path = require("path");
509
807
  var import_promises = require("fs/promises");
510
- var import_kysely4 = require("kysely");
808
+ var import_kysely5 = require("kysely");
511
809
  var __filename2 = (0, import_url.fileURLToPath)(importMetaUrl);
512
810
  var __dirname = (0, import_path.dirname)(__filename2);
513
811
  var CONTENT_TYPES = {
@@ -516,6 +814,13 @@ var CONTENT_TYPES = {
516
814
  ".js": "application/javascript; charset=utf-8",
517
815
  ".json": "application/json; charset=utf-8"
518
816
  };
817
+ function requestUrl(url) {
818
+ return url || "";
819
+ }
820
+ function staticContentTypeFor(filePath) {
821
+ const ext = filePath.substring(filePath.lastIndexOf(".")) || ".html";
822
+ return CONTENT_TYPES[ext] || "application/octet-stream";
823
+ }
519
824
  function createRequestHandler(db, workerMap, basePath = "") {
520
825
  function sendJson(res, data, status = 200) {
521
826
  res.writeHead(status, { "Content-Type": "application/json" });
@@ -538,7 +843,7 @@ function createRequestHandler(db, workerMap, basePath = "") {
538
843
  return path;
539
844
  }
540
845
  async function handleGetJobs(req, res) {
541
- const query = parseQuery(req.url || "");
846
+ const query = parseQuery(requestUrl(req.url));
542
847
  const queueFilter = query.get("queue");
543
848
  const statusFilter = query.get("status");
544
849
  const limit = Math.min(parseInt(query.get("limit") || "50", 10), 100);
@@ -579,11 +884,11 @@ function createRequestHandler(db, workerMap, basePath = "") {
579
884
  sendJson(res, { jobs: apiJobs, limit, offset });
580
885
  }
581
886
  async function handleGetStats(req, res) {
582
- const query = parseQuery(req.url || "");
887
+ const query = parseQuery(requestUrl(req.url));
583
888
  const queueFilter = query.get("queue");
584
889
  let statsQuery = db.selectFrom("workmatic_jobs").select([
585
890
  "status",
586
- import_kysely4.sql`count(*)`.as("count")
891
+ import_kysely5.sql`count(*)`.as("count")
587
892
  ]).groupBy("status");
588
893
  if (queueFilter) {
589
894
  statsQuery = statsQuery.where("queue", "=", queueFilter);
@@ -593,7 +898,6 @@ function createRequestHandler(db, workerMap, basePath = "") {
593
898
  ready: 0,
594
899
  running: 0,
595
900
  done: 0,
596
- failed: 0,
597
901
  dead: 0,
598
902
  total: 0
599
903
  };
@@ -666,8 +970,7 @@ function createRequestHandler(db, workerMap, basePath = "") {
666
970
  async function serveStatic(res, filePath) {
667
971
  const dashboardDir = (0, import_path.join)(__dirname, "..", "dashboard");
668
972
  const fullPath = (0, import_path.join)(dashboardDir, filePath);
669
- const ext = filePath.substring(filePath.lastIndexOf(".")) || ".html";
670
- const contentType = CONTENT_TYPES[ext] || "application/octet-stream";
973
+ const contentType = staticContentTypeFor(filePath);
671
974
  try {
672
975
  const content = await (0, import_promises.readFile)(fullPath, "utf-8");
673
976
  res.writeHead(200, { "Content-Type": contentType });
@@ -677,21 +980,21 @@ function createRequestHandler(db, workerMap, basePath = "") {
677
980
  }
678
981
  }
679
982
  return async function handleRequest(req, res, next) {
680
- const path = getPath(req.url || "/");
681
- const fullUrl = req.url || "/";
682
- if (basePath && !fullUrl.startsWith(basePath)) {
683
- if (next) next();
684
- return false;
685
- }
686
- res.setHeader("Access-Control-Allow-Origin", "*");
687
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
688
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
689
- if (req.method === "OPTIONS") {
690
- res.writeHead(204);
691
- res.end();
692
- return true;
693
- }
694
983
  try {
984
+ const path = getPath(req.url || "/");
985
+ const fullUrl = req.url || "/";
986
+ if (basePath && !fullUrl.startsWith(basePath)) {
987
+ if (next) next();
988
+ return false;
989
+ }
990
+ res.setHeader("Access-Control-Allow-Origin", "*");
991
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
992
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
993
+ if (req.method === "OPTIONS") {
994
+ res.writeHead(204);
995
+ res.end();
996
+ return true;
997
+ }
695
998
  if (path === "/api/jobs" && req.method === "GET") {
696
999
  await handleGetJobs(req, res);
697
1000
  return true;
@@ -751,11 +1054,7 @@ function createDashboard(options) {
751
1054
  }
752
1055
  const handleRequest = createRequestHandler(db, workerMap);
753
1056
  const server = (0, import_http.createServer)((req, res) => {
754
- handleRequest(req, res).catch((error) => {
755
- console.error("[workmatic] Unhandled error:", error);
756
- res.writeHead(500, { "Content-Type": "application/json" });
757
- res.end(JSON.stringify({ error: "Internal server error" }));
758
- });
1057
+ void handleRequest(req, res);
759
1058
  });
760
1059
  server.listen(port);
761
1060
  return {
@@ -787,25 +1086,20 @@ function createDashboardMiddleware(options) {
787
1086
  }
788
1087
  const handleRequest = createRequestHandler(db, workerMap, basePath);
789
1088
  return (req, res, next) => {
790
- handleRequest(req, res, next).catch((error) => {
791
- console.error("[workmatic] Unhandled error:", error);
792
- if (next) {
793
- next();
794
- } else {
795
- res.writeHead(500, { "Content-Type": "application/json" });
796
- res.end(JSON.stringify({ error: "Internal server error" }));
797
- }
798
- });
1089
+ void handleRequest(req, res, next);
799
1090
  };
800
1091
  }
801
1092
  // Annotate the CommonJS export names for ESM import in node:
802
1093
  0 && (module.exports = {
1094
+ DEFAULT_WORKER_TIMEOUT_MS,
803
1095
  createClient,
804
1096
  createDashboard,
805
1097
  createDashboardMiddleware,
806
1098
  createDatabase,
1099
+ createOrchestrator,
807
1100
  createWorker,
808
1101
  defaultBackoff,
1102
+ getUnderlyingDb,
809
1103
  validatePayload
810
1104
  });
811
1105
  //# sourceMappingURL=index.cjs.map