workmatic 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,6 +16,7 @@ Workmatic combines the simplicity of fastq with SQLite persistence. No Redis, no
16
16
  - **Delayed**: Schedule jobs to run in the future
17
17
  - **Retries**: Automatic retries with exponential backoff
18
18
  - **Lease-based**: Prevents double processing with lease locks
19
+ - **State Persistence**: Optionally persist worker state across restarts
19
20
  - **Dashboard**: Built-in web UI for monitoring
20
21
  - **Type-safe**: Full TypeScript support with Kysely
21
22
 
@@ -114,6 +115,20 @@ const stats = await client.stats();
114
115
  // { ready: 5, running: 2, done: 100, failed: 0, dead: 1, total: 108 }
115
116
  ```
116
117
 
118
+ #### `client.clear(options?)`
119
+
120
+ Clear all jobs from the queue.
121
+
122
+ ```typescript
123
+ // Clear all jobs
124
+ const deleted = await client.clear();
125
+ console.log(`Deleted ${deleted} jobs`);
126
+
127
+ // Clear only jobs with specific status
128
+ const deleted = await client.clear({ status: 'done' });
129
+ console.log(`Deleted ${deleted} done jobs`);
130
+ ```
131
+
117
132
  ### `createWorker(options)`
118
133
 
119
134
  Create a worker to process jobs from a queue.
@@ -127,6 +142,8 @@ const worker = createWorker({
127
142
  pollMs: 1000, // Optional: Poll interval when idle (default: 1000)
128
143
  timeoutMs: 60000, // Optional: Job execution timeout in ms (default: none)
129
144
  backoff: (n) => 1000 * Math.pow(2, n), // Optional: Retry backoff function
145
+ persistState: false, // Optional: Persist worker state to database (default: false)
146
+ autoRestore: true, // Optional: Auto-restore state on creation (default: true)
130
147
  });
131
148
  ```
132
149
 
@@ -178,6 +195,20 @@ Get job statistics for the queue.
178
195
  const stats = await worker.stats();
179
196
  ```
180
197
 
198
+ #### `worker.clear(options?)`
199
+
200
+ Clear all jobs from the queue.
201
+
202
+ ```typescript
203
+ // Clear all jobs in the queue
204
+ const deleted = await worker.clear();
205
+ console.log(`Deleted ${deleted} jobs`);
206
+
207
+ // Clear only jobs with specific status
208
+ const deleted = await worker.clear({ status: 'dead' });
209
+ console.log(`Deleted ${deleted} dead jobs`);
210
+ ```
211
+
181
212
  #### Worker properties
182
213
 
183
214
  ```typescript
@@ -186,6 +217,48 @@ worker.isPaused; // boolean
186
217
  worker.queue; // string
