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.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // src/database.ts
2
2
  import Database from "better-sqlite3";
3
3
  import { Kysely, SqliteDialect } from "kysely";
4
+ var kyselyToSqlite = /* @__PURE__ */ new WeakMap();
4
5
  function createDatabase(options = {}) {
5
6
  let sqliteDb;
6
7
  if (options.db) {
@@ -18,6 +19,7 @@ function createDatabase(options = {}) {
18
19
  })
19
20
  });
20
21
  createSchema(sqliteDb);
22
+ kyselyToSqlite.set(db, sqliteDb);
21
23
  return db;
22
24
  }
23
25
  function createSchema(db) {
@@ -53,6 +55,26 @@ function createSchema(db) {
53
55
  updated_at INTEGER NOT NULL
54
56
  )
55
57
  `);
58
+ db.exec(`
59
+ UPDATE workmatic_jobs SET status = 'dead' WHERE status = 'failed'
60
+ `);
61
+ }
62
+ function getUnderlyingDb(db) {
63
+ const mapped = kyselyToSqlite.get(db);
64
+ if (mapped) {
65
+ return mapped;
66
+ }
67
+ try {
68
+ const ex = db.getExecutor?.();
69
+ const dialect = ex?.adapter?.db;
70
+ if (dialect) {
71
+ return dialect;
72
+ }
73
+ } catch {
74
+ }
75
+ throw new Error(
76
+ "getUnderlyingDb: could not resolve better-sqlite3 instance (use createDatabase() or pass db from it)"
77
+ );
56
78
  }
57
79
 
58
80
  // src/client.ts
@@ -121,6 +143,42 @@ function createClient(options) {
121
143
  }).execute();
122
144
  return { ok: true, id: publicId };
123
145
  },
146
+ async addMany(payloads, opts = {}) {
147
+ const {
148
+ priority = 0,
149
+ delayMs = 0,
150
+ maxAttempts = 3
151
+ } = opts;
152
+ if (payloads.length === 0) {
153
+ return { ok: true, ids: [] };
154
+ }
155
+ const timestamp = now();
156
+ const runAt = timestamp + delayMs;
157
+ return await db.transaction().execute(async (trx) => {
158
+ const ids = [];
159
+ const rows = payloads.map((payload) => {
160
+ const payloadJson = validatePayload(payload);
161
+ const publicId = nanoid();
162
+ ids.push(publicId);
163
+ return {
164
+ public_id: publicId,
165
+ queue,
166
+ payload: payloadJson,
167
+ status: "ready",
168
+ priority,
169
+ run_at: runAt,
170
+ attempts: 0,
171
+ max_attempts: maxAttempts,
172
+ lease_until: 0,
173
+ created_at: timestamp,
174
+ updated_at: timestamp,
175
+ last_error: null
176
+ };
177
+ });
178
+ await trx.insertInto("workmatic_jobs").values(rows).execute();
179
+ return { ok: true, ids };
180
+ });
181
+ },
124
182
  /**
125
183
  * Get job statistics for the queue
126
184
  */
@@ -133,7 +191,6 @@ function createClient(options) {
133
191
  ready: 0,
134
192
  running: 0,
135
193
  done: 0,
136
- failed: 0,
137
194
  dead: 0,
138
195
  total: 0
139
196
  };
@@ -164,6 +221,30 @@ function createClient(options) {
164
221
  // src/worker.ts
165
222
  import fastq from "fastq";
166
223
  import { sql as sql2 } from "kysely";
224
+ var DEFAULT_WORKER_TIMEOUT_MS = 6e4;
225
+ function parseClaimedRows(result) {
226
+ const rows = result.rows;
227
+ if (!rows || !Array.isArray(rows)) {
228
+ return [];
229
+ }
230
+ return rows.map((row) => ({
231
+ id: row.id,
232
+ public_id: row.public_id,
233
+ queue: row.queue,
234
+ payload: row.payload,
235
+ attempts: row.attempts,
236
+ max_attempts: row.max_attempts,
237
+ priority: row.priority,
238
+ created_at: row.created_at,
239
+ last_error: row.last_error
240
+ }));
241
+ }
242
+ function requireProcessor(processor) {
243
+ if (!processor) {
244
+ throw new Error("No processor set");
245
+ }
246
+ return processor;
247
+ }
167
248
  function createWorker(options) {
168
249
  const {
169
250
  db,
@@ -171,10 +252,13 @@ function createWorker(options) {
171
252
  concurrency = 1,
172
253
  leaseMs = 3e4,
173
254
  pollMs = 1e3,
174
- timeoutMs,
255
+ timeoutMs = DEFAULT_WORKER_TIMEOUT_MS,
175
256
  backoff = defaultBackoff,
176
257
  persistState = false,
177
- autoRestore = true
258
+ autoRestore = true,
259
+ pauseCheckIntervalMs = 300,
260
+ requeueExpiredIntervalMs = 0,
261
+ onPumpError
178
262
  } = options;
179
263
  if (!db) {
180
264
  throw new Error("Database instance is required");
@@ -184,6 +268,13 @@ function createWorker(options) {
184
268
  let processor = null;
185
269
  let pumpTimeout = null;
186
270
  let fastqQueue = null;
271
+ let lastPauseCheckAt = 0;
272
+ let cachedDbPaused = false;
273
+ let lastRequeueAt = 0;
274
+ function notifyPumpError(error) {
275
+ console.error("[workmatic] Pump error:", error);
276
+ onPumpError?.(error);
277
+ }
187
278
  function getStateKey() {
188
279
  return `worker_state_${queue}`;
189
280
  }
@@ -191,8 +282,6 @@ function createWorker(options) {
191
282
  if (!persistState) return;
192
283
  const timestamp = now();
193
284
  const key = getStateKey();
194
- 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(() => {
195
- });
196
285
  await sql2`
197
286
  INSERT INTO workmatic_settings (queue, paused, updated_at)
198
287
  VALUES (${key}, ${state === "paused" ? 1 : state === "running" ? 2 : 0}, ${timestamp})
@@ -227,17 +316,20 @@ function createWorker(options) {
227
316
  const timestamp = now();
228
317
  const leaseUntil = timestamp + leaseMs;
229
318
  return await db.transaction().execute(async (trx) => {
230
- 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();
231
- if (jobs.length === 0) {
232
- return [];
233
- }
234
- const jobIds = jobs.map((j) => j.id);
235
- await trx.updateTable("workmatic_jobs").set({
236
- status: "running",
237
- lease_until: leaseUntil,
238
- updated_at: timestamp
239
- }).where("id", "in", jobIds).execute();
240
- return jobs;
319
+ const result = await sql2`
320
+ UPDATE workmatic_jobs
321
+ SET status = 'running', lease_until = ${leaseUntil}, updated_at = ${timestamp}
322
+ WHERE rowid IN (
323
+ SELECT rowid FROM workmatic_jobs
324
+ WHERE queue = ${queue}
325
+ AND status = 'ready'
326
+ AND run_at <= ${timestamp}
327
+ ORDER BY priority ASC, id ASC
328
+ LIMIT ${limit}
329
+ )
330
+ RETURNING id, public_id, queue, payload, attempts, max_attempts, priority, created_at, last_error
331
+ `.execute(trx);
332
+ return parseClaimedRows(result);
241
333
  });
242
334
  }
243
335
  async function markDone(jobId) {
@@ -285,28 +377,24 @@ function createWorker(options) {
285
377
  }
286
378
  }
287
379
  async function processJob(claimedJob) {
288
- if (!processor) {
289
- throw new Error("No processor set");
290
- }
380
+ const fn = requireProcessor(processor);
291
381
  const payload = parsePayload(claimedJob.payload);
292
382
  const job = {
293
383
  id: claimedJob.public_id,
294
384
  queue: claimedJob.queue,
295
385
  payload,
296
386
  status: "running",
297
- priority: 0,
298
- // Not needed for processing
387
+ priority: claimedJob.priority,
299
388
  attempts: claimedJob.attempts,
300
389
  maxAttempts: claimedJob.max_attempts,
301
- createdAt: 0,
302
- // Not needed for processing
303
- lastError: null
390
+ createdAt: claimedJob.created_at,
391
+ lastError: claimedJob.last_error
304
392
  };
305
393
  try {
306
394
  if (timeoutMs) {
307
- await withTimeout(processor(job), timeoutMs, job.id);
395
+ await withTimeout(fn(job), timeoutMs, job.id);
308
396
  } else {
309
- await processor(job);
397
+ await fn(job);
310
398
  }
311
399
  await markDone(claimedJob.id);
312
400
  } catch (error) {
@@ -331,12 +419,21 @@ function createWorker(options) {
331
419
  return;
332
420
  }
333
421
  try {
334
- const dbPaused = await isQueuePausedInDb();
335
- if (dbPaused) {
422
+ const t = now();
423
+ if (t - lastPauseCheckAt >= pauseCheckIntervalMs) {
424
+ lastPauseCheckAt = t;
425
+ cachedDbPaused = await isQueuePausedInDb();
426
+ }
427
+ if (cachedDbPaused) {
336
428
  pumpTimeout = setTimeout(pump, pollMs);
337
429
  return;
338
430
  }
339
- await requeueExpiredLeases();
431
+ if (requeueExpiredIntervalMs <= 0 || t - lastRequeueAt >= requeueExpiredIntervalMs) {
432
+ if (requeueExpiredIntervalMs > 0) {
433
+ lastRequeueAt = t;
434
+ }
435
+ await requeueExpiredLeases();
436
+ }
340
437
  const batchSize = concurrency * 2;
341
438
  const jobs = await claimBatch(batchSize);
342
439
  if (jobs.length > 0) {
@@ -348,7 +445,7 @@ function createWorker(options) {
348
445
  pumpTimeout = setTimeout(pump, pollMs);
349
446
  }
350
447
  } catch (error) {
351
- console.error("[workmatic] Pump error:", error);
448
+ notifyPumpError(error);
352
449
  pumpTimeout = setTimeout(pump, pollMs);
353
450
  }
354
451
  }
@@ -404,7 +501,6 @@ function createWorker(options) {
404
501
  ready: 0,
405
502
  running: 0,
406
503
  done: 0,
407
- failed: 0,
408
504
  dead: 0,
409
505
  total: 0
410
506
  };
@@ -456,12 +552,211 @@ function createWorker(options) {
456
552
  return worker;
457
553
  }
458
554
 
555
+ // src/orchestrator.ts
556
+ import { sql as sql3 } from "kysely";
557
+ var DEFAULT_TRANSFER_STATUSES = ["ready", "dead"];
558
+ var DEFAULT_TRANSFER_LIMIT = 1e4;
559
+ function rowsUpdated(result) {
560
+ const row = result[0];
561
+ return Number(row?.numUpdatedRows ?? 0);
562
+ }
563
+ function normalizeStatuses(status) {
564
+ if (!status) return [...DEFAULT_TRANSFER_STATUSES];
565
+ return Array.isArray(status) ? status : [status];
566
+ }
567
+ function createOrchestrator(options) {
568
+ const { db } = options;
569
+ if (!db) {
570
+ throw new Error("Database instance is required");
571
+ }
572
+ const registry = /* @__PURE__ */ new Map();
573
+ function ensureEntry(queue) {
574
+ let entry = registry.get(queue);
575
+ if (!entry) {
576
+ entry = { client: createClient({ db, queue }) };
577
+ registry.set(queue, entry);
578
+ }
579
+ return entry;
580
+ }
581
+ async function setQueuePaused(queue, paused) {
582
+ const timestamp = now();
583
+ const value = paused ? 1 : 0;
584
+ await sql3`
585
+ INSERT INTO workmatic_settings (queue, paused, updated_at)
586
+ VALUES (${queue}, ${value}, ${timestamp})
587
+ ON CONFLICT(queue) DO UPDATE SET
588
+ paused = ${value},
589
+ updated_at = ${timestamp}
590
+ `.execute(db);
591
+ }
592
+ async function selectJobIds(from, status, limit) {
593
+ const rows = await db.selectFrom("workmatic_jobs").select("id").where("queue", "=", from).where("status", "=", status).orderBy("priority", "asc").orderBy("id", "asc").limit(limit).execute();
594
+ return rows.map((r) => r.id);
595
+ }
596
+ async function transferReady(from, to, limit) {
597
+ const ids = await selectJobIds(from, "ready", limit);
598
+ if (ids.length === 0) return 0;
599
+ const timestamp = now();
600
+ const result = await db.updateTable("workmatic_jobs").set({ queue: to, updated_at: timestamp }).where("id", "in", ids).execute();
601
+ return rowsUpdated(result);
602
+ }
603
+ async function transferDead(from, to, limit, resetForRetry) {
604
+ const ids = await selectJobIds(from, "dead", limit);
605
+ if (ids.length === 0) return 0;
606
+ const timestamp = now();
607
+ if (resetForRetry) {
608
+ const result2 = await db.updateTable("workmatic_jobs").set({
609
+ queue: to,
610
+ status: "ready",
611
+ attempts: 0,
612
+ lease_until: 0,
613
+ last_error: null,
614
+ updated_at: timestamp,
615
+ run_at: timestamp
616
+ }).where("id", "in", ids).execute();
617
+ return rowsUpdated(result2);
618
+ }
619
+ const result = await db.updateTable("workmatic_jobs").set({ queue: to, updated_at: timestamp }).where("id", "in", ids).execute();
620
+ return rowsUpdated(result);
621
+ }
622
+ async function transferOtherStatus(from, to, status, limit) {
623
+ const ids = await selectJobIds(from, status, limit);
624
+ if (ids.length === 0) return 0;
625
+ const timestamp = now();
626
+ const result = await db.updateTable("workmatic_jobs").set({ queue: to, updated_at: timestamp }).where("id", "in", ids).execute();
627
+ return rowsUpdated(result);
628
+ }
629
+ const orchestrator = {
630
+ register(queue, opts = {}) {
631
+ const client = createClient({ db, queue });
632
+ let worker;
633
+ if (opts.worker) {
634
+ worker = createWorker({ db, queue, ...opts.worker });
635
+ }
636
+ registry.set(queue, { client, worker });
637
+ return client;
638
+ },
639
+ client(queue) {
640
+ return ensureEntry(queue).client;
641
+ },
642
+ worker(queue) {
643
+ const entry = registry.get(queue);
644
+ if (!entry?.worker) {
645
+ throw new Error(`No worker registered for queue "${queue}"`);
646
+ }
647
+ return entry.worker;
648
+ },
649
+ workers() {
650
+ return [...registry.values()].map((e) => e.worker).filter((w) => w !== void 0);
651
+ },
652
+ async queues() {
653
+ const rows = await db.selectFrom("workmatic_jobs").select("queue").distinct().execute();
654
+ const names = new Set(rows.map((r) => r.queue));
655
+ for (const name of registry.keys()) {
656
+ names.add(name);
657
+ }
658
+ return [...names].sort();
659
+ },
660
+ process(queue, fn) {
661
+ this.worker(queue).process(fn);
662
+ },
663
+ startAll() {
664
+ for (const entry of registry.values()) {
665
+ entry.worker?.start();
666
+ }
667
+ },
668
+ async stopAll() {
669
+ const stops = [...registry.values()].map((e) => e.worker?.stop()).filter((p) => p !== void 0);
670
+ await Promise.all(stops);
671
+ },
672
+ async pause(queue) {
673
+ const entry = registry.get(queue);
674
+ entry?.worker?.pause();
675
+ await setQueuePaused(queue, true);
676
+ },
677
+ async resume(queue) {
678
+ const entry = registry.get(queue);
679
+ entry?.worker?.resume();
680
+ await setQueuePaused(queue, false);
681
+ },
682
+ async isPaused(queue) {
683
+ const setting = await db.selectFrom("workmatic_settings").select("paused").where("queue", "=", queue).executeTakeFirst();
684
+ return setting?.paused === 1;
685
+ },
686
+ async stats(queueName) {
687
+ const names = queueName ? [queueName] : await this.queues();
688
+ const result = {};
689
+ for (const name of names) {
690
+ result[name] = await ensureEntry(name).client.stats();
691
+ }
692
+ return result;
693
+ },
694
+ async transfer(opts) {
695
+ const { from, to, resetForRetry = false } = opts;
696
+ if (from === to) {
697
+ return { moved: 0 };
698
+ }
699
+ const statuses = normalizeStatuses(opts.status);
700
+ let remaining = opts.limit ?? DEFAULT_TRANSFER_LIMIT;
701
+ let moved = 0;
702
+ if (statuses.includes("ready") && remaining > 0) {
703
+ const n = await transferReady(from, to, remaining);
704
+ moved += n;
705
+ remaining -= n;
706
+ }
707
+ if (statuses.includes("dead") && remaining > 0) {
708
+ const n = await transferDead(from, to, remaining, resetForRetry);
709
+ moved += n;
710
+ remaining -= n;
711
+ }
712
+ for (const status of statuses) {
713
+ if (status === "ready" || status === "dead" || remaining <= 0) continue;
714
+ const n = await transferOtherStatus(from, to, status, remaining);
715
+ moved += n;
716
+ remaining -= n;
717
+ }
718
+ return { moved };
719
+ },
720
+ async moveJob(publicId, toQueue, opts = {}) {
721
+ const allowed = normalizeStatuses(opts.status);
722
+ const row = await db.selectFrom("workmatic_jobs").select(["id", "queue", "status"]).where("public_id", "=", publicId).executeTakeFirst();
723
+ if (!row) {
724
+ throw new Error(`Job not found: ${publicId}`);
725
+ }
726
+ if (row.queue === toQueue) {
727
+ return;
728
+ }
729
+ if (!allowed.includes(row.status)) {
730
+ throw new Error(
731
+ `Job ${publicId} has status "${row.status}" and cannot be moved (allowed: ${allowed.join(", ")})`
732
+ );
733
+ }
734
+ const timestamp = now();
735
+ const resetForRetry = opts.resetForRetry ?? false;
736
+ if (row.status === "dead" && resetForRetry) {
737
+ await db.updateTable("workmatic_jobs").set({
738
+ queue: toQueue,
739
+ status: "ready",
740
+ attempts: 0,
741
+ lease_until: 0,
742
+ last_error: null,
743
+ updated_at: timestamp,
744
+ run_at: timestamp
745
+ }).where("public_id", "=", publicId).execute();
746
+ return;
747
+ }
748
+ await db.updateTable("workmatic_jobs").set({ queue: toQueue, updated_at: timestamp }).where("public_id", "=", publicId).execute();
749
+ }
750
+ };
751
+ return orchestrator;
752
+ }
753
+
459
754
  // src/dashboard.ts
460
755
  import { createServer } from "http";
461
756
  import { fileURLToPath } from "url";
462
757
  import { dirname, join } from "path";
463
758
  import { readFile } from "fs/promises";
464
- import { sql as sql3 } from "kysely";
759
+ import { sql as sql4 } from "kysely";
465
760
  var __filename2 = fileURLToPath(import.meta.url);
466
761
  var __dirname2 = dirname(__filename2);
467
762
  var CONTENT_TYPES = {
@@ -470,6 +765,13 @@ var CONTENT_TYPES = {
470
765
  ".js": "application/javascript; charset=utf-8",
471
766
  ".json": "application/json; charset=utf-8"
472
767
  };
768
+ function requestUrl(url) {
769
+ return url || "";
770
+ }
771
+ function staticContentTypeFor(filePath) {
772
+ const ext = filePath.substring(filePath.lastIndexOf(".")) || ".html";
773
+ return CONTENT_TYPES[ext] || "application/octet-stream";
774
+ }
473
775
  function createRequestHandler(db, workerMap, basePath = "") {
474
776
  function sendJson(res, data, status = 200) {
475
777
  res.writeHead(status, { "Content-Type": "application/json" });
@@ -492,7 +794,7 @@ function createRequestHandler(db, workerMap, basePath = "") {
492
794
  return path;
493
795
  }
494
796
  async function handleGetJobs(req, res) {
495
- const query = parseQuery(req.url || "");
797
+ const query = parseQuery(requestUrl(req.url));
496
798
  const queueFilter = query.get("queue");
497
799
  const statusFilter = query.get("status");
498
800
  const limit = Math.min(parseInt(query.get("limit") || "50", 10), 100);
@@ -533,11 +835,11 @@ function createRequestHandler(db, workerMap, basePath = "") {
533
835
  sendJson(res, { jobs: apiJobs, limit, offset });
534
836
  }
535
837
  async function handleGetStats(req, res) {
536
- const query = parseQuery(req.url || "");
838
+ const query = parseQuery(requestUrl(req.url));
537
839
  const queueFilter = query.get("queue");
538
840
  let statsQuery = db.selectFrom("workmatic_jobs").select([
539
841
  "status",
540
- sql3`count(*)`.as("count")
842
+ sql4`count(*)`.as("count")
541
843
  ]).groupBy("status");
542
844
  if (queueFilter) {
543
845
  statsQuery = statsQuery.where("queue", "=", queueFilter);
@@ -547,7 +849,6 @@ function createRequestHandler(db, workerMap, basePath = "") {
547
849
  ready: 0,
548
850
  running: 0,
549
851
  done: 0,
550
- failed: 0,
551
852
  dead: 0,
552
853
  total: 0
553
854
  };
@@ -620,8 +921,7 @@ function createRequestHandler(db, workerMap, basePath = "") {
620
921
  async function serveStatic(res, filePath) {
621
922
  const dashboardDir = join(__dirname2, "..", "dashboard");
622
923
  const fullPath = join(dashboardDir, filePath);
623
- const ext = filePath.substring(filePath.lastIndexOf(".")) || ".html";
624
- const contentType = CONTENT_TYPES[ext] || "application/octet-stream";
924
+ const contentType = staticContentTypeFor(filePath);
625
925
  try {
626
926
  const content = await readFile(fullPath, "utf-8");
627
927
  res.writeHead(200, { "Content-Type": contentType });
@@ -631,21 +931,21 @@ function createRequestHandler(db, workerMap, basePath = "") {
631
931
  }
632
932
  }
633
933
  return async function handleRequest(req, res, next) {
634
- const path = getPath(req.url || "/");
635
- const fullUrl = req.url || "/";
636
- if (basePath && !fullUrl.startsWith(basePath)) {
637
- if (next) next();
638
- return false;
639
- }
640
- res.setHeader("Access-Control-Allow-Origin", "*");
641
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
642
- res.setHeader("Access-Control-Allow-Headers", "Content-Type");
643
- if (req.method === "OPTIONS") {
644
- res.writeHead(204);
645
- res.end();
646
- return true;
647
- }
648
934
  try {
935
+ const path = getPath(req.url || "/");
936
+ const fullUrl = req.url || "/";
937
+ if (basePath && !fullUrl.startsWith(basePath)) {
938
+ if (next) next();
939
+ return false;
940
+ }
941
+ res.setHeader("Access-Control-Allow-Origin", "*");
942
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
943
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
944
+ if (req.method === "OPTIONS") {
945
+ res.writeHead(204);
946
+ res.end();
947
+ return true;
948
+ }
649
949
  if (path === "/api/jobs" && req.method === "GET") {
650
950
  await handleGetJobs(req, res);
651
951
  return true;
@@ -705,11 +1005,7 @@ function createDashboard(options) {
705
1005
  }
706
1006
  const handleRequest = createRequestHandler(db, workerMap);
707
1007
  const server = createServer((req, res) => {
708
- handleRequest(req, res).catch((error) => {
709
- console.error("[workmatic] Unhandled error:", error);
710
- res.writeHead(500, { "Content-Type": "application/json" });
711
- res.end(JSON.stringify({ error: "Internal server error" }));
712
- });
1008
+ void handleRequest(req, res);
713
1009
  });
714
1010
  server.listen(port);
715
1011
  return {
@@ -741,24 +1037,19 @@ function createDashboardMiddleware(options) {
741
1037
  }
742
1038
  const handleRequest = createRequestHandler(db, workerMap, basePath);
743
1039
  return (req, res, next) => {
744
- handleRequest(req, res, next).catch((error) => {
745
- console.error("[workmatic] Unhandled error:", error);
746
- if (next) {
747
- next();
748
- } else {
749
- res.writeHead(500, { "Content-Type": "application/json" });
750
- res.end(JSON.stringify({ error: "Internal server error" }));
751
- }
752
- });
1040
+ void handleRequest(req, res, next);
753
1041
  };
754
1042
  }
755
1043
  export {
1044
+ DEFAULT_WORKER_TIMEOUT_MS,
756
1045
  createClient,
757
1046
  createDashboard,
758
1047
  createDashboardMiddleware,
759
1048
  createDatabase,
1049
+ createOrchestrator,
760
1050
  createWorker,
761
1051
  defaultBackoff,
1052
+ getUnderlyingDb,
762
1053
  validatePayload
763
1054
  };
764
1055
  //# sourceMappingURL=index.js.map