187
218
  ```
188
219
 
220
+ #### Persist State Mode
221
+
222
+ When `persistState: true`, the worker's state (running/paused/stopped) is saved to the database. This is useful when you want the worker to remember its state across app restarts.
223
+
224
+ ```typescript
225
+ const worker = createWorker({
226
+ db,
227
+ queue: 'emails',
228
+ persistState: true, // Save state to database
229
+ autoRestore: true, // Auto-restore on creation (default: true)
230
+ });
231
+
232
+ worker.process(async (job) => {
233
+ // ...
234
+ });
235
+
236
+ // Start the worker - state is saved as "running"
237
+ worker.start();
238
+
239
+ // Later, stop the worker - state is saved as "stopped"
240
+ await worker.stop();
241
+
242
+ // If the app restarts, the worker will NOT auto-start
243
+ // because the last saved state was "stopped"
244
+ ```
245
+
246
+ With `autoRestore: false`, you can manually control when to restore:
247
+
248
+ ```typescript
249
+ const worker = createWorker({
250
+ db,
251
+ persistState: true,
252
+ autoRestore: false, // Don't auto-restore
253
+ });
254
+
255
+ worker.process(async (job) => { /* ... */ });
256
+
257
+ // Manually check and restore saved state
258
+ const savedState = await worker.restoreState();
259
+ console.log(`Restored state: ${savedState}`); // 'running' | 'paused' | 'stopped' | null
260
+ ```
261
+
189
262
  ### `createDashboard(options)`
190
263
 
191
264
  Create a standalone web dashboard server for monitoring and control.
package/dist/index.cjs CHANGED
@@ -192,6 +192,17 @@ function createClient(options) {
192
192
  stats.total += count;
193
193
  }
194
194
  return stats;
195
+ },
196
+ /**
197
+ * Clear all jobs from the queue
198
+ */
199
+ async clear(options2 = {}) {
200
+ let query = db.deleteFrom("workmatic_jobs").where("queue", "=", queue);
201
+ if (options2.status) {
202
+ query = query.where("status", "=", options2.status);
203
+ }
204
+ const result = await query.execute();
205
+ return Number(result[0]?.numDeletedRows ?? 0);
195
206
  }
196
207
  };
197
208
  }
@@ -207,7 +218,9 @@ function createWorker(options) {
207
218
  leaseMs = 3e4,
208
219
  pollMs = 1e3,
209
220
  timeoutMs,
210
- backoff = defaultBackoff
221
+ backoff = defaultBackoff,
222
+ persistState = false,
223
+ autoRestore = true
211
224
  } = options;
212
225
  if (!db) {
213
226
  throw new Error("Database instance is required");
@@ -217,6 +230,36 @@ function createWorker(options) {
217
230
  let processor = null;
218
231
  let pumpTimeout = null;
219
232
  let fastqQueue = null;
233
+ function getStateKey() {
234
+ return `worker_state_${queue}`;
235
+ }
236
+ async function saveState(state) {
237
+ if (!persistState) return;
238
+ const timestamp = now();
239
+ 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
+ await import_kysely3.sql`
243
+ INSERT INTO workmatic_settings (queue, paused, updated_at)
244
+ VALUES (${key}, ${state === "paused" ? 1 : state === "running" ? 2 : 0}, ${timestamp})
245
+ ON CONFLICT(queue) DO UPDATE SET
246
+ paused = ${state === "paused" ? 1 : state === "running" ? 2 : 0},
247
+ updated_at = ${timestamp}
248
+ `.execute(db);
249
+ }
250
+ async function loadState() {
251
+ if (!persistState) return null;
252
+ try {
253
+ const key = getStateKey();
254
+ const result = await db.selectFrom("workmatic_settings").select("paused").where("queue", "=", key).executeTakeFirst();
255
+ if (!result) return null;
256
+ if (result.paused === 2) return "running";
257
+ if (result.paused === 1) return "paused";
258
+ return "stopped";
259
+ } catch {
260
+ return null;
261
+ }
262
+ }
220
263
  async function requeueExpiredLeases() {
221
264
  const timestamp = now();
222
265
  const result = await db.updateTable("workmatic_jobs").set({
@@ -369,6 +412,8 @@ function createWorker(options) {
369
412
  running = true;
370
413
  paused = false;
371
414
  fastqQueue = import_fastq.default.promise(processJob, concurrency);
415
+ saveState("running").catch(() => {
416
+ });
372
417
  pump();
373
418
  },
374
419
  async stop() {
@@ -384,12 +429,17 @@ function createWorker(options) {
384
429
  await fastqQueue.drained();
385
430
  fastqQueue = null;
386
431
  }
432
+ await saveState("stopped");
387
433
  },
388
434
  pause() {
389
435
  paused = true;
436
+ saveState("paused").catch(() => {
437
+ });
390
438
  },
391
439
  resume() {
392
440
  paused = false;
441
+ saveState("running").catch(() => {
442
+ });
393
443
  },
394
444
  async stats() {
395
445
  const result = await db.selectFrom("workmatic_jobs").select([
@@ -422,8 +472,33 @@ function createWorker(options) {
422
472
  },
423
473
  get queue() {
424
474
  return queue;
475
+ },
476
+ async restoreState() {
477
+ const state = await loadState();
478
+ if (state === "running" && processor) {
479
+ this.start();
480
+ } else if (state === "paused" && processor) {
481
+ this.start();
482
+ this.pause();
483
+ }
484
+ return state;
485
+ },
486
+ async clear(options2 = {}) {
487
+ let query = db.deleteFrom("workmatic_jobs").where("queue", "=", queue);
488
+ if (options2.status) {
489
+ query = query.where("status", "=", options2.status);
490
+ }
491
+ const result = await query.execute();
492
+ return Number(result[0]?.numDeletedRows ?? 0);
425
493
  }
426
494
  };
495
+ if (persistState && autoRestore) {
496
+ setImmediate(async () => {
497
+ if (processor) {
498
+ await worker.restoreState();
499
+ }
500
+ });
501
+ }
427
502
  return worker;
428
503
  }
429
504
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js","../src/database.ts","../src/client.ts","../src/utils.ts","../src/worker.ts","../src/dashboard.ts"],"sourcesContent":["/**\n * Workmatic - A persistent job queue for Node.js\n * \n * @packageDocumentation\n */\n\n// Main exports\nexport { createDatabase } from './database.js';\nexport { createClient } from './client.js';\nexport { createWorker } from './worker.js';\nexport { createDashboard, createDashboardMiddleware } from './dashboard.js';\n\n// Utility exports\nexport { defaultBackoff, validatePayload } from './utils.js';\n\n// Type exports\nexport type {\n // Core types\n Job,\n JobStatus,\n JobStats,\n ClaimedJob,\n \n // Options\n AddJobOptions,\n AddJobResult,\n DatabaseOptions,\n ClientOptions,\n WorkerOptions,\n DashboardOptions,\n DashboardMiddlewareOptions,\n BackoffFunction,\n JobProcessor,\n \n // Interfaces\n WorkmaticClient,\n WorkmaticWorker,\n WorkmaticDashboard,\n DashboardMiddleware,\n WorkmaticDb,\n WorkmaticDatabase,\n WorkmaticJobsTable,\n} from './types.js';\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import Database from 'better-sqlite3';\nimport { Kysely, SqliteDialect } from 'kysely';\nimport type { WorkmaticDatabase, WorkmaticDb, DatabaseOptions } from './types.js';\n\n/**\n * Create and initialize the workmatic database\n * \n * @param options - Database options\n * @returns Kysely database instance\n * \n * @example\n * ```ts\n * // Using a file path\n * const db = createDatabase({ filename: './jobs.db' });\n * \n * // Using an existing better-sqlite3 instance\n * import Database from 'better-sqlite3';\n * const sqlite = new Database('./jobs.db');\n * const db = createDatabase({ db: sqlite });\n * \n * // In-memory database (for testing)\n * const db = createDatabase({ filename: ':memory:' });\n * ```\n */\nexport function createDatabase(options: DatabaseOptions = {}): WorkmaticDb {\n let sqliteDb: Database.Database;\n\n if (options.db) {\n sqliteDb = options.db;\n } else {\n const filename = options.filename ?? ':memory:';\n sqliteDb = new Database(filename);\n }\n\n // Set pragmas for performance and safety\n sqliteDb.pragma('journal_mode = WAL');\n sqliteDb.pragma('synchronous = NORMAL');\n sqliteDb.pragma('busy_timeout = 5000');\n\n // Create Kysely instance\n const db = new Kysely<WorkmaticDatabase>({\n dialect: new SqliteDialect({\n database: sqliteDb,\n }),\n });\n\n // Create schema synchronously using better-sqlite3 directly\n createSchema(sqliteDb);\n\n return db;\n}\n\n/**\n * Create the database schema if it doesn't exist\n */\nfunction createSchema(db: Database.Database): void {\n // Create the jobs table\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_jobs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n public_id TEXT UNIQUE NOT NULL,\n queue TEXT NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'ready',\n priority INTEGER NOT NULL DEFAULT 0,\n run_at INTEGER NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n lease_until INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n last_error TEXT\n )\n `);\n\n // Create composite index for efficient job claiming\n // (queue, status, run_at, priority, id)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_claim \n ON workmatic_jobs (queue, status, run_at, priority, id)\n `);\n\n // Create index for lease expiration checking\n // (status, lease_until)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_lease \n ON workmatic_jobs (status, lease_until)\n `);\n\n // Create settings table for queue-level settings (pause state, etc.)\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_settings (\n queue TEXT PRIMARY KEY,\n paused INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL\n )\n `);\n}\n\n/**\n * Get the underlying better-sqlite3 database instance from a Kysely instance\n * This is useful for advanced operations or testing\n */\nexport function getUnderlyingDb(db: WorkmaticDb): Database.Database {\n // Access the internal database through the dialect\n // This is a bit hacky but necessary for some operations\n const dialect = (db as any).getExecutor().adapter.db;\n return dialect;\n}\n","import { nanoid } from 'nanoid';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n ClientOptions,\n WorkmaticClient,\n AddJobOptions,\n AddJobResult,\n JobStats,\n JobStatus,\n} from './types.js';\nimport { validatePayload, now } from './utils.js';\n\n/**\n * Create a job queue client for adding jobs\n * \n * @param options - Client options\n * @returns Client instance\n * \n * @example\n * ```ts\n * const client = createClient({ db });\n * \n * // Add a simple job\n * const result = await client.add({ email: 'user@example.com' });\n * console.log(result.id); // Job public ID\n * \n * // Add a job with options\n * await client.add(\n * { userId: 123 },\n * { priority: 1, delayMs: 5000, maxAttempts: 5 }\n * );\n * \n * // Get queue statistics\n * const stats = await client.stats();\n * console.log(stats); // { ready: 5, running: 2, done: 100, ... }\n * ```\n */\nexport function createClient(options: ClientOptions): WorkmaticClient {\n const { db, queue = 'default' } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n return {\n /**\n * Add a job to the queue\n */\n async add<TPayload = unknown>(\n payload: TPayload,\n opts: AddJobOptions = {}\n ): Promise<AddJobResult> {\n const {\n priority = 0,\n delayMs = 0,\n maxAttempts = 3,\n } = opts;\n\n // Validate and serialize payload\n const payloadJson = validatePayload(payload);\n \n // Generate unique public ID\n const publicId = nanoid();\n \n const timestamp = now();\n const runAt = timestamp + delayMs;\n\n // Insert job\n await db\n .insertInto('workmatic_jobs')\n .values({\n public_id: publicId,\n queue,\n payload: payloadJson,\n status: 'ready',\n priority,\n run_at: runAt,\n attempts: 0,\n max_attempts: maxAttempts,\n lease_until: 0,\n created_at: timestamp,\n updated_at: timestamp,\n last_error: null,\n })\n .execute();\n\n return { ok: true, id: publicId };\n },\n\n /**\n * Get job statistics for the queue\n */\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n };\n}\n","import type { BackoffFunction } from './types.js';\n\n/**\n * Default exponential backoff function\n * Returns delay in milliseconds: 1000 * 2^attempts\n * \n * @param attempts - Number of failed attempts\n * @returns Delay in milliseconds\n * \n * @example\n * ```ts\n * defaultBackoff(0) // 1000ms (1 second)\n * defaultBackoff(1) // 2000ms (2 seconds)\n * defaultBackoff(2) // 4000ms (4 seconds)\n * defaultBackoff(3) // 8000ms (8 seconds)\n * ```\n */\nexport const defaultBackoff: BackoffFunction = (attempts: number): number => {\n return 1000 * Math.pow(2, attempts);\n};\n\n/**\n * Validate that a payload is JSON-serializable\n * \n * @param payload - The payload to validate\n * @throws Error if payload cannot be serialized to JSON\n * @returns The JSON string representation\n */\nexport function validatePayload(payload: unknown): string {\n try {\n return JSON.stringify(payload);\n } catch (error) {\n throw new Error(\n `Payload is not JSON-serializable: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Parse a JSON payload safely\n * \n * @param json - JSON string to parse\n * @returns Parsed payload\n * @throws Error if JSON is invalid\n */\nexport function parsePayload<T = unknown>(json: string): T {\n try {\n return JSON.parse(json) as T;\n } catch (error) {\n throw new Error(\n `Invalid JSON payload: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Sleep for a specified duration\n * \n * @param ms - Duration in milliseconds\n * @returns Promise that resolves after the duration\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Get current timestamp in milliseconds\n */\nexport function now(): number {\n return Date.now();\n}\n","import fastq from 'fastq';\nimport type { queueAsPromised } from 'fastq';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n WorkerOptions,\n WorkmaticWorker,\n JobProcessor,\n JobStats,\n JobStatus,\n Job,\n ClaimedJob,\n} from './types.js';\nimport { defaultBackoff, parsePayload, sleep, now } from './utils.js';\n\n/**\n * Create a job queue worker for processing jobs\n * \n * @param options - Worker options\n * @returns Worker instance\n * \n * @example\n * ```ts\n * const worker = createWorker({ db, concurrency: 4 });\n * \n * // Set the processor function\n * worker.process(async (job) => {\n * console.log('Processing job:', job.id);\n * await sendEmail(job.payload.email);\n * });\n * \n * // Start processing\n * worker.start();\n * \n * // Later, gracefully stop\n * await worker.stop();\n * ```\n */\nexport function createWorker(options: WorkerOptions): WorkmaticWorker {\n const {\n db,\n queue = 'default',\n concurrency = 1,\n leaseMs = 30000,\n pollMs = 1000,\n timeoutMs,\n backoff = defaultBackoff,\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // State\n let running = false;\n let paused = false;\n let processor: JobProcessor<any> | null = null;\n let pumpTimeout: NodeJS.Timeout | null = null;\n let fastqQueue: queueAsPromised<ClaimedJob, void> | null = null;\n\n /**\n * Requeue jobs with expired leases\n */\n async function requeueExpiredLeases(): Promise<number> {\n const timestamp = now();\n\n const result = await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n lease_until: 0,\n updated_at: timestamp,\n })\n .where('status', '=', 'running')\n .where('lease_until', '<', timestamp)\n .where('lease_until', '>', 0)\n .execute();\n\n return Number(result[0]?.numUpdatedRows ?? 0);\n }\n\n /**\n * Claim a batch of jobs for processing\n */\n async function claimBatch(limit: number): Promise<ClaimedJob[]> {\n const timestamp = now();\n const leaseUntil = timestamp + leaseMs;\n\n // Use a transaction to ensure atomic claim\n return await db.transaction().execute(async (trx) => {\n // Select eligible jobs\n const jobs = await trx\n .selectFrom('workmatic_jobs')\n .select(['id', 'public_id', 'queue', 'payload', 'attempts', 'max_attempts'])\n .where('queue', '=', queue)\n .where('status', '=', 'ready')\n .where('run_at', '<=', timestamp)\n .orderBy('priority', 'asc')\n .orderBy('id', 'asc')\n .limit(limit)\n .execute();\n\n if (jobs.length === 0) {\n return [];\n }\n\n // Get the IDs of claimed jobs\n const jobIds = jobs.map(j => j.id);\n\n // Update them to running status\n await trx\n .updateTable('workmatic_jobs')\n .set({\n status: 'running',\n lease_until: leaseUntil,\n updated_at: timestamp,\n })\n .where('id', 'in', jobIds)\n .execute();\n\n return jobs;\n });\n }\n\n /**\n * Mark a job as completed\n */\n async function markDone(jobId: number): Promise<void> {\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'done',\n lease_until: 0,\n updated_at: now(),\n })\n .where('id', '=', jobId)\n .execute();\n }\n\n /**\n * Handle job failure\n */\n async function markFailed(\n jobId: number,\n attempts: number,\n maxAttempts: number,\n error: Error\n ): Promise<void> {\n const timestamp = now();\n const newAttempts = attempts + 1;\n const errorMessage = error.message || String(error);\n\n if (newAttempts < maxAttempts) {\n // Retry: set back to ready with backoff delay\n const runAt = timestamp + backoff(newAttempts);\n \n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n attempts: newAttempts,\n run_at: runAt,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n } else {\n // Max attempts reached: mark as dead\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'dead',\n attempts: newAttempts,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n }\n }\n\n /**\n * Run a promise with timeout\n */\n async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n jobId: string\n ): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`Job ${jobId} timed out after ${ms}ms`));\n }, ms);\n });\n\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n }\n\n /**\n * Process a single job\n */\n async function processJob(claimedJob: ClaimedJob): Promise<void> {\n if (!processor) {\n throw new Error('No processor set');\n }\n\n // Parse payload and create job object\n const payload = parsePayload(claimedJob.payload);\n const job: Job = {\n id: claimedJob.public_id,\n queue: claimedJob.queue,\n payload,\n status: 'running',\n priority: 0, // Not needed for processing\n attempts: claimedJob.attempts,\n maxAttempts: claimedJob.max_attempts,\n createdAt: 0, // Not needed for processing\n lastError: null,\n };\n\n try {\n // Run processor with optional timeout\n if (timeoutMs) {\n await withTimeout(processor(job), timeoutMs, job.id);\n } else {\n await processor(job);\n }\n await markDone(claimedJob.id);\n } catch (error) {\n await markFailed(\n claimedJob.id,\n claimedJob.attempts,\n claimedJob.max_attempts,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Check if queue is paused in database (for CLI control)\n */\n async function isQueuePausedInDb(): Promise<boolean> {\n const setting = await db\n .selectFrom('workmatic_settings')\n .select('paused')\n .where('queue', '=', queue)\n .executeTakeFirst();\n \n return setting?.paused === 1;\n }\n\n /**\n * Main pump loop\n */\n async function pump(): Promise<void> {\n if (!running) {\n return;\n }\n\n // Check both in-memory pause and database pause (for CLI control)\n if (paused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n try {\n // Check database pause state (allows CLI to pause running workers)\n const dbPaused = await isQueuePausedInDb();\n if (dbPaused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n // Requeue expired leases\n await requeueExpiredLeases();\n\n // Claim a batch of jobs\n const batchSize = concurrency * 2;\n const jobs = await claimBatch(batchSize);\n\n if (jobs.length > 0) {\n // Push jobs into fastq\n for (const job of jobs) {\n fastqQueue!.push(job);\n }\n // Immediately pump again\n pumpTimeout = setTimeout(pump, 0);\n } else {\n // No jobs, wait before polling again\n pumpTimeout = setTimeout(pump, pollMs);\n }\n } catch (error) {\n // Log error and continue\n console.error('[workmatic] Pump error:', error);\n pumpTimeout = setTimeout(pump, pollMs);\n }\n }\n\n // Create the worker object\n const worker: WorkmaticWorker = {\n process<TPayload = unknown>(fn: JobProcessor<TPayload>): void {\n processor = fn as JobProcessor<any>;\n },\n\n start(): void {\n if (running) {\n return;\n }\n\n if (!processor) {\n throw new Error('No processor set. Call process() before start()');\n }\n\n running = true;\n paused = false;\n\n // Create fastq queue\n fastqQueue = fastq.promise(processJob, concurrency);\n\n // Start pump loop\n pump();\n },\n\n async stop(): Promise<void> {\n if (!running) {\n return;\n }\n\n running = false;\n\n // Clear pump timeout\n if (pumpTimeout) {\n clearTimeout(pumpTimeout);\n pumpTimeout = null;\n }\n\n // Wait for fastq to drain\n if (fastqQueue) {\n await fastqQueue.drained();\n fastqQueue = null;\n }\n },\n\n pause(): void {\n paused = true;\n },\n\n resume(): void {\n paused = false;\n },\n\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n\n get isRunning(): boolean {\n return running;\n },\n\n get isPaused(): boolean {\n return paused;\n },\n\n get queue(): string {\n return queue;\n },\n };\n\n return worker;\n}\n","import { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { readFile } from 'fs/promises';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n DashboardOptions,\n DashboardMiddlewareOptions,\n WorkmaticDashboard,\n WorkmaticWorker,\n DashboardMiddleware,\n JobStats,\n JobStatus,\n} from './types.js';\n\n// Get the directory of this module for static file serving\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Content types for static files\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n};\n\n/**\n * Create the core request handler (shared between standalone and middleware)\n */\nfunction createRequestHandler(\n db: WorkmaticDb,\n workerMap: Map<string, WorkmaticWorker>,\n basePath: string = ''\n) {\n /**\n * Send JSON response\n */\n function sendJson(res: ServerResponse, data: unknown, status = 200): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n }\n\n /**\n * Send error response\n */\n function sendError(res: ServerResponse, message: string, status = 500): void {\n sendJson(res, { error: message }, status);\n }\n\n /**\n * Parse URL query parameters\n */\n function parseQuery(url: string): URLSearchParams {\n const queryIndex = url.indexOf('?');\n if (queryIndex === -1) return new URLSearchParams();\n return new URLSearchParams(url.slice(queryIndex + 1));\n }\n\n /**\n * Get path from URL (removing basePath prefix)\n */\n function getPath(url: string): string {\n const queryIndex = url.indexOf('?');\n let path = queryIndex === -1 ? url : url.slice(0, queryIndex);\n \n // Remove basePath prefix if present\n if (basePath && path.startsWith(basePath)) {\n path = path.slice(basePath.length) || '/';\n }\n \n return path;\n }\n\n /**\n * API: Get jobs list\n */\n async function handleGetJobs(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n const statusFilter = query.get('status') as JobStatus | null;\n const limit = Math.min(parseInt(query.get('limit') || '50', 10), 100);\n const offset = parseInt(query.get('offset') || '0', 10);\n\n let queryBuilder = db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .orderBy('created_at', 'desc')\n .limit(limit)\n .offset(offset);\n\n if (queueFilter) {\n queryBuilder = queryBuilder.where('queue', '=', queueFilter);\n }\n if (statusFilter) {\n queryBuilder = queryBuilder.where('status', '=', statusFilter);\n }\n\n const jobs = await queryBuilder.execute();\n\n // Transform to API format\n const apiJobs = jobs.map(job => ({\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n }));\n\n sendJson(res, { jobs: apiJobs, limit, offset });\n }\n\n /**\n * API: Get statistics\n */\n async function handleGetStats(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n\n // Get counts by status\n let statsQuery = db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .groupBy('status');\n\n if (queueFilter) {\n statsQuery = statsQuery.where('queue', '=', queueFilter);\n }\n\n const statusCounts = await statsQuery.execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of statusCounts) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n // Get queue list\n const queuesResult = await db\n .selectFrom('workmatic_jobs')\n .select('queue')\n .groupBy('queue')\n .execute();\n\n const queues = queuesResult.map(q => q.queue);\n\n // Get worker status\n const workersStatus = Array.from(workerMap.entries()).map(([queue, worker]) => ({\n queue,\n running: worker.isRunning,\n paused: worker.isPaused,\n }));\n\n sendJson(res, { stats, queues, workers: workersStatus });\n }\n\n /**\n * API: Get single job by ID\n */\n async function handleGetJob(req: IncomingMessage, res: ServerResponse, publicId: string): Promise<void> {\n const job = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'lease_until',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .where('public_id', '=', publicId)\n .executeTakeFirst();\n\n if (!job) {\n return sendError(res, 'Job not found', 404);\n }\n\n sendJson(res, {\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n leaseUntil: job.lease_until,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n });\n }\n\n /**\n * API: Pause worker\n */\n async function handlePauseWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.pause();\n sendJson(res, { ok: true, queue, paused: true });\n }\n\n /**\n * API: Resume worker\n */\n async function handleResumeWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.resume();\n sendJson(res, { ok: true, queue, paused: false });\n }\n\n /**\n * Serve static files from the dashboard directory\n */\n async function serveStatic(res: ServerResponse, filePath: string): Promise<void> {\n // Navigate from dist/dashboard.js to dashboard/ directory\n const dashboardDir = join(__dirname, '..', 'dashboard');\n const fullPath = join(dashboardDir, filePath);\n \n // Determine content type\n const ext = filePath.substring(filePath.lastIndexOf('.')) || '.html';\n const contentType = CONTENT_TYPES[ext] || 'application/octet-stream';\n\n try {\n const content = await readFile(fullPath, 'utf-8');\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(content);\n } catch (error) {\n sendError(res, 'Not found', 404);\n }\n }\n\n /**\n * Main request handler\n */\n return async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void\n ): Promise<boolean> {\n const path = getPath(req.url || '/');\n\n // Check if this request is for the dashboard\n const fullUrl = req.url || '/';\n if (basePath && !fullUrl.startsWith(basePath)) {\n // Not our route, call next middleware if available\n if (next) next();\n return false;\n }\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return true;\n }\n\n try {\n // API routes\n if (path === '/api/jobs' && req.method === 'GET') {\n await handleGetJobs(req, res);\n return true;\n }\n\n if (path === '/api/stats' && req.method === 'GET') {\n await handleGetStats(req, res);\n return true;\n }\n\n // Single job: /api/jobs/:id\n const jobMatch = path.match(/^\\/api\\/jobs\\/([^/]+)$/);\n if (jobMatch && req.method === 'GET') {\n await handleGetJob(req, res, jobMatch[1]);\n return true;\n }\n\n // Pause worker: /api/workers/:queue/pause\n const pauseMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/pause$/);\n if (pauseMatch && req.method === 'POST') {\n await handlePauseWorker(res, pauseMatch[1]);\n return true;\n }\n\n // Resume worker: /api/workers/:queue/resume\n const resumeMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/resume$/);\n if (resumeMatch && req.method === 'POST') {\n await handleResumeWorker(res, resumeMatch[1]);\n return true;\n }\n\n // Static files\n if (path === '/' || path === '/index.html') {\n await serveStatic(res, 'index.html');\n return true;\n }\n if (path === '/style.css') {\n await serveStatic(res, 'style.css');\n return true;\n }\n if (path === '/app.js') {\n await serveStatic(res, 'app.js');\n return true;\n }\n\n // 404 for unknown routes under our basePath\n sendError(res, 'Not found', 404);\n return true;\n } catch (error) {\n console.error('[workmatic] Dashboard error:', error);\n sendError(res, 'Internal server error', 500);\n return true;\n }\n };\n}\n\n/**\n * Create a dashboard HTTP server for monitoring and controlling the job queue\n * \n * @param options - Dashboard options\n * @returns Dashboard instance\n * \n * @example\n * ```ts\n * const dashboard = createDashboard({ \n * db, \n * port: 3000,\n * workers: [worker1, worker2] \n * });\n * \n * console.log(`Dashboard running at http://localhost:${dashboard.port}`);\n * \n * // Later, close the server\n * await dashboard.close();\n * ```\n */\nexport function createDashboard(options: DashboardOptions): WorkmaticDashboard {\n const {\n db,\n port = 3000,\n workers = [],\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap);\n\n // Create and start HTTP server\n const server = createServer((req, res) => {\n handleRequest(req, res).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n });\n });\n\n server.listen(port);\n\n return {\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n get port(): number {\n return port;\n },\n };\n}\n\n/**\n * Create an Express-compatible middleware for the dashboard\n * \n * @param options - Middleware options\n * @returns Express middleware function\n * \n * @example\n * ```ts\n * import express from 'express';\n * import { createDashboardMiddleware } from 'workmatic';\n * \n * const app = express();\n * \n * // Mount dashboard at /workmatic\n * app.use(createDashboardMiddleware({ \n * db,\n * basePath: '/workmatic',\n * workers: [worker] \n * }));\n * \n * app.listen(3000);\n * // Dashboard available at http://localhost:3000/workmatic\n * ```\n */\nexport function createDashboardMiddleware(options: DashboardMiddlewareOptions): DashboardMiddleware {\n const {\n db,\n workers = [],\n basePath = '',\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap, basePath);\n\n return (req, res, next) => {\n handleRequest(req, res, next).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n if (next) {\n next();\n } else {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n };\n}\n"],"mappings":"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","names":["Database","import_kysely","import_kysely","fastq","import_kysely","__filename"]}
1
+ {"version":3,"sources":["../src/index.ts","../node_modules/tsup/assets/cjs_shims.js","../src/database.ts","../src/client.ts","../src/utils.ts","../src/worker.ts","../src/dashboard.ts"],"sourcesContent":["/**\n * Workmatic - A persistent job queue for Node.js\n * \n * @packageDocumentation\n */\n\n// Main exports\nexport { createDatabase } from './database.js';\nexport { createClient } from './client.js';\nexport { createWorker } from './worker.js';\nexport { createDashboard, createDashboardMiddleware } from './dashboard.js';\n\n// Utility exports\nexport { defaultBackoff, validatePayload } from './utils.js';\n\n// Type exports\nexport type {\n // Core types\n Job,\n JobStatus,\n JobStats,\n ClaimedJob,\n WorkerState,\n \n // Options\n AddJobOptions,\n AddJobResult,\n DatabaseOptions,\n ClientOptions,\n WorkerOptions,\n DashboardOptions,\n DashboardMiddlewareOptions,\n BackoffFunction,\n JobProcessor,\n \n // Interfaces\n WorkmaticClient,\n WorkmaticWorker,\n WorkmaticDashboard,\n DashboardMiddleware,\n WorkmaticDb,\n WorkmaticDatabase,\n WorkmaticJobsTable,\n} from './types.js';\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import Database from 'better-sqlite3';\nimport { Kysely, SqliteDialect } from 'kysely';\nimport type { WorkmaticDatabase, WorkmaticDb, DatabaseOptions } from './types.js';\n\n/**\n * Create and initialize the workmatic database\n * \n * @param options - Database options\n * @returns Kysely database instance\n * \n * @example\n * ```ts\n * // Using a file path\n * const db = createDatabase({ filename: './jobs.db' });\n * \n * // Using an existing better-sqlite3 instance\n * import Database from 'better-sqlite3';\n * const sqlite = new Database('./jobs.db');\n * const db = createDatabase({ db: sqlite });\n * \n * // In-memory database (for testing)\n * const db = createDatabase({ filename: ':memory:' });\n * ```\n */\nexport function createDatabase(options: DatabaseOptions = {}): WorkmaticDb {\n let sqliteDb: Database.Database;\n\n if (options.db) {\n sqliteDb = options.db;\n } else {\n const filename = options.filename ?? ':memory:';\n sqliteDb = new Database(filename);\n }\n\n // Set pragmas for performance and safety\n sqliteDb.pragma('journal_mode = WAL');\n sqliteDb.pragma('synchronous = NORMAL');\n sqliteDb.pragma('busy_timeout = 5000');\n\n // Create Kysely instance\n const db = new Kysely<WorkmaticDatabase>({\n dialect: new SqliteDialect({\n database: sqliteDb,\n }),\n });\n\n // Create schema synchronously using better-sqlite3 directly\n createSchema(sqliteDb);\n\n return db;\n}\n\n/**\n * Create the database schema if it doesn't exist\n */\nfunction createSchema(db: Database.Database): void {\n // Create the jobs table\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_jobs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n public_id TEXT UNIQUE NOT NULL,\n queue TEXT NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'ready',\n priority INTEGER NOT NULL DEFAULT 0,\n run_at INTEGER NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n lease_until INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n last_error TEXT\n )\n `);\n\n // Create composite index for efficient job claiming\n // (queue, status, run_at, priority, id)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_claim \n ON workmatic_jobs (queue, status, run_at, priority, id)\n `);\n\n // Create index for lease expiration checking\n // (status, lease_until)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_lease \n ON workmatic_jobs (status, lease_until)\n `);\n\n // Create settings table for queue-level settings (pause state, etc.)\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_settings (\n queue TEXT PRIMARY KEY,\n paused INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL\n )\n `);\n}\n\n/**\n * Get the underlying better-sqlite3 database instance from a Kysely instance\n * This is useful for advanced operations or testing\n */\nexport function getUnderlyingDb(db: WorkmaticDb): Database.Database {\n // Access the internal database through the dialect\n // This is a bit hacky but necessary for some operations\n const dialect = (db as any).getExecutor().adapter.db;\n return dialect;\n}\n","import { nanoid } from 'nanoid';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n ClientOptions,\n WorkmaticClient,\n AddJobOptions,\n AddJobResult,\n JobStats,\n JobStatus,\n} from './types.js';\nimport { validatePayload, now } from './utils.js';\n\n/**\n * Create a job queue client for adding jobs\n * \n * @param options - Client options\n * @returns Client instance\n * \n * @example\n * ```ts\n * const client = createClient({ db });\n * \n * // Add a simple job\n * const result = await client.add({ email: 'user@example.com' });\n * console.log(result.id); // Job public ID\n * \n * // Add a job with options\n * await client.add(\n * { userId: 123 },\n * { priority: 1, delayMs: 5000, maxAttempts: 5 }\n * );\n * \n * // Get queue statistics\n * const stats = await client.stats();\n * console.log(stats); // { ready: 5, running: 2, done: 100, ... }\n * ```\n */\nexport function createClient(options: ClientOptions): WorkmaticClient {\n const { db, queue = 'default' } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n return {\n /**\n * Add a job to the queue\n */\n async add<TPayload = unknown>(\n payload: TPayload,\n opts: AddJobOptions = {}\n ): Promise<AddJobResult> {\n const {\n priority = 0,\n delayMs = 0,\n maxAttempts = 3,\n } = opts;\n\n // Validate and serialize payload\n const payloadJson = validatePayload(payload);\n \n // Generate unique public ID\n const publicId = nanoid();\n \n const timestamp = now();\n const runAt = timestamp + delayMs;\n\n // Insert job\n await db\n .insertInto('workmatic_jobs')\n .values({\n public_id: publicId,\n queue,\n payload: payloadJson,\n status: 'ready',\n priority,\n run_at: runAt,\n attempts: 0,\n max_attempts: maxAttempts,\n lease_until: 0,\n created_at: timestamp,\n updated_at: timestamp,\n last_error: null,\n })\n .execute();\n\n return { ok: true, id: publicId };\n },\n\n /**\n * Get job statistics for the queue\n */\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n\n /**\n * Clear all jobs from the queue\n */\n async clear(options: { status?: JobStatus } = {}): Promise<number> {\n let query = db\n .deleteFrom('workmatic_jobs')\n .where('queue', '=', queue);\n\n if (options.status) {\n query = query.where('status', '=', options.status);\n }\n\n const result = await query.execute();\n return Number(result[0]?.numDeletedRows ?? 0);\n },\n };\n}\n","import type { BackoffFunction } from './types.js';\n\n/**\n * Default exponential backoff function\n * Returns delay in milliseconds: 1000 * 2^attempts\n * \n * @param attempts - Number of failed attempts\n * @returns Delay in milliseconds\n * \n * @example\n * ```ts\n * defaultBackoff(0) // 1000ms (1 second)\n * defaultBackoff(1) // 2000ms (2 seconds)\n * defaultBackoff(2) // 4000ms (4 seconds)\n * defaultBackoff(3) // 8000ms (8 seconds)\n * ```\n */\nexport const defaultBackoff: BackoffFunction = (attempts: number): number => {\n return 1000 * Math.pow(2, attempts);\n};\n\n/**\n * Validate that a payload is JSON-serializable\n * \n * @param payload - The payload to validate\n * @throws Error if payload cannot be serialized to JSON\n * @returns The JSON string representation\n */\nexport function validatePayload(payload: unknown): string {\n try {\n return JSON.stringify(payload);\n } catch (error) {\n throw new Error(\n `Payload is not JSON-serializable: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Parse a JSON payload safely\n * \n * @param json - JSON string to parse\n * @returns Parsed payload\n * @throws Error if JSON is invalid\n */\nexport function parsePayload<T = unknown>(json: string): T {\n try {\n return JSON.parse(json) as T;\n } catch (error) {\n throw new Error(\n `Invalid JSON payload: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Sleep for a specified duration\n * \n * @param ms - Duration in milliseconds\n * @returns Promise that resolves after the duration\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Get current timestamp in milliseconds\n */\nexport function now(): number {\n return Date.now();\n}\n","import fastq from 'fastq';\nimport type { queueAsPromised } from 'fastq';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n WorkerOptions,\n WorkmaticWorker,\n JobProcessor,\n JobStats,\n JobStatus,\n Job,\n ClaimedJob,\n WorkerState,\n} from './types.js';\nimport { defaultBackoff, parsePayload, sleep, now } from './utils.js';\n\n/**\n * Create a job queue worker for processing jobs\n * \n * @param options - Worker options\n * @returns Worker instance\n * \n * @example\n * ```ts\n * const worker = createWorker({ db, concurrency: 4 });\n * \n * // Set the processor function\n * worker.process(async (job) => {\n * console.log('Processing job:', job.id);\n * await sendEmail(job.payload.email);\n * });\n * \n * // Start processing\n * worker.start();\n * \n * // Later, gracefully stop\n * await worker.stop();\n * ```\n */\nexport function createWorker(options: WorkerOptions): WorkmaticWorker {\n const {\n db,\n queue = 'default',\n concurrency = 1,\n leaseMs = 30000,\n pollMs = 1000,\n timeoutMs,\n backoff = defaultBackoff,\n persistState = false,\n autoRestore = true,\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // State\n let running = false;\n let paused = false;\n let processor: JobProcessor<any> | null = null;\n let pumpTimeout: NodeJS.Timeout | null = null;\n let fastqQueue: queueAsPromised<ClaimedJob, void> | null = null;\n\n /**\n * Get the settings key for this worker's state\n */\n function getStateKey(): string {\n return `worker_state_${queue}`;\n }\n\n /**\n * Save worker state to database\n */\n async function saveState(state: WorkerState): Promise<void> {\n if (!persistState) return;\n\n const timestamp = now();\n const key = getStateKey();\n\n // Ensure settings table exists\n await db.schema\n .createTable('workmatic_settings')\n .ifNotExists()\n .addColumn('queue', 'text', (col) => col.primaryKey())\n .addColumn('paused', 'integer', (col) => col.notNull().defaultTo(0))\n .addColumn('updated_at', 'integer', (col) => col.notNull())\n .execute()\n .catch(() => {}); // Ignore if exists\n\n // Use raw SQL for upsert since different SQLite versions have different syntax\n await sql`\n INSERT INTO workmatic_settings (queue, paused, updated_at)\n VALUES (${key}, ${state === 'paused' ? 1 : state === 'running' ? 2 : 0}, ${timestamp})\n ON CONFLICT(queue) DO UPDATE SET \n paused = ${state === 'paused' ? 1 : state === 'running' ? 2 : 0},\n updated_at = ${timestamp}\n `.execute(db);\n }\n\n /**\n * Load worker state from database\n */\n async function loadState(): Promise<WorkerState | null> {\n if (!persistState) return null;\n\n try {\n const key = getStateKey();\n const result = await db\n .selectFrom('workmatic_settings')\n .select('paused')\n .where('queue', '=', key)\n .executeTakeFirst();\n\n if (!result) return null;\n \n // paused: 0 = stopped, 1 = paused, 2 = running\n if (result.paused === 2) return 'running';\n if (result.paused === 1) return 'paused';\n return 'stopped';\n } catch {\n return null;\n }\n }\n\n /**\n * Requeue jobs with expired leases\n */\n async function requeueExpiredLeases(): Promise<number> {\n const timestamp = now();\n\n const result = await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n lease_until: 0,\n updated_at: timestamp,\n })\n .where('status', '=', 'running')\n .where('lease_until', '<', timestamp)\n .where('lease_until', '>', 0)\n .execute();\n\n return Number(result[0]?.numUpdatedRows ?? 0);\n }\n\n /**\n * Claim a batch of jobs for processing\n */\n async function claimBatch(limit: number): Promise<ClaimedJob[]> {\n const timestamp = now();\n const leaseUntil = timestamp + leaseMs;\n\n // Use a transaction to ensure atomic claim\n return await db.transaction().execute(async (trx) => {\n // Select eligible jobs\n const jobs = await trx\n .selectFrom('workmatic_jobs')\n .select(['id', 'public_id', 'queue', 'payload', 'attempts', 'max_attempts'])\n .where('queue', '=', queue)\n .where('status', '=', 'ready')\n .where('run_at', '<=', timestamp)\n .orderBy('priority', 'asc')\n .orderBy('id', 'asc')\n .limit(limit)\n .execute();\n\n if (jobs.length === 0) {\n return [];\n }\n\n // Get the IDs of claimed jobs\n const jobIds = jobs.map(j => j.id);\n\n // Update them to running status\n await trx\n .updateTable('workmatic_jobs')\n .set({\n status: 'running',\n lease_until: leaseUntil,\n updated_at: timestamp,\n })\n .where('id', 'in', jobIds)\n .execute();\n\n return jobs;\n });\n }\n\n /**\n * Mark a job as completed\n */\n async function markDone(jobId: number): Promise<void> {\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'done',\n lease_until: 0,\n updated_at: now(),\n })\n .where('id', '=', jobId)\n .execute();\n }\n\n /**\n * Handle job failure\n */\n async function markFailed(\n jobId: number,\n attempts: number,\n maxAttempts: number,\n error: Error\n ): Promise<void> {\n const timestamp = now();\n const newAttempts = attempts + 1;\n const errorMessage = error.message || String(error);\n\n if (newAttempts < maxAttempts) {\n // Retry: set back to ready with backoff delay\n const runAt = timestamp + backoff(newAttempts);\n \n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n attempts: newAttempts,\n run_at: runAt,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n } else {\n // Max attempts reached: mark as dead\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'dead',\n attempts: newAttempts,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n }\n }\n\n /**\n * Run a promise with timeout\n */\n async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n jobId: string\n ): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`Job ${jobId} timed out after ${ms}ms`));\n }, ms);\n });\n\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n }\n\n /**\n * Process a single job\n */\n async function processJob(claimedJob: ClaimedJob): Promise<void> {\n if (!processor) {\n throw new Error('No processor set');\n }\n\n // Parse payload and create job object\n const payload = parsePayload(claimedJob.payload);\n const job: Job = {\n id: claimedJob.public_id,\n queue: claimedJob.queue,\n payload,\n status: 'running',\n priority: 0, // Not needed for processing\n attempts: claimedJob.attempts,\n maxAttempts: claimedJob.max_attempts,\n createdAt: 0, // Not needed for processing\n lastError: null,\n };\n\n try {\n // Run processor with optional timeout\n if (timeoutMs) {\n await withTimeout(processor(job), timeoutMs, job.id);\n } else {\n await processor(job);\n }\n await markDone(claimedJob.id);\n } catch (error) {\n await markFailed(\n claimedJob.id,\n claimedJob.attempts,\n claimedJob.max_attempts,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Check if queue is paused in database (for CLI control)\n */\n async function isQueuePausedInDb(): Promise<boolean> {\n const setting = await db\n .selectFrom('workmatic_settings')\n .select('paused')\n .where('queue', '=', queue)\n .executeTakeFirst();\n \n return setting?.paused === 1;\n }\n\n /**\n * Main pump loop\n */\n async function pump(): Promise<void> {\n if (!running) {\n return;\n }\n\n // Check both in-memory pause and database pause (for CLI control)\n if (paused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n try {\n // Check database pause state (allows CLI to pause running workers)\n const dbPaused = await isQueuePausedInDb();\n if (dbPaused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n // Requeue expired leases\n await requeueExpiredLeases();\n\n // Claim a batch of jobs\n const batchSize = concurrency * 2;\n const jobs = await claimBatch(batchSize);\n\n if (jobs.length > 0) {\n // Push jobs into fastq\n for (const job of jobs) {\n fastqQueue!.push(job);\n }\n // Immediately pump again\n pumpTimeout = setTimeout(pump, 0);\n } else {\n // No jobs, wait before polling again\n pumpTimeout = setTimeout(pump, pollMs);\n }\n } catch (error) {\n // Log error and continue\n console.error('[workmatic] Pump error:', error);\n pumpTimeout = setTimeout(pump, pollMs);\n }\n }\n\n // Create the worker object\n const worker: WorkmaticWorker = {\n process<TPayload = unknown>(fn: JobProcessor<TPayload>): void {\n processor = fn as JobProcessor<any>;\n },\n\n start(): void {\n if (running) {\n return;\n }\n\n if (!processor) {\n throw new Error('No processor set. Call process() before start()');\n }\n\n running = true;\n paused = false;\n\n // Create fastq queue\n fastqQueue = fastq.promise(processJob, concurrency);\n\n // Save state to database\n saveState('running').catch(() => {});\n\n // Start pump loop\n pump();\n },\n\n async stop(): Promise<void> {\n if (!running) {\n return;\n }\n\n running = false;\n\n // Clear pump timeout\n if (pumpTimeout) {\n clearTimeout(pumpTimeout);\n pumpTimeout = null;\n }\n\n // Wait for fastq to drain\n if (fastqQueue) {\n await fastqQueue.drained();\n fastqQueue = null;\n }\n\n // Save state to database\n await saveState('stopped');\n },\n\n pause(): void {\n paused = true;\n saveState('paused').catch(() => {});\n },\n\n resume(): void {\n paused = false;\n saveState('running').catch(() => {});\n },\n\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n\n get isRunning(): boolean {\n return running;\n },\n\n get isPaused(): boolean {\n return paused;\n },\n\n get queue(): string {\n return queue;\n },\n\n async restoreState(): Promise<WorkerState | null> {\n const state = await loadState();\n if (state === 'running' && processor) {\n this.start();\n } else if (state === 'paused' && processor) {\n this.start();\n this.pause();\n }\n return state;\n },\n\n async clear(options: { status?: JobStatus } = {}): Promise<number> {\n let query = db\n .deleteFrom('workmatic_jobs')\n .where('queue', '=', queue);\n\n if (options.status) {\n query = query.where('status', '=', options.status);\n }\n\n const result = await query.execute();\n return Number(result[0]?.numDeletedRows ?? 0);\n },\n };\n\n // Auto-restore state if enabled\n if (persistState && autoRestore) {\n // Use setImmediate to allow processor to be set first\n setImmediate(async () => {\n if (processor) {\n await worker.restoreState();\n }\n });\n }\n\n return worker;\n}\n","import { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { readFile } from 'fs/promises';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n DashboardOptions,\n DashboardMiddlewareOptions,\n WorkmaticDashboard,\n WorkmaticWorker,\n DashboardMiddleware,\n JobStats,\n JobStatus,\n} from './types.js';\n\n// Get the directory of this module for static file serving\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Content types for static files\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n};\n\n/**\n * Create the core request handler (shared between standalone and middleware)\n */\nfunction createRequestHandler(\n db: WorkmaticDb,\n workerMap: Map<string, WorkmaticWorker>,\n basePath: string = ''\n) {\n /**\n * Send JSON response\n */\n function sendJson(res: ServerResponse, data: unknown, status = 200): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n }\n\n /**\n * Send error response\n */\n function sendError(res: ServerResponse, message: string, status = 500): void {\n sendJson(res, { error: message }, status);\n }\n\n /**\n * Parse URL query parameters\n */\n function parseQuery(url: string): URLSearchParams {\n const queryIndex = url.indexOf('?');\n if (queryIndex === -1) return new URLSearchParams();\n return new URLSearchParams(url.slice(queryIndex + 1));\n }\n\n /**\n * Get path from URL (removing basePath prefix)\n */\n function getPath(url: string): string {\n const queryIndex = url.indexOf('?');\n let path = queryIndex === -1 ? url : url.slice(0, queryIndex);\n \n // Remove basePath prefix if present\n if (basePath && path.startsWith(basePath)) {\n path = path.slice(basePath.length) || '/';\n }\n \n return path;\n }\n\n /**\n * API: Get jobs list\n */\n async function handleGetJobs(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n const statusFilter = query.get('status') as JobStatus | null;\n const limit = Math.min(parseInt(query.get('limit') || '50', 10), 100);\n const offset = parseInt(query.get('offset') || '0', 10);\n\n let queryBuilder = db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .orderBy('created_at', 'desc')\n .limit(limit)\n .offset(offset);\n\n if (queueFilter) {\n queryBuilder = queryBuilder.where('queue', '=', queueFilter);\n }\n if (statusFilter) {\n queryBuilder = queryBuilder.where('status', '=', statusFilter);\n }\n\n const jobs = await queryBuilder.execute();\n\n // Transform to API format\n const apiJobs = jobs.map(job => ({\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n }));\n\n sendJson(res, { jobs: apiJobs, limit, offset });\n }\n\n /**\n * API: Get statistics\n */\n async function handleGetStats(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n\n // Get counts by status\n let statsQuery = db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .groupBy('status');\n\n if (queueFilter) {\n statsQuery = statsQuery.where('queue', '=', queueFilter);\n }\n\n const statusCounts = await statsQuery.execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of statusCounts) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n // Get queue list\n const queuesResult = await db\n .selectFrom('workmatic_jobs')\n .select('queue')\n .groupBy('queue')\n .execute();\n\n const queues = queuesResult.map(q => q.queue);\n\n // Get worker status\n const workersStatus = Array.from(workerMap.entries()).map(([queue, worker]) => ({\n queue,\n running: worker.isRunning,\n paused: worker.isPaused,\n }));\n\n sendJson(res, { stats, queues, workers: workersStatus });\n }\n\n /**\n * API: Get single job by ID\n */\n async function handleGetJob(req: IncomingMessage, res: ServerResponse, publicId: string): Promise<void> {\n const job = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'lease_until',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .where('public_id', '=', publicId)\n .executeTakeFirst();\n\n if (!job) {\n return sendError(res, 'Job not found', 404);\n }\n\n sendJson(res, {\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n leaseUntil: job.lease_until,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n });\n }\n\n /**\n * API: Pause worker\n */\n async function handlePauseWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.pause();\n sendJson(res, { ok: true, queue, paused: true });\n }\n\n /**\n * API: Resume worker\n */\n async function handleResumeWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.resume();\n sendJson(res, { ok: true, queue, paused: false });\n }\n\n /**\n * Serve static files from the dashboard directory\n */\n async function serveStatic(res: ServerResponse, filePath: string): Promise<void> {\n // Navigate from dist/dashboard.js to dashboard/ directory\n const dashboardDir = join(__dirname, '..', 'dashboard');\n const fullPath = join(dashboardDir, filePath);\n \n // Determine content type\n const ext = filePath.substring(filePath.lastIndexOf('.')) || '.html';\n const contentType = CONTENT_TYPES[ext] || 'application/octet-stream';\n\n try {\n const content = await readFile(fullPath, 'utf-8');\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(content);\n } catch (error) {\n sendError(res, 'Not found', 404);\n }\n }\n\n /**\n * Main request handler\n */\n return async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void\n ): Promise<boolean> {\n const path = getPath(req.url || '/');\n\n // Check if this request is for the dashboard\n const fullUrl = req.url || '/';\n if (basePath && !fullUrl.startsWith(basePath)) {\n // Not our route, call next middleware if available\n if (next) next();\n return false;\n }\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return true;\n }\n\n try {\n // API routes\n if (path === '/api/jobs' && req.method === 'GET') {\n await handleGetJobs(req, res);\n return true;\n }\n\n if (path === '/api/stats' && req.method === 'GET') {\n await handleGetStats(req, res);\n return true;\n }\n\n // Single job: /api/jobs/:id\n const jobMatch = path.match(/^\\/api\\/jobs\\/([^/]+)$/);\n if (jobMatch && req.method === 'GET') {\n await handleGetJob(req, res, jobMatch[1]);\n return true;\n }\n\n // Pause worker: /api/workers/:queue/pause\n const pauseMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/pause$/);\n if (pauseMatch && req.method === 'POST') {\n await handlePauseWorker(res, pauseMatch[1]);\n return true;\n }\n\n // Resume worker: /api/workers/:queue/resume\n const resumeMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/resume$/);\n if (resumeMatch && req.method === 'POST') {\n await handleResumeWorker(res, resumeMatch[1]);\n return true;\n }\n\n // Static files\n if (path === '/' || path === '/index.html') {\n await serveStatic(res, 'index.html');\n return true;\n }\n if (path === '/style.css') {\n await serveStatic(res, 'style.css');\n return true;\n }\n if (path === '/app.js') {\n await serveStatic(res, 'app.js');\n return true;\n }\n\n // 404 for unknown routes under our basePath\n sendError(res, 'Not found', 404);\n return true;\n } catch (error) {\n console.error('[workmatic] Dashboard error:', error);\n sendError(res, 'Internal server error', 500);\n return true;\n }\n };\n}\n\n/**\n * Create a dashboard HTTP server for monitoring and controlling the job queue\n * \n * @param options - Dashboard options\n * @returns Dashboard instance\n * \n * @example\n * ```ts\n * const dashboard = createDashboard({ \n * db, \n * port: 3000,\n * workers: [worker1, worker2] \n * });\n * \n * console.log(`Dashboard running at http://localhost:${dashboard.port}`);\n * \n * // Later, close the server\n * await dashboard.close();\n * ```\n */\nexport function createDashboard(options: DashboardOptions): WorkmaticDashboard {\n const {\n db,\n port = 3000,\n workers = [],\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap);\n\n // Create and start HTTP server\n const server = createServer((req, res) => {\n handleRequest(req, res).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n });\n });\n\n server.listen(port);\n\n return {\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n get port(): number {\n return port;\n },\n };\n}\n\n/**\n * Create an Express-compatible middleware for the dashboard\n * \n * @param options - Middleware options\n * @returns Express middleware function\n * \n * @example\n * ```ts\n * import express from 'express';\n * import { createDashboardMiddleware } from 'workmatic';\n * \n * const app = express();\n * \n * // Mount dashboard at /workmatic\n * app.use(createDashboardMiddleware({ \n * db,\n * basePath: '/workmatic',\n * workers: [worker] \n * }));\n * \n * app.listen(3000);\n * // Dashboard available at http://localhost:3000/workmatic\n * ```\n */\nexport function createDashboardMiddleware(options: DashboardMiddlewareOptions): DashboardMiddleware {\n const {\n db,\n workers = [],\n basePath = '',\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap, basePath);\n\n return (req, res, next) => {\n handleRequest(req, res, next).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n if (next) {\n next();\n } else {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n };\n}\n"],"mappings":"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","names":["Database","import_kysely","options","import_kysely","fastq","options","import_kysely","__filename"]}
package/dist/index.d.cts CHANGED
@@ -106,6 +106,10 @@ interface ClientOptions {
106
106
  * Backoff function type
107
107
  */
108
108
  type BackoffFunction = (attempts: number) => number;
109
+ /**
110
+ * Worker state for persistence
111
+ */
112
+ type WorkerState = 'stopped' | 'running' | 'paused';
109
113
  /**
110
114
  * Options for creating a worker
111
115
  */
@@ -124,6 +128,10 @@ interface WorkerOptions {
124
128
  timeoutMs?: number;
125
129
  /** Backoff function for retries. Default: exponential */
126
130
  backoff?: BackoffFunction;
131
+ /** Persist worker state to database. Default: false */
132
+ persistState?: boolean;
133
+ /** Auto-restore worker state on creation. Default: true (only applies if persistState is true) */
134
+ autoRestore?: boolean;
127
135
  }
128
136
  /**
129
137
  * Job processor function type
@@ -148,6 +156,10 @@ interface WorkmaticClient {
148
156
  add<TPayload = unknown>(payload: TPayload, options?: AddJobOptions): Promise<AddJobResult>;
149
157
  /** Get job statistics */
150
158
  stats(): Promise<JobStats>;
159
+ /** Clear all jobs from the queue */
160
+ clear(options?: {
161
+ status?: JobStatus;
162
+ }): Promise<number>;
151
163
  }
152
164
  /**
153
165
  * Worker interface
@@ -165,6 +177,12 @@ interface WorkmaticWorker {
165
177
  resume(): void;
166
178
  /** Get job statistics */
167
179
  stats(): Promise<JobStats>;
180
+ /** Restore worker state from database (only when persistState is true) */
181
+ restoreState(): Promise<WorkerState | null>;
182
+ /** Clear all jobs from the queue */
183
+ clear(options?: {
184
+ status?: JobStatus;
185
+ }): Promise<number>;
168
186
  /** Check if worker is running */
169
187
  readonly isRunning: boolean;
170
188
  /** Check if worker is paused */
@@ -365,4 +383,4 @@ declare const defaultBackoff: BackoffFunction;
365
383
  */
366
384
  declare function validatePayload(payload: unknown): string;
367
385
 
368
- export { type AddJobOptions, type AddJobResult, type BackoffFunction, type ClaimedJob, type ClientOptions, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, validatePayload };
386
+ export { type AddJobOptions, type AddJobResult, type BackoffFunction, type ClaimedJob, type ClientOptions, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkerState, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, validatePayload };
package/dist/index.d.ts CHANGED
@@ -106,6 +106,10 @@ interface ClientOptions {
106
106
  * Backoff function type
107
107
  */
108
108
  type BackoffFunction = (attempts: number) => number;
109
+ /**
110
+ * Worker state for persistence
111
+ */
112
+ type WorkerState = 'stopped' | 'running' | 'paused';
109
113
  /**
110
114
  * Options for creating a worker
111
115
  */
@@ -124,6 +128,10 @@ interface WorkerOptions {
124
128
  timeoutMs?: number;
125
129
  /** Backoff function for retries. Default: exponential */
126
130
  backoff?: BackoffFunction;
131
+ /** Persist worker state to database. Default: false */
132
+ persistState?: boolean;
133
+ /** Auto-restore worker state on creation. Default: true (only applies if persistState is true) */
134
+ autoRestore?: boolean;
127
135
  }
128
136
  /**
129
137
  * Job processor function type
@@ -148,6 +156,10 @@ interface WorkmaticClient {
148
156
  add<TPayload = unknown>(payload: TPayload, options?: AddJobOptions): Promise<AddJobResult>;
149
157
  /** Get job statistics */
150
158
  stats(): Promise<JobStats>;
159
+ /** Clear all jobs from the queue */
160
+ clear(options?: {
161
+ status?: JobStatus;
162
+ }): Promise<number>;
151
163
  }
152
164
  /**
153
165
  * Worker interface
@@ -165,6 +177,12 @@ interface WorkmaticWorker {
165
177
  resume(): void;
166
178
  /** Get job statistics */
167
179
  stats(): Promise<JobStats>;
180
+ /** Restore worker state from database (only when persistState is true) */
181
+ restoreState(): Promise<WorkerState | null>;
182
+ /** Clear all jobs from the queue */
183
+ clear(options?: {
184
+ status?: JobStatus;
185
+ }): Promise<number>;
168
186
  /** Check if worker is running */
169
187
  readonly isRunning: boolean;
170
188
  /** Check if worker is paused */
@@ -365,4 +383,4 @@ declare const defaultBackoff: BackoffFunction;
365
383
  */
366
384
  declare function validatePayload(payload: unknown): string;
367
385
 
368
- export { type AddJobOptions, type AddJobResult, type BackoffFunction, type ClaimedJob, type ClientOptions, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, validatePayload };
386
+ export { type AddJobOptions, type AddJobResult, type BackoffFunction, type ClaimedJob, type ClientOptions, type DashboardMiddleware, type DashboardMiddlewareOptions, type DashboardOptions, type DatabaseOptions, type Job, type JobProcessor, type JobStats, type JobStatus, type WorkerOptions, type WorkerState, type WorkmaticClient, type WorkmaticDashboard, type WorkmaticDatabase, type WorkmaticDb, type WorkmaticJobsTable, type WorkmaticWorker, createClient, createDashboard, createDashboardMiddleware, createDatabase, createWorker, defaultBackoff, validatePayload };
package/dist/index.js CHANGED
@@ -146,6 +146,17 @@ function createClient(options) {
146
146
  stats.total += count;
147
147
  }
148
148
  return stats;
149
+ },
150
+ /**
151
+ * Clear all jobs from the queue
152
+ */
153
+ async clear(options2 = {}) {
154
+ let query = db.deleteFrom("workmatic_jobs").where("queue", "=", queue);
155
+ if (options2.status) {
156
+ query = query.where("status", "=", options2.status);
157
+ }
158
+ const result = await query.execute();
159
+ return Number(result[0]?.numDeletedRows ?? 0);
149
160
  }
150
161
  };
151
162
  }
@@ -161,7 +172,9 @@ function createWorker(options) {
161
172
  leaseMs = 3e4,
162
173
  pollMs = 1e3,
163
174
  timeoutMs,
164
- backoff = defaultBackoff
175
+ backoff = defaultBackoff,
176
+ persistState = false,
177
+ autoRestore = true
165
178
  } = options;
166
179
  if (!db) {
167
180
  throw new Error("Database instance is required");
@@ -171,6 +184,36 @@ function createWorker(options) {
171
184
  let processor = null;
172
185
  let pumpTimeout = null;
173
186
  let fastqQueue = null;
187
+ function getStateKey() {
188
+ return `worker_state_${queue}`;
189
+ }
190
+ async function saveState(state) {
191
+ if (!persistState) return;
192
+ const timestamp = now();
193
+ 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
+ await sql2`
197
+ INSERT INTO workmatic_settings (queue, paused, updated_at)
198
+ VALUES (${key}, ${state === "paused" ? 1 : state === "running" ? 2 : 0}, ${timestamp})
199
+ ON CONFLICT(queue) DO UPDATE SET
200
+ paused = ${state === "paused" ? 1 : state === "running" ? 2 : 0},
201
+ updated_at = ${timestamp}
202
+ `.execute(db);
203
+ }
204
+ async function loadState() {
205
+ if (!persistState) return null;
206
+ try {
207
+ const key = getStateKey();
208
+ const result = await db.selectFrom("workmatic_settings").select("paused").where("queue", "=", key).executeTakeFirst();
209
+ if (!result) return null;
210
+ if (result.paused === 2) return "running";
211
+ if (result.paused === 1) return "paused";
212
+ return "stopped";
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
174
217
  async function requeueExpiredLeases() {
175
218
  const timestamp = now();
176
219
  const result = await db.updateTable("workmatic_jobs").set({
@@ -323,6 +366,8 @@ function createWorker(options) {
323
366
  running = true;
324
367
  paused = false;
325
368
  fastqQueue = fastq.promise(processJob, concurrency);
369
+ saveState("running").catch(() => {
370
+ });
326
371
  pump();
327
372
  },
328
373
  async stop() {
@@ -338,12 +383,17 @@ function createWorker(options) {
338
383
  await fastqQueue.drained();
339
384
  fastqQueue = null;
340
385
  }
386
+ await saveState("stopped");
341
387
  },
342
388
  pause() {
343
389
  paused = true;
390
+ saveState("paused").catch(() => {
391
+ });
344
392
  },
345
393
  resume() {
346
394
  paused = false;
395
+ saveState("running").catch(() => {
396
+ });
347
397
  },
348
398
  async stats() {
349
399
  const result = await db.selectFrom("workmatic_jobs").select([
@@ -376,8 +426,33 @@ function createWorker(options) {
376
426
  },
377
427
  get queue() {
378
428
  return queue;
429
+ },
430
+ async restoreState() {
431
+ const state = await loadState();
432
+ if (state === "running" && processor) {
433
+ this.start();
434
+ } else if (state === "paused" && processor) {
435
+ this.start();
436
+ this.pause();
437
+ }
438
+ return state;
439
+ },
440
+ async clear(options2 = {}) {
441
+ let query = db.deleteFrom("workmatic_jobs").where("queue", "=", queue);
442
+ if (options2.status) {
443
+ query = query.where("status", "=", options2.status);
444
+ }
445
+ const result = await query.execute();
446
+ return Number(result[0]?.numDeletedRows ?? 0);
379
447
  }
380
448
  };
449
+ if (persistState && autoRestore) {
450
+ setImmediate(async () => {
451
+ if (processor) {
452
+ await worker.restoreState();
453
+ }
454
+ });
455
+ }
381
456
  return worker;
382
457
  }
383
458
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/database.ts","../src/client.ts","../src/utils.ts","../src/worker.ts","../src/dashboard.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { Kysely, SqliteDialect } from 'kysely';\nimport type { WorkmaticDatabase, WorkmaticDb, DatabaseOptions } from './types.js';\n\n/**\n * Create and initialize the workmatic database\n * \n * @param options - Database options\n * @returns Kysely database instance\n * \n * @example\n * ```ts\n * // Using a file path\n * const db = createDatabase({ filename: './jobs.db' });\n * \n * // Using an existing better-sqlite3 instance\n * import Database from 'better-sqlite3';\n * const sqlite = new Database('./jobs.db');\n * const db = createDatabase({ db: sqlite });\n * \n * // In-memory database (for testing)\n * const db = createDatabase({ filename: ':memory:' });\n * ```\n */\nexport function createDatabase(options: DatabaseOptions = {}): WorkmaticDb {\n let sqliteDb: Database.Database;\n\n if (options.db) {\n sqliteDb = options.db;\n } else {\n const filename = options.filename ?? ':memory:';\n sqliteDb = new Database(filename);\n }\n\n // Set pragmas for performance and safety\n sqliteDb.pragma('journal_mode = WAL');\n sqliteDb.pragma('synchronous = NORMAL');\n sqliteDb.pragma('busy_timeout = 5000');\n\n // Create Kysely instance\n const db = new Kysely<WorkmaticDatabase>({\n dialect: new SqliteDialect({\n database: sqliteDb,\n }),\n });\n\n // Create schema synchronously using better-sqlite3 directly\n createSchema(sqliteDb);\n\n return db;\n}\n\n/**\n * Create the database schema if it doesn't exist\n */\nfunction createSchema(db: Database.Database): void {\n // Create the jobs table\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_jobs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n public_id TEXT UNIQUE NOT NULL,\n queue TEXT NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'ready',\n priority INTEGER NOT NULL DEFAULT 0,\n run_at INTEGER NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n lease_until INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n last_error TEXT\n )\n `);\n\n // Create composite index for efficient job claiming\n // (queue, status, run_at, priority, id)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_claim \n ON workmatic_jobs (queue, status, run_at, priority, id)\n `);\n\n // Create index for lease expiration checking\n // (status, lease_until)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_lease \n ON workmatic_jobs (status, lease_until)\n `);\n\n // Create settings table for queue-level settings (pause state, etc.)\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_settings (\n queue TEXT PRIMARY KEY,\n paused INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL\n )\n `);\n}\n\n/**\n * Get the underlying better-sqlite3 database instance from a Kysely instance\n * This is useful for advanced operations or testing\n */\nexport function getUnderlyingDb(db: WorkmaticDb): Database.Database {\n // Access the internal database through the dialect\n // This is a bit hacky but necessary for some operations\n const dialect = (db as any).getExecutor().adapter.db;\n return dialect;\n}\n","import { nanoid } from 'nanoid';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n ClientOptions,\n WorkmaticClient,\n AddJobOptions,\n AddJobResult,\n JobStats,\n JobStatus,\n} from './types.js';\nimport { validatePayload, now } from './utils.js';\n\n/**\n * Create a job queue client for adding jobs\n * \n * @param options - Client options\n * @returns Client instance\n * \n * @example\n * ```ts\n * const client = createClient({ db });\n * \n * // Add a simple job\n * const result = await client.add({ email: 'user@example.com' });\n * console.log(result.id); // Job public ID\n * \n * // Add a job with options\n * await client.add(\n * { userId: 123 },\n * { priority: 1, delayMs: 5000, maxAttempts: 5 }\n * );\n * \n * // Get queue statistics\n * const stats = await client.stats();\n * console.log(stats); // { ready: 5, running: 2, done: 100, ... }\n * ```\n */\nexport function createClient(options: ClientOptions): WorkmaticClient {\n const { db, queue = 'default' } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n return {\n /**\n * Add a job to the queue\n */\n async add<TPayload = unknown>(\n payload: TPayload,\n opts: AddJobOptions = {}\n ): Promise<AddJobResult> {\n const {\n priority = 0,\n delayMs = 0,\n maxAttempts = 3,\n } = opts;\n\n // Validate and serialize payload\n const payloadJson = validatePayload(payload);\n \n // Generate unique public ID\n const publicId = nanoid();\n \n const timestamp = now();\n const runAt = timestamp + delayMs;\n\n // Insert job\n await db\n .insertInto('workmatic_jobs')\n .values({\n public_id: publicId,\n queue,\n payload: payloadJson,\n status: 'ready',\n priority,\n run_at: runAt,\n attempts: 0,\n max_attempts: maxAttempts,\n lease_until: 0,\n created_at: timestamp,\n updated_at: timestamp,\n last_error: null,\n })\n .execute();\n\n return { ok: true, id: publicId };\n },\n\n /**\n * Get job statistics for the queue\n */\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n };\n}\n","import type { BackoffFunction } from './types.js';\n\n/**\n * Default exponential backoff function\n * Returns delay in milliseconds: 1000 * 2^attempts\n * \n * @param attempts - Number of failed attempts\n * @returns Delay in milliseconds\n * \n * @example\n * ```ts\n * defaultBackoff(0) // 1000ms (1 second)\n * defaultBackoff(1) // 2000ms (2 seconds)\n * defaultBackoff(2) // 4000ms (4 seconds)\n * defaultBackoff(3) // 8000ms (8 seconds)\n * ```\n */\nexport const defaultBackoff: BackoffFunction = (attempts: number): number => {\n return 1000 * Math.pow(2, attempts);\n};\n\n/**\n * Validate that a payload is JSON-serializable\n * \n * @param payload - The payload to validate\n * @throws Error if payload cannot be serialized to JSON\n * @returns The JSON string representation\n */\nexport function validatePayload(payload: unknown): string {\n try {\n return JSON.stringify(payload);\n } catch (error) {\n throw new Error(\n `Payload is not JSON-serializable: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Parse a JSON payload safely\n * \n * @param json - JSON string to parse\n * @returns Parsed payload\n * @throws Error if JSON is invalid\n */\nexport function parsePayload<T = unknown>(json: string): T {\n try {\n return JSON.parse(json) as T;\n } catch (error) {\n throw new Error(\n `Invalid JSON payload: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Sleep for a specified duration\n * \n * @param ms - Duration in milliseconds\n * @returns Promise that resolves after the duration\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Get current timestamp in milliseconds\n */\nexport function now(): number {\n return Date.now();\n}\n","import fastq from 'fastq';\nimport type { queueAsPromised } from 'fastq';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n WorkerOptions,\n WorkmaticWorker,\n JobProcessor,\n JobStats,\n JobStatus,\n Job,\n ClaimedJob,\n} from './types.js';\nimport { defaultBackoff, parsePayload, sleep, now } from './utils.js';\n\n/**\n * Create a job queue worker for processing jobs\n * \n * @param options - Worker options\n * @returns Worker instance\n * \n * @example\n * ```ts\n * const worker = createWorker({ db, concurrency: 4 });\n * \n * // Set the processor function\n * worker.process(async (job) => {\n * console.log('Processing job:', job.id);\n * await sendEmail(job.payload.email);\n * });\n * \n * // Start processing\n * worker.start();\n * \n * // Later, gracefully stop\n * await worker.stop();\n * ```\n */\nexport function createWorker(options: WorkerOptions): WorkmaticWorker {\n const {\n db,\n queue = 'default',\n concurrency = 1,\n leaseMs = 30000,\n pollMs = 1000,\n timeoutMs,\n backoff = defaultBackoff,\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // State\n let running = false;\n let paused = false;\n let processor: JobProcessor<any> | null = null;\n let pumpTimeout: NodeJS.Timeout | null = null;\n let fastqQueue: queueAsPromised<ClaimedJob, void> | null = null;\n\n /**\n * Requeue jobs with expired leases\n */\n async function requeueExpiredLeases(): Promise<number> {\n const timestamp = now();\n\n const result = await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n lease_until: 0,\n updated_at: timestamp,\n })\n .where('status', '=', 'running')\n .where('lease_until', '<', timestamp)\n .where('lease_until', '>', 0)\n .execute();\n\n return Number(result[0]?.numUpdatedRows ?? 0);\n }\n\n /**\n * Claim a batch of jobs for processing\n */\n async function claimBatch(limit: number): Promise<ClaimedJob[]> {\n const timestamp = now();\n const leaseUntil = timestamp + leaseMs;\n\n // Use a transaction to ensure atomic claim\n return await db.transaction().execute(async (trx) => {\n // Select eligible jobs\n const jobs = await trx\n .selectFrom('workmatic_jobs')\n .select(['id', 'public_id', 'queue', 'payload', 'attempts', 'max_attempts'])\n .where('queue', '=', queue)\n .where('status', '=', 'ready')\n .where('run_at', '<=', timestamp)\n .orderBy('priority', 'asc')\n .orderBy('id', 'asc')\n .limit(limit)\n .execute();\n\n if (jobs.length === 0) {\n return [];\n }\n\n // Get the IDs of claimed jobs\n const jobIds = jobs.map(j => j.id);\n\n // Update them to running status\n await trx\n .updateTable('workmatic_jobs')\n .set({\n status: 'running',\n lease_until: leaseUntil,\n updated_at: timestamp,\n })\n .where('id', 'in', jobIds)\n .execute();\n\n return jobs;\n });\n }\n\n /**\n * Mark a job as completed\n */\n async function markDone(jobId: number): Promise<void> {\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'done',\n lease_until: 0,\n updated_at: now(),\n })\n .where('id', '=', jobId)\n .execute();\n }\n\n /**\n * Handle job failure\n */\n async function markFailed(\n jobId: number,\n attempts: number,\n maxAttempts: number,\n error: Error\n ): Promise<void> {\n const timestamp = now();\n const newAttempts = attempts + 1;\n const errorMessage = error.message || String(error);\n\n if (newAttempts < maxAttempts) {\n // Retry: set back to ready with backoff delay\n const runAt = timestamp + backoff(newAttempts);\n \n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n attempts: newAttempts,\n run_at: runAt,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n } else {\n // Max attempts reached: mark as dead\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'dead',\n attempts: newAttempts,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n }\n }\n\n /**\n * Run a promise with timeout\n */\n async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n jobId: string\n ): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`Job ${jobId} timed out after ${ms}ms`));\n }, ms);\n });\n\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n }\n\n /**\n * Process a single job\n */\n async function processJob(claimedJob: ClaimedJob): Promise<void> {\n if (!processor) {\n throw new Error('No processor set');\n }\n\n // Parse payload and create job object\n const payload = parsePayload(claimedJob.payload);\n const job: Job = {\n id: claimedJob.public_id,\n queue: claimedJob.queue,\n payload,\n status: 'running',\n priority: 0, // Not needed for processing\n attempts: claimedJob.attempts,\n maxAttempts: claimedJob.max_attempts,\n createdAt: 0, // Not needed for processing\n lastError: null,\n };\n\n try {\n // Run processor with optional timeout\n if (timeoutMs) {\n await withTimeout(processor(job), timeoutMs, job.id);\n } else {\n await processor(job);\n }\n await markDone(claimedJob.id);\n } catch (error) {\n await markFailed(\n claimedJob.id,\n claimedJob.attempts,\n claimedJob.max_attempts,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Check if queue is paused in database (for CLI control)\n */\n async function isQueuePausedInDb(): Promise<boolean> {\n const setting = await db\n .selectFrom('workmatic_settings')\n .select('paused')\n .where('queue', '=', queue)\n .executeTakeFirst();\n \n return setting?.paused === 1;\n }\n\n /**\n * Main pump loop\n */\n async function pump(): Promise<void> {\n if (!running) {\n return;\n }\n\n // Check both in-memory pause and database pause (for CLI control)\n if (paused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n try {\n // Check database pause state (allows CLI to pause running workers)\n const dbPaused = await isQueuePausedInDb();\n if (dbPaused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n // Requeue expired leases\n await requeueExpiredLeases();\n\n // Claim a batch of jobs\n const batchSize = concurrency * 2;\n const jobs = await claimBatch(batchSize);\n\n if (jobs.length > 0) {\n // Push jobs into fastq\n for (const job of jobs) {\n fastqQueue!.push(job);\n }\n // Immediately pump again\n pumpTimeout = setTimeout(pump, 0);\n } else {\n // No jobs, wait before polling again\n pumpTimeout = setTimeout(pump, pollMs);\n }\n } catch (error) {\n // Log error and continue\n console.error('[workmatic] Pump error:', error);\n pumpTimeout = setTimeout(pump, pollMs);\n }\n }\n\n // Create the worker object\n const worker: WorkmaticWorker = {\n process<TPayload = unknown>(fn: JobProcessor<TPayload>): void {\n processor = fn as JobProcessor<any>;\n },\n\n start(): void {\n if (running) {\n return;\n }\n\n if (!processor) {\n throw new Error('No processor set. Call process() before start()');\n }\n\n running = true;\n paused = false;\n\n // Create fastq queue\n fastqQueue = fastq.promise(processJob, concurrency);\n\n // Start pump loop\n pump();\n },\n\n async stop(): Promise<void> {\n if (!running) {\n return;\n }\n\n running = false;\n\n // Clear pump timeout\n if (pumpTimeout) {\n clearTimeout(pumpTimeout);\n pumpTimeout = null;\n }\n\n // Wait for fastq to drain\n if (fastqQueue) {\n await fastqQueue.drained();\n fastqQueue = null;\n }\n },\n\n pause(): void {\n paused = true;\n },\n\n resume(): void {\n paused = false;\n },\n\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n\n get isRunning(): boolean {\n return running;\n },\n\n get isPaused(): boolean {\n return paused;\n },\n\n get queue(): string {\n return queue;\n },\n };\n\n return worker;\n}\n","import { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { readFile } from 'fs/promises';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n DashboardOptions,\n DashboardMiddlewareOptions,\n WorkmaticDashboard,\n WorkmaticWorker,\n DashboardMiddleware,\n JobStats,\n JobStatus,\n} from './types.js';\n\n// Get the directory of this module for static file serving\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Content types for static files\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n};\n\n/**\n * Create the core request handler (shared between standalone and middleware)\n */\nfunction createRequestHandler(\n db: WorkmaticDb,\n workerMap: Map<string, WorkmaticWorker>,\n basePath: string = ''\n) {\n /**\n * Send JSON response\n */\n function sendJson(res: ServerResponse, data: unknown, status = 200): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n }\n\n /**\n * Send error response\n */\n function sendError(res: ServerResponse, message: string, status = 500): void {\n sendJson(res, { error: message }, status);\n }\n\n /**\n * Parse URL query parameters\n */\n function parseQuery(url: string): URLSearchParams {\n const queryIndex = url.indexOf('?');\n if (queryIndex === -1) return new URLSearchParams();\n return new URLSearchParams(url.slice(queryIndex + 1));\n }\n\n /**\n * Get path from URL (removing basePath prefix)\n */\n function getPath(url: string): string {\n const queryIndex = url.indexOf('?');\n let path = queryIndex === -1 ? url : url.slice(0, queryIndex);\n \n // Remove basePath prefix if present\n if (basePath && path.startsWith(basePath)) {\n path = path.slice(basePath.length) || '/';\n }\n \n return path;\n }\n\n /**\n * API: Get jobs list\n */\n async function handleGetJobs(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n const statusFilter = query.get('status') as JobStatus | null;\n const limit = Math.min(parseInt(query.get('limit') || '50', 10), 100);\n const offset = parseInt(query.get('offset') || '0', 10);\n\n let queryBuilder = db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .orderBy('created_at', 'desc')\n .limit(limit)\n .offset(offset);\n\n if (queueFilter) {\n queryBuilder = queryBuilder.where('queue', '=', queueFilter);\n }\n if (statusFilter) {\n queryBuilder = queryBuilder.where('status', '=', statusFilter);\n }\n\n const jobs = await queryBuilder.execute();\n\n // Transform to API format\n const apiJobs = jobs.map(job => ({\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n }));\n\n sendJson(res, { jobs: apiJobs, limit, offset });\n }\n\n /**\n * API: Get statistics\n */\n async function handleGetStats(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n\n // Get counts by status\n let statsQuery = db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .groupBy('status');\n\n if (queueFilter) {\n statsQuery = statsQuery.where('queue', '=', queueFilter);\n }\n\n const statusCounts = await statsQuery.execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of statusCounts) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n // Get queue list\n const queuesResult = await db\n .selectFrom('workmatic_jobs')\n .select('queue')\n .groupBy('queue')\n .execute();\n\n const queues = queuesResult.map(q => q.queue);\n\n // Get worker status\n const workersStatus = Array.from(workerMap.entries()).map(([queue, worker]) => ({\n queue,\n running: worker.isRunning,\n paused: worker.isPaused,\n }));\n\n sendJson(res, { stats, queues, workers: workersStatus });\n }\n\n /**\n * API: Get single job by ID\n */\n async function handleGetJob(req: IncomingMessage, res: ServerResponse, publicId: string): Promise<void> {\n const job = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'lease_until',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .where('public_id', '=', publicId)\n .executeTakeFirst();\n\n if (!job) {\n return sendError(res, 'Job not found', 404);\n }\n\n sendJson(res, {\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n leaseUntil: job.lease_until,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n });\n }\n\n /**\n * API: Pause worker\n */\n async function handlePauseWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.pause();\n sendJson(res, { ok: true, queue, paused: true });\n }\n\n /**\n * API: Resume worker\n */\n async function handleResumeWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.resume();\n sendJson(res, { ok: true, queue, paused: false });\n }\n\n /**\n * Serve static files from the dashboard directory\n */\n async function serveStatic(res: ServerResponse, filePath: string): Promise<void> {\n // Navigate from dist/dashboard.js to dashboard/ directory\n const dashboardDir = join(__dirname, '..', 'dashboard');\n const fullPath = join(dashboardDir, filePath);\n \n // Determine content type\n const ext = filePath.substring(filePath.lastIndexOf('.')) || '.html';\n const contentType = CONTENT_TYPES[ext] || 'application/octet-stream';\n\n try {\n const content = await readFile(fullPath, 'utf-8');\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(content);\n } catch (error) {\n sendError(res, 'Not found', 404);\n }\n }\n\n /**\n * Main request handler\n */\n return async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void\n ): Promise<boolean> {\n const path = getPath(req.url || '/');\n\n // Check if this request is for the dashboard\n const fullUrl = req.url || '/';\n if (basePath && !fullUrl.startsWith(basePath)) {\n // Not our route, call next middleware if available\n if (next) next();\n return false;\n }\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return true;\n }\n\n try {\n // API routes\n if (path === '/api/jobs' && req.method === 'GET') {\n await handleGetJobs(req, res);\n return true;\n }\n\n if (path === '/api/stats' && req.method === 'GET') {\n await handleGetStats(req, res);\n return true;\n }\n\n // Single job: /api/jobs/:id\n const jobMatch = path.match(/^\\/api\\/jobs\\/([^/]+)$/);\n if (jobMatch && req.method === 'GET') {\n await handleGetJob(req, res, jobMatch[1]);\n return true;\n }\n\n // Pause worker: /api/workers/:queue/pause\n const pauseMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/pause$/);\n if (pauseMatch && req.method === 'POST') {\n await handlePauseWorker(res, pauseMatch[1]);\n return true;\n }\n\n // Resume worker: /api/workers/:queue/resume\n const resumeMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/resume$/);\n if (resumeMatch && req.method === 'POST') {\n await handleResumeWorker(res, resumeMatch[1]);\n return true;\n }\n\n // Static files\n if (path === '/' || path === '/index.html') {\n await serveStatic(res, 'index.html');\n return true;\n }\n if (path === '/style.css') {\n await serveStatic(res, 'style.css');\n return true;\n }\n if (path === '/app.js') {\n await serveStatic(res, 'app.js');\n return true;\n }\n\n // 404 for unknown routes under our basePath\n sendError(res, 'Not found', 404);\n return true;\n } catch (error) {\n console.error('[workmatic] Dashboard error:', error);\n sendError(res, 'Internal server error', 500);\n return true;\n }\n };\n}\n\n/**\n * Create a dashboard HTTP server for monitoring and controlling the job queue\n * \n * @param options - Dashboard options\n * @returns Dashboard instance\n * \n * @example\n * ```ts\n * const dashboard = createDashboard({ \n * db, \n * port: 3000,\n * workers: [worker1, worker2] \n * });\n * \n * console.log(`Dashboard running at http://localhost:${dashboard.port}`);\n * \n * // Later, close the server\n * await dashboard.close();\n * ```\n */\nexport function createDashboard(options: DashboardOptions): WorkmaticDashboard {\n const {\n db,\n port = 3000,\n workers = [],\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap);\n\n // Create and start HTTP server\n const server = createServer((req, res) => {\n handleRequest(req, res).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n });\n });\n\n server.listen(port);\n\n return {\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n get port(): number {\n return port;\n },\n };\n}\n\n/**\n * Create an Express-compatible middleware for the dashboard\n * \n * @param options - Middleware options\n * @returns Express middleware function\n * \n * @example\n * ```ts\n * import express from 'express';\n * import { createDashboardMiddleware } from 'workmatic';\n * \n * const app = express();\n * \n * // Mount dashboard at /workmatic\n * app.use(createDashboardMiddleware({ \n * db,\n * basePath: '/workmatic',\n * workers: [worker] \n * }));\n * \n * app.listen(3000);\n * // Dashboard available at http://localhost:3000/workmatic\n * ```\n */\nexport function createDashboardMiddleware(options: DashboardMiddlewareOptions): DashboardMiddleware {\n const {\n db,\n workers = [],\n basePath = '',\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap, basePath);\n\n return (req, res, next) => {\n handleRequest(req, res, next).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n if (next) {\n next();\n } else {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n };\n}\n"],"mappings":"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","names":["sql","sql","sql","__filename","__dirname"]}
1
+ {"version":3,"sources":["../src/database.ts","../src/client.ts","../src/utils.ts","../src/worker.ts","../src/dashboard.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { Kysely, SqliteDialect } from 'kysely';\nimport type { WorkmaticDatabase, WorkmaticDb, DatabaseOptions } from './types.js';\n\n/**\n * Create and initialize the workmatic database\n * \n * @param options - Database options\n * @returns Kysely database instance\n * \n * @example\n * ```ts\n * // Using a file path\n * const db = createDatabase({ filename: './jobs.db' });\n * \n * // Using an existing better-sqlite3 instance\n * import Database from 'better-sqlite3';\n * const sqlite = new Database('./jobs.db');\n * const db = createDatabase({ db: sqlite });\n * \n * // In-memory database (for testing)\n * const db = createDatabase({ filename: ':memory:' });\n * ```\n */\nexport function createDatabase(options: DatabaseOptions = {}): WorkmaticDb {\n let sqliteDb: Database.Database;\n\n if (options.db) {\n sqliteDb = options.db;\n } else {\n const filename = options.filename ?? ':memory:';\n sqliteDb = new Database(filename);\n }\n\n // Set pragmas for performance and safety\n sqliteDb.pragma('journal_mode = WAL');\n sqliteDb.pragma('synchronous = NORMAL');\n sqliteDb.pragma('busy_timeout = 5000');\n\n // Create Kysely instance\n const db = new Kysely<WorkmaticDatabase>({\n dialect: new SqliteDialect({\n database: sqliteDb,\n }),\n });\n\n // Create schema synchronously using better-sqlite3 directly\n createSchema(sqliteDb);\n\n return db;\n}\n\n/**\n * Create the database schema if it doesn't exist\n */\nfunction createSchema(db: Database.Database): void {\n // Create the jobs table\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_jobs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n public_id TEXT UNIQUE NOT NULL,\n queue TEXT NOT NULL,\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'ready',\n priority INTEGER NOT NULL DEFAULT 0,\n run_at INTEGER NOT NULL,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n lease_until INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL,\n last_error TEXT\n )\n `);\n\n // Create composite index for efficient job claiming\n // (queue, status, run_at, priority, id)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_claim \n ON workmatic_jobs (queue, status, run_at, priority, id)\n `);\n\n // Create index for lease expiration checking\n // (status, lease_until)\n db.exec(`\n CREATE INDEX IF NOT EXISTS idx_workmatic_jobs_lease \n ON workmatic_jobs (status, lease_until)\n `);\n\n // Create settings table for queue-level settings (pause state, etc.)\n db.exec(`\n CREATE TABLE IF NOT EXISTS workmatic_settings (\n queue TEXT PRIMARY KEY,\n paused INTEGER NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL\n )\n `);\n}\n\n/**\n * Get the underlying better-sqlite3 database instance from a Kysely instance\n * This is useful for advanced operations or testing\n */\nexport function getUnderlyingDb(db: WorkmaticDb): Database.Database {\n // Access the internal database through the dialect\n // This is a bit hacky but necessary for some operations\n const dialect = (db as any).getExecutor().adapter.db;\n return dialect;\n}\n","import { nanoid } from 'nanoid';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n ClientOptions,\n WorkmaticClient,\n AddJobOptions,\n AddJobResult,\n JobStats,\n JobStatus,\n} from './types.js';\nimport { validatePayload, now } from './utils.js';\n\n/**\n * Create a job queue client for adding jobs\n * \n * @param options - Client options\n * @returns Client instance\n * \n * @example\n * ```ts\n * const client = createClient({ db });\n * \n * // Add a simple job\n * const result = await client.add({ email: 'user@example.com' });\n * console.log(result.id); // Job public ID\n * \n * // Add a job with options\n * await client.add(\n * { userId: 123 },\n * { priority: 1, delayMs: 5000, maxAttempts: 5 }\n * );\n * \n * // Get queue statistics\n * const stats = await client.stats();\n * console.log(stats); // { ready: 5, running: 2, done: 100, ... }\n * ```\n */\nexport function createClient(options: ClientOptions): WorkmaticClient {\n const { db, queue = 'default' } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n return {\n /**\n * Add a job to the queue\n */\n async add<TPayload = unknown>(\n payload: TPayload,\n opts: AddJobOptions = {}\n ): Promise<AddJobResult> {\n const {\n priority = 0,\n delayMs = 0,\n maxAttempts = 3,\n } = opts;\n\n // Validate and serialize payload\n const payloadJson = validatePayload(payload);\n \n // Generate unique public ID\n const publicId = nanoid();\n \n const timestamp = now();\n const runAt = timestamp + delayMs;\n\n // Insert job\n await db\n .insertInto('workmatic_jobs')\n .values({\n public_id: publicId,\n queue,\n payload: payloadJson,\n status: 'ready',\n priority,\n run_at: runAt,\n attempts: 0,\n max_attempts: maxAttempts,\n lease_until: 0,\n created_at: timestamp,\n updated_at: timestamp,\n last_error: null,\n })\n .execute();\n\n return { ok: true, id: publicId };\n },\n\n /**\n * Get job statistics for the queue\n */\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n\n /**\n * Clear all jobs from the queue\n */\n async clear(options: { status?: JobStatus } = {}): Promise<number> {\n let query = db\n .deleteFrom('workmatic_jobs')\n .where('queue', '=', queue);\n\n if (options.status) {\n query = query.where('status', '=', options.status);\n }\n\n const result = await query.execute();\n return Number(result[0]?.numDeletedRows ?? 0);\n },\n };\n}\n","import type { BackoffFunction } from './types.js';\n\n/**\n * Default exponential backoff function\n * Returns delay in milliseconds: 1000 * 2^attempts\n * \n * @param attempts - Number of failed attempts\n * @returns Delay in milliseconds\n * \n * @example\n * ```ts\n * defaultBackoff(0) // 1000ms (1 second)\n * defaultBackoff(1) // 2000ms (2 seconds)\n * defaultBackoff(2) // 4000ms (4 seconds)\n * defaultBackoff(3) // 8000ms (8 seconds)\n * ```\n */\nexport const defaultBackoff: BackoffFunction = (attempts: number): number => {\n return 1000 * Math.pow(2, attempts);\n};\n\n/**\n * Validate that a payload is JSON-serializable\n * \n * @param payload - The payload to validate\n * @throws Error if payload cannot be serialized to JSON\n * @returns The JSON string representation\n */\nexport function validatePayload(payload: unknown): string {\n try {\n return JSON.stringify(payload);\n } catch (error) {\n throw new Error(\n `Payload is not JSON-serializable: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Parse a JSON payload safely\n * \n * @param json - JSON string to parse\n * @returns Parsed payload\n * @throws Error if JSON is invalid\n */\nexport function parsePayload<T = unknown>(json: string): T {\n try {\n return JSON.parse(json) as T;\n } catch (error) {\n throw new Error(\n `Invalid JSON payload: ${error instanceof Error ? error.message : 'Unknown error'}`\n );\n }\n}\n\n/**\n * Sleep for a specified duration\n * \n * @param ms - Duration in milliseconds\n * @returns Promise that resolves after the duration\n */\nexport function sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/**\n * Get current timestamp in milliseconds\n */\nexport function now(): number {\n return Date.now();\n}\n","import fastq from 'fastq';\nimport type { queueAsPromised } from 'fastq';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n WorkerOptions,\n WorkmaticWorker,\n JobProcessor,\n JobStats,\n JobStatus,\n Job,\n ClaimedJob,\n WorkerState,\n} from './types.js';\nimport { defaultBackoff, parsePayload, sleep, now } from './utils.js';\n\n/**\n * Create a job queue worker for processing jobs\n * \n * @param options - Worker options\n * @returns Worker instance\n * \n * @example\n * ```ts\n * const worker = createWorker({ db, concurrency: 4 });\n * \n * // Set the processor function\n * worker.process(async (job) => {\n * console.log('Processing job:', job.id);\n * await sendEmail(job.payload.email);\n * });\n * \n * // Start processing\n * worker.start();\n * \n * // Later, gracefully stop\n * await worker.stop();\n * ```\n */\nexport function createWorker(options: WorkerOptions): WorkmaticWorker {\n const {\n db,\n queue = 'default',\n concurrency = 1,\n leaseMs = 30000,\n pollMs = 1000,\n timeoutMs,\n backoff = defaultBackoff,\n persistState = false,\n autoRestore = true,\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // State\n let running = false;\n let paused = false;\n let processor: JobProcessor<any> | null = null;\n let pumpTimeout: NodeJS.Timeout | null = null;\n let fastqQueue: queueAsPromised<ClaimedJob, void> | null = null;\n\n /**\n * Get the settings key for this worker's state\n */\n function getStateKey(): string {\n return `worker_state_${queue}`;\n }\n\n /**\n * Save worker state to database\n */\n async function saveState(state: WorkerState): Promise<void> {\n if (!persistState) return;\n\n const timestamp = now();\n const key = getStateKey();\n\n // Ensure settings table exists\n await db.schema\n .createTable('workmatic_settings')\n .ifNotExists()\n .addColumn('queue', 'text', (col) => col.primaryKey())\n .addColumn('paused', 'integer', (col) => col.notNull().defaultTo(0))\n .addColumn('updated_at', 'integer', (col) => col.notNull())\n .execute()\n .catch(() => {}); // Ignore if exists\n\n // Use raw SQL for upsert since different SQLite versions have different syntax\n await sql`\n INSERT INTO workmatic_settings (queue, paused, updated_at)\n VALUES (${key}, ${state === 'paused' ? 1 : state === 'running' ? 2 : 0}, ${timestamp})\n ON CONFLICT(queue) DO UPDATE SET \n paused = ${state === 'paused' ? 1 : state === 'running' ? 2 : 0},\n updated_at = ${timestamp}\n `.execute(db);\n }\n\n /**\n * Load worker state from database\n */\n async function loadState(): Promise<WorkerState | null> {\n if (!persistState) return null;\n\n try {\n const key = getStateKey();\n const result = await db\n .selectFrom('workmatic_settings')\n .select('paused')\n .where('queue', '=', key)\n .executeTakeFirst();\n\n if (!result) return null;\n \n // paused: 0 = stopped, 1 = paused, 2 = running\n if (result.paused === 2) return 'running';\n if (result.paused === 1) return 'paused';\n return 'stopped';\n } catch {\n return null;\n }\n }\n\n /**\n * Requeue jobs with expired leases\n */\n async function requeueExpiredLeases(): Promise<number> {\n const timestamp = now();\n\n const result = await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n lease_until: 0,\n updated_at: timestamp,\n })\n .where('status', '=', 'running')\n .where('lease_until', '<', timestamp)\n .where('lease_until', '>', 0)\n .execute();\n\n return Number(result[0]?.numUpdatedRows ?? 0);\n }\n\n /**\n * Claim a batch of jobs for processing\n */\n async function claimBatch(limit: number): Promise<ClaimedJob[]> {\n const timestamp = now();\n const leaseUntil = timestamp + leaseMs;\n\n // Use a transaction to ensure atomic claim\n return await db.transaction().execute(async (trx) => {\n // Select eligible jobs\n const jobs = await trx\n .selectFrom('workmatic_jobs')\n .select(['id', 'public_id', 'queue', 'payload', 'attempts', 'max_attempts'])\n .where('queue', '=', queue)\n .where('status', '=', 'ready')\n .where('run_at', '<=', timestamp)\n .orderBy('priority', 'asc')\n .orderBy('id', 'asc')\n .limit(limit)\n .execute();\n\n if (jobs.length === 0) {\n return [];\n }\n\n // Get the IDs of claimed jobs\n const jobIds = jobs.map(j => j.id);\n\n // Update them to running status\n await trx\n .updateTable('workmatic_jobs')\n .set({\n status: 'running',\n lease_until: leaseUntil,\n updated_at: timestamp,\n })\n .where('id', 'in', jobIds)\n .execute();\n\n return jobs;\n });\n }\n\n /**\n * Mark a job as completed\n */\n async function markDone(jobId: number): Promise<void> {\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'done',\n lease_until: 0,\n updated_at: now(),\n })\n .where('id', '=', jobId)\n .execute();\n }\n\n /**\n * Handle job failure\n */\n async function markFailed(\n jobId: number,\n attempts: number,\n maxAttempts: number,\n error: Error\n ): Promise<void> {\n const timestamp = now();\n const newAttempts = attempts + 1;\n const errorMessage = error.message || String(error);\n\n if (newAttempts < maxAttempts) {\n // Retry: set back to ready with backoff delay\n const runAt = timestamp + backoff(newAttempts);\n \n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'ready',\n attempts: newAttempts,\n run_at: runAt,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n } else {\n // Max attempts reached: mark as dead\n await db\n .updateTable('workmatic_jobs')\n .set({\n status: 'dead',\n attempts: newAttempts,\n lease_until: 0,\n last_error: errorMessage,\n updated_at: timestamp,\n })\n .where('id', '=', jobId)\n .execute();\n }\n }\n\n /**\n * Run a promise with timeout\n */\n async function withTimeout<T>(\n promise: Promise<T>,\n ms: number,\n jobId: string\n ): Promise<T> {\n let timeoutId: NodeJS.Timeout;\n \n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => {\n reject(new Error(`Job ${jobId} timed out after ${ms}ms`));\n }, ms);\n });\n\n try {\n return await Promise.race([promise, timeoutPromise]);\n } finally {\n clearTimeout(timeoutId!);\n }\n }\n\n /**\n * Process a single job\n */\n async function processJob(claimedJob: ClaimedJob): Promise<void> {\n if (!processor) {\n throw new Error('No processor set');\n }\n\n // Parse payload and create job object\n const payload = parsePayload(claimedJob.payload);\n const job: Job = {\n id: claimedJob.public_id,\n queue: claimedJob.queue,\n payload,\n status: 'running',\n priority: 0, // Not needed for processing\n attempts: claimedJob.attempts,\n maxAttempts: claimedJob.max_attempts,\n createdAt: 0, // Not needed for processing\n lastError: null,\n };\n\n try {\n // Run processor with optional timeout\n if (timeoutMs) {\n await withTimeout(processor(job), timeoutMs, job.id);\n } else {\n await processor(job);\n }\n await markDone(claimedJob.id);\n } catch (error) {\n await markFailed(\n claimedJob.id,\n claimedJob.attempts,\n claimedJob.max_attempts,\n error instanceof Error ? error : new Error(String(error))\n );\n }\n }\n\n /**\n * Check if queue is paused in database (for CLI control)\n */\n async function isQueuePausedInDb(): Promise<boolean> {\n const setting = await db\n .selectFrom('workmatic_settings')\n .select('paused')\n .where('queue', '=', queue)\n .executeTakeFirst();\n \n return setting?.paused === 1;\n }\n\n /**\n * Main pump loop\n */\n async function pump(): Promise<void> {\n if (!running) {\n return;\n }\n\n // Check both in-memory pause and database pause (for CLI control)\n if (paused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n try {\n // Check database pause state (allows CLI to pause running workers)\n const dbPaused = await isQueuePausedInDb();\n if (dbPaused) {\n pumpTimeout = setTimeout(pump, pollMs);\n return;\n }\n\n // Requeue expired leases\n await requeueExpiredLeases();\n\n // Claim a batch of jobs\n const batchSize = concurrency * 2;\n const jobs = await claimBatch(batchSize);\n\n if (jobs.length > 0) {\n // Push jobs into fastq\n for (const job of jobs) {\n fastqQueue!.push(job);\n }\n // Immediately pump again\n pumpTimeout = setTimeout(pump, 0);\n } else {\n // No jobs, wait before polling again\n pumpTimeout = setTimeout(pump, pollMs);\n }\n } catch (error) {\n // Log error and continue\n console.error('[workmatic] Pump error:', error);\n pumpTimeout = setTimeout(pump, pollMs);\n }\n }\n\n // Create the worker object\n const worker: WorkmaticWorker = {\n process<TPayload = unknown>(fn: JobProcessor<TPayload>): void {\n processor = fn as JobProcessor<any>;\n },\n\n start(): void {\n if (running) {\n return;\n }\n\n if (!processor) {\n throw new Error('No processor set. Call process() before start()');\n }\n\n running = true;\n paused = false;\n\n // Create fastq queue\n fastqQueue = fastq.promise(processJob, concurrency);\n\n // Save state to database\n saveState('running').catch(() => {});\n\n // Start pump loop\n pump();\n },\n\n async stop(): Promise<void> {\n if (!running) {\n return;\n }\n\n running = false;\n\n // Clear pump timeout\n if (pumpTimeout) {\n clearTimeout(pumpTimeout);\n pumpTimeout = null;\n }\n\n // Wait for fastq to drain\n if (fastqQueue) {\n await fastqQueue.drained();\n fastqQueue = null;\n }\n\n // Save state to database\n await saveState('stopped');\n },\n\n pause(): void {\n paused = true;\n saveState('paused').catch(() => {});\n },\n\n resume(): void {\n paused = false;\n saveState('running').catch(() => {});\n },\n\n async stats(): Promise<JobStats> {\n const result = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .where('queue', '=', queue)\n .groupBy('status')\n .execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of result) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n return stats;\n },\n\n get isRunning(): boolean {\n return running;\n },\n\n get isPaused(): boolean {\n return paused;\n },\n\n get queue(): string {\n return queue;\n },\n\n async restoreState(): Promise<WorkerState | null> {\n const state = await loadState();\n if (state === 'running' && processor) {\n this.start();\n } else if (state === 'paused' && processor) {\n this.start();\n this.pause();\n }\n return state;\n },\n\n async clear(options: { status?: JobStatus } = {}): Promise<number> {\n let query = db\n .deleteFrom('workmatic_jobs')\n .where('queue', '=', queue);\n\n if (options.status) {\n query = query.where('status', '=', options.status);\n }\n\n const result = await query.execute();\n return Number(result[0]?.numDeletedRows ?? 0);\n },\n };\n\n // Auto-restore state if enabled\n if (persistState && autoRestore) {\n // Use setImmediate to allow processor to be set first\n setImmediate(async () => {\n if (processor) {\n await worker.restoreState();\n }\n });\n }\n\n return worker;\n}\n","import { createServer, IncomingMessage, ServerResponse } from 'http';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\nimport { readFile } from 'fs/promises';\nimport { sql } from 'kysely';\nimport type {\n WorkmaticDb,\n DashboardOptions,\n DashboardMiddlewareOptions,\n WorkmaticDashboard,\n WorkmaticWorker,\n DashboardMiddleware,\n JobStats,\n JobStatus,\n} from './types.js';\n\n// Get the directory of this module for static file serving\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Content types for static files\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n};\n\n/**\n * Create the core request handler (shared between standalone and middleware)\n */\nfunction createRequestHandler(\n db: WorkmaticDb,\n workerMap: Map<string, WorkmaticWorker>,\n basePath: string = ''\n) {\n /**\n * Send JSON response\n */\n function sendJson(res: ServerResponse, data: unknown, status = 200): void {\n res.writeHead(status, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(data));\n }\n\n /**\n * Send error response\n */\n function sendError(res: ServerResponse, message: string, status = 500): void {\n sendJson(res, { error: message }, status);\n }\n\n /**\n * Parse URL query parameters\n */\n function parseQuery(url: string): URLSearchParams {\n const queryIndex = url.indexOf('?');\n if (queryIndex === -1) return new URLSearchParams();\n return new URLSearchParams(url.slice(queryIndex + 1));\n }\n\n /**\n * Get path from URL (removing basePath prefix)\n */\n function getPath(url: string): string {\n const queryIndex = url.indexOf('?');\n let path = queryIndex === -1 ? url : url.slice(0, queryIndex);\n \n // Remove basePath prefix if present\n if (basePath && path.startsWith(basePath)) {\n path = path.slice(basePath.length) || '/';\n }\n \n return path;\n }\n\n /**\n * API: Get jobs list\n */\n async function handleGetJobs(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n const statusFilter = query.get('status') as JobStatus | null;\n const limit = Math.min(parseInt(query.get('limit') || '50', 10), 100);\n const offset = parseInt(query.get('offset') || '0', 10);\n\n let queryBuilder = db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .orderBy('created_at', 'desc')\n .limit(limit)\n .offset(offset);\n\n if (queueFilter) {\n queryBuilder = queryBuilder.where('queue', '=', queueFilter);\n }\n if (statusFilter) {\n queryBuilder = queryBuilder.where('status', '=', statusFilter);\n }\n\n const jobs = await queryBuilder.execute();\n\n // Transform to API format\n const apiJobs = jobs.map(job => ({\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n }));\n\n sendJson(res, { jobs: apiJobs, limit, offset });\n }\n\n /**\n * API: Get statistics\n */\n async function handleGetStats(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const query = parseQuery(req.url || '');\n const queueFilter = query.get('queue');\n\n // Get counts by status\n let statsQuery = db\n .selectFrom('workmatic_jobs')\n .select([\n 'status',\n sql<number>`count(*)`.as('count'),\n ])\n .groupBy('status');\n\n if (queueFilter) {\n statsQuery = statsQuery.where('queue', '=', queueFilter);\n }\n\n const statusCounts = await statsQuery.execute();\n\n const stats: JobStats = {\n ready: 0,\n running: 0,\n done: 0,\n failed: 0,\n dead: 0,\n total: 0,\n };\n\n for (const row of statusCounts) {\n const status = row.status as JobStatus;\n const count = Number(row.count);\n if (status in stats) {\n stats[status] = count;\n }\n stats.total += count;\n }\n\n // Get queue list\n const queuesResult = await db\n .selectFrom('workmatic_jobs')\n .select('queue')\n .groupBy('queue')\n .execute();\n\n const queues = queuesResult.map(q => q.queue);\n\n // Get worker status\n const workersStatus = Array.from(workerMap.entries()).map(([queue, worker]) => ({\n queue,\n running: worker.isRunning,\n paused: worker.isPaused,\n }));\n\n sendJson(res, { stats, queues, workers: workersStatus });\n }\n\n /**\n * API: Get single job by ID\n */\n async function handleGetJob(req: IncomingMessage, res: ServerResponse, publicId: string): Promise<void> {\n const job = await db\n .selectFrom('workmatic_jobs')\n .select([\n 'public_id',\n 'queue',\n 'payload',\n 'status',\n 'priority',\n 'attempts',\n 'max_attempts',\n 'run_at',\n 'lease_until',\n 'created_at',\n 'updated_at',\n 'last_error',\n ])\n .where('public_id', '=', publicId)\n .executeTakeFirst();\n\n if (!job) {\n return sendError(res, 'Job not found', 404);\n }\n\n sendJson(res, {\n id: job.public_id,\n queue: job.queue,\n payload: JSON.parse(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n maxAttempts: job.max_attempts,\n runAt: job.run_at,\n leaseUntil: job.lease_until,\n createdAt: job.created_at,\n updatedAt: job.updated_at,\n lastError: job.last_error,\n });\n }\n\n /**\n * API: Pause worker\n */\n async function handlePauseWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.pause();\n sendJson(res, { ok: true, queue, paused: true });\n }\n\n /**\n * API: Resume worker\n */\n async function handleResumeWorker(res: ServerResponse, queue: string): Promise<void> {\n const worker = workerMap.get(queue);\n if (!worker) {\n return sendError(res, `Worker for queue '${queue}' not found`, 404);\n }\n worker.resume();\n sendJson(res, { ok: true, queue, paused: false });\n }\n\n /**\n * Serve static files from the dashboard directory\n */\n async function serveStatic(res: ServerResponse, filePath: string): Promise<void> {\n // Navigate from dist/dashboard.js to dashboard/ directory\n const dashboardDir = join(__dirname, '..', 'dashboard');\n const fullPath = join(dashboardDir, filePath);\n \n // Determine content type\n const ext = filePath.substring(filePath.lastIndexOf('.')) || '.html';\n const contentType = CONTENT_TYPES[ext] || 'application/octet-stream';\n\n try {\n const content = await readFile(fullPath, 'utf-8');\n res.writeHead(200, { 'Content-Type': contentType });\n res.end(content);\n } catch (error) {\n sendError(res, 'Not found', 404);\n }\n }\n\n /**\n * Main request handler\n */\n return async function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n next?: () => void\n ): Promise<boolean> {\n const path = getPath(req.url || '/');\n\n // Check if this request is for the dashboard\n const fullUrl = req.url || '/';\n if (basePath && !fullUrl.startsWith(basePath)) {\n // Not our route, call next middleware if available\n if (next) next();\n return false;\n }\n\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return true;\n }\n\n try {\n // API routes\n if (path === '/api/jobs' && req.method === 'GET') {\n await handleGetJobs(req, res);\n return true;\n }\n\n if (path === '/api/stats' && req.method === 'GET') {\n await handleGetStats(req, res);\n return true;\n }\n\n // Single job: /api/jobs/:id\n const jobMatch = path.match(/^\\/api\\/jobs\\/([^/]+)$/);\n if (jobMatch && req.method === 'GET') {\n await handleGetJob(req, res, jobMatch[1]);\n return true;\n }\n\n // Pause worker: /api/workers/:queue/pause\n const pauseMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/pause$/);\n if (pauseMatch && req.method === 'POST') {\n await handlePauseWorker(res, pauseMatch[1]);\n return true;\n }\n\n // Resume worker: /api/workers/:queue/resume\n const resumeMatch = path.match(/^\\/api\\/workers\\/([^/]+)\\/resume$/);\n if (resumeMatch && req.method === 'POST') {\n await handleResumeWorker(res, resumeMatch[1]);\n return true;\n }\n\n // Static files\n if (path === '/' || path === '/index.html') {\n await serveStatic(res, 'index.html');\n return true;\n }\n if (path === '/style.css') {\n await serveStatic(res, 'style.css');\n return true;\n }\n if (path === '/app.js') {\n await serveStatic(res, 'app.js');\n return true;\n }\n\n // 404 for unknown routes under our basePath\n sendError(res, 'Not found', 404);\n return true;\n } catch (error) {\n console.error('[workmatic] Dashboard error:', error);\n sendError(res, 'Internal server error', 500);\n return true;\n }\n };\n}\n\n/**\n * Create a dashboard HTTP server for monitoring and controlling the job queue\n * \n * @param options - Dashboard options\n * @returns Dashboard instance\n * \n * @example\n * ```ts\n * const dashboard = createDashboard({ \n * db, \n * port: 3000,\n * workers: [worker1, worker2] \n * });\n * \n * console.log(`Dashboard running at http://localhost:${dashboard.port}`);\n * \n * // Later, close the server\n * await dashboard.close();\n * ```\n */\nexport function createDashboard(options: DashboardOptions): WorkmaticDashboard {\n const {\n db,\n port = 3000,\n workers = [],\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap);\n\n // Create and start HTTP server\n const server = createServer((req, res) => {\n handleRequest(req, res).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n });\n });\n\n server.listen(port);\n\n return {\n async close(): Promise<void> {\n return new Promise((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n });\n },\n\n get port(): number {\n return port;\n },\n };\n}\n\n/**\n * Create an Express-compatible middleware for the dashboard\n * \n * @param options - Middleware options\n * @returns Express middleware function\n * \n * @example\n * ```ts\n * import express from 'express';\n * import { createDashboardMiddleware } from 'workmatic';\n * \n * const app = express();\n * \n * // Mount dashboard at /workmatic\n * app.use(createDashboardMiddleware({ \n * db,\n * basePath: '/workmatic',\n * workers: [worker] \n * }));\n * \n * app.listen(3000);\n * // Dashboard available at http://localhost:3000/workmatic\n * ```\n */\nexport function createDashboardMiddleware(options: DashboardMiddlewareOptions): DashboardMiddleware {\n const {\n db,\n workers = [],\n basePath = '',\n } = options;\n\n if (!db) {\n throw new Error('Database instance is required');\n }\n\n // Worker registry by queue name\n const workerMap = new Map<string, WorkmaticWorker>();\n for (const worker of workers) {\n workerMap.set(worker.queue, worker);\n }\n\n const handleRequest = createRequestHandler(db, workerMap, basePath);\n\n return (req, res, next) => {\n handleRequest(req, res, next).catch(error => {\n console.error('[workmatic] Unhandled error:', error);\n if (next) {\n next();\n } else {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Internal server error' }));\n }\n });\n };\n}\n"],"mappings":"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","names":["options","sql","sql","options","sql","__filename","__dirname"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workmatic",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "A persistent job queue for Node.js using SQLite",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",