@happyvertical/jobs 0.81.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
+ import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-Cac1FH1X.js";
2
2
  import { createId } from "@happyvertical/utils";
3
3
  //#region src/adapters/bull.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
+ import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-Cac1FH1X.js";
2
2
  import { createId } from "@happyvertical/utils";
3
3
  //#region src/adapters/bullmq.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
+ import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-Cac1FH1X.js";
2
2
  import { createId } from "@happyvertical/utils";
3
3
  //#region src/adapters/cloud-tasks.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAqB,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAEhB,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,GAAE,sBAA2B;IAWzC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YA0DnB,mBAAmB;IA4DjC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAa/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAoChD,OAAO,CACX,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IA2CX,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IA6EvD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IASpC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAqEvC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAiDjD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiD1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B;AAED,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/adapters/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,oBAAoB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAqB,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAEhB,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,8BAA8B;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;IAChD,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,GAAE,sBAA2B;IAWzC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YA0DnB,mBAAmB;IA4DjC;;;OAGG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAa/B,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAoChD,OAAO,CACX,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IA2CX,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAwFvD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IASpC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAqEvC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAiDjD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAczD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAiD1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAM7B;AAED,eAAe,gBAAgB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { r as validateTableName, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
+ import { r as validateTableName, t as BaseJobStore } from "../chunks/base-store-Cac1FH1X.js";
2
2
  import { getDatabase } from "@happyvertical/sql";
3
3
  //#region src/adapters/postgres.ts
4
4
  /**
@@ -220,8 +220,8 @@ var PostgresJobStore = class extends BaseJobStore {
220
220
  workerHeartbeat: "worker_heartbeat"
221
221
  };
222
222
  for (const [key, value] of Object.entries(updates)) {
223
+ if (!Object.hasOwn(fieldMap, key)) continue;
223
224
  const column = fieldMap[key];
224
- if (!column) continue;
225
225
  setClause.push(`${column} = $${paramIndex}`);
226
226
  if (value instanceof Date) params.push(value.toISOString());
227
227
  else if (typeof value === "object" && value !== null) params.push(JSON.stringify(value));
@@ -240,9 +240,10 @@ var PostgresJobStore = class extends BaseJobStore {
240
240
  await this.db.query(`UPDATE ${this.tableName} SET ${setClause.join(", ")} WHERE id = $${paramIndex}`, params);
241
241
  const job = await this.get(id);
242
242
  if (!job) throw new Error(`Job not found after update: ${id}`);
243
- if (updates.status === "completed") await this.emitEvent("job.completed", job, { resultPointer: job.resultPointer ?? void 0 });
244
- else if (updates.status === "failed") await this.emitEvent("job.failed", job, { error: job.lastError ?? void 0 });
245
- else if (updates.status === "cancelled") await this.emitEvent("job.cancelled", job);
243
+ const statusUpdate = Object.hasOwn(updates, "status") ? updates.status : void 0;
244
+ if (statusUpdate === "completed") await this.emitEvent("job.completed", job, { resultPointer: job.resultPointer ?? void 0 });
245
+ else if (statusUpdate === "failed") await this.emitEvent("job.failed", job, { error: job.lastError ?? void 0 });
246
+ else if (statusUpdate === "cancelled") await this.emitEvent("job.cancelled", job);
246
247
  return job;
247
248
  }
248
249
  async get(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.js","names":[],"sources":["../../src/adapters/postgres.ts"],"sourcesContent":["import { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport { BaseJobStore, validateTableName } from '../base-store.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobEventListener,\n JobFilter,\n QueueStats,\n} from '../types.js';\n\n/**\n * PostgreSQL job store configuration\n */\nexport interface PostgresJobStoreConfig {\n /** Database connection URL */\n url?: string;\n /** Existing database instance to use */\n db?: DatabaseInterface;\n /** Table name for jobs (default: '_jobs') */\n tableName?: string;\n /** Enable NOTIFY/LISTEN for push-based job retrieval */\n enableNotify?: boolean;\n /** Channel name for notifications (default: 'job_events') */\n notifyChannel?: string;\n}\n\n/**\n * PostgreSQL-based job store with NOTIFY/LISTEN support\n *\n * Uses PostgreSQL for job persistence with optional push-based\n * notifications via NOTIFY/LISTEN for efficient job retrieval.\n */\nexport class PostgresJobStore extends BaseJobStore {\n private db: DatabaseInterface | null = null;\n private readonly url: string;\n private readonly externalDb: DatabaseInterface | null;\n private readonly tableName: string;\n private readonly enableNotify: boolean;\n private readonly notifyChannel: string;\n private notifyListeners: Set<JobEventListener> = new Set();\n private listening = false;\n\n constructor(config: PostgresJobStoreConfig = {}) {\n super();\n this.url = config.url ?? process.env.DATABASE_URL ?? '';\n this.externalDb = config.db ?? null;\n this.tableName = validateTableName(config.tableName ?? '_jobs');\n this.enableNotify = config.enableNotify ?? true;\n this.notifyChannel = validateTableName(\n config.notifyChannel ?? 'job_events',\n );\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Use existing database or create new one\n this.db =\n this.externalDb ??\n (await getDatabase({ type: 'postgres', url: this.url }));\n\n // Create jobs table using raw query\n await this.db.query(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL DEFAULT 'default',\n payload JSONB NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n priority INTEGER NOT NULL DEFAULT 50,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n run_at TIMESTAMPTZ NOT NULL,\n started_at TIMESTAMPTZ,\n completed_at TIMESTAMPTZ,\n timeout INTEGER NOT NULL DEFAULT 300000,\n timeout_behavior TEXT NOT NULL DEFAULT 'fail',\n last_error TEXT,\n result_pointer TEXT,\n retry_strategy JSONB NOT NULL,\n worker_id TEXT,\n worker_heartbeat TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n )\n `);\n\n // Create indexes\n await this.db.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_dequeue\n ON ${this.tableName} (status, queue, run_at, priority DESC)\n WHERE status = 'pending'\n `);\n\n await this.db.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_created_at\n ON ${this.tableName} (created_at)\n `);\n\n await this.db.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_queue\n ON ${this.tableName} (queue)\n `);\n\n // Create notify functions and triggers if enabled\n if (this.enableNotify) {\n await this.setupNotifyTriggers();\n }\n\n this.initialized = true;\n }\n\n private async setupNotifyTriggers(): Promise<void> {\n if (!this.db) return;\n\n // Create notify function for job creation\n await this.db.query(`\n CREATE OR REPLACE FUNCTION ${this.tableName}_notify_created()\n RETURNS TRIGGER AS $$\n BEGIN\n PERFORM pg_notify('${this.notifyChannel}', json_build_object(\n 'event', 'created',\n 'id', NEW.id,\n 'queue', NEW.queue,\n 'priority', NEW.priority,\n 'run_at', NEW.run_at\n )::text);\n RETURN NEW;\n END;\n $$ LANGUAGE plpgsql\n `);\n\n // Create trigger for inserts\n await this.db.query(\n `DROP TRIGGER IF EXISTS ${this.tableName}_created_trigger ON ${this.tableName}`,\n );\n await this.db.query(`\n CREATE TRIGGER ${this.tableName}_created_trigger\n AFTER INSERT ON ${this.tableName}\n FOR EACH ROW EXECUTE FUNCTION ${this.tableName}_notify_created()\n `);\n\n // Create notify function for job ready (when run_at passes)\n await this.db.query(`\n CREATE OR REPLACE FUNCTION ${this.tableName}_notify_ready()\n RETURNS TRIGGER AS $$\n BEGIN\n IF NEW.status = 'pending' AND NEW.run_at <= NOW() AND\n (OLD.run_at > NOW() OR OLD IS NULL) THEN\n PERFORM pg_notify('${this.notifyChannel}', json_build_object(\n 'event', 'ready',\n 'id', NEW.id,\n 'queue', NEW.queue,\n 'priority', NEW.priority\n )::text);\n END IF;\n RETURN NEW;\n END;\n $$ LANGUAGE plpgsql\n `);\n\n // Create trigger for updates\n await this.db.query(\n `DROP TRIGGER IF EXISTS ${this.tableName}_ready_trigger ON ${this.tableName}`,\n );\n await this.db.query(`\n CREATE TRIGGER ${this.tableName}_ready_trigger\n AFTER UPDATE ON ${this.tableName}\n FOR EACH ROW EXECUTE FUNCTION ${this.tableName}_notify_ready()\n `);\n }\n\n /**\n * Start listening for PostgreSQL notifications\n * This enables push-based job retrieval\n */\n async startListening(): Promise<void> {\n if (!this.db || !this.enableNotify || this.listening) return;\n\n // Note: This requires the underlying pg client to support LISTEN\n // The @happyvertical/sql package may need to expose this functionality\n try {\n await this.db.query(`LISTEN ${this.notifyChannel}`);\n this.listening = true;\n } catch (error) {\n console.warn('Could not start LISTEN, falling back to polling:', error);\n }\n }\n\n async stopListening(): Promise<void> {\n if (!this.db || !this.listening) return;\n\n try {\n await this.db.query(`UNLISTEN ${this.notifyChannel}`);\n this.listening = false;\n } catch (error) {\n // Ignore errors when stopping\n }\n }\n\n async enqueue(options: JobCreateOptions): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = this.createJobRecord(options);\n\n await this.db.insert(this.tableName, {\n id: job.id,\n queue: job.queue,\n payload: JSON.stringify(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n max_attempts: job.maxAttempts,\n run_at: job.runAt.toISOString(),\n started_at: job.startedAt?.toISOString() ?? null,\n completed_at: job.completedAt?.toISOString() ?? null,\n timeout: job.timeout,\n timeout_behavior: job.timeoutBehavior,\n last_error: job.lastError,\n result_pointer: job.resultPointer,\n retry_strategy: JSON.stringify(job.retryStrategy),\n worker_id: job.workerId,\n worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,\n created_at: job.createdAt.toISOString(),\n updated_at: job.updatedAt.toISOString(),\n });\n\n await this.emitEvent('job.created', job);\n\n if (job.runAt <= new Date()) {\n await this.emitEvent('job.ready', job);\n }\n\n return job;\n }\n\n async dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n\n // Use FOR UPDATE SKIP LOCKED for concurrent-safe dequeue\n // Build PostgreSQL parameterized query\n const queuePlaceholders = queues.map((_, i) => `$${i + 1}`).join(', ');\n\n const { rows } = await this.db.query(\n `\n UPDATE ${this.tableName}\n SET status = 'running',\n worker_id = $${queues.length + 1},\n worker_heartbeat = $${queues.length + 2},\n started_at = $${queues.length + 2},\n attempts = attempts + 1,\n updated_at = $${queues.length + 2}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE status = 'pending'\n AND queue IN (${queuePlaceholders})\n AND run_at <= $${queues.length + 2}\n ORDER BY priority DESC, run_at ASC\n LIMIT $${queues.length + 3}\n FOR UPDATE SKIP LOCKED\n )\n RETURNING *\n `,\n [...queues, workerId, now, limit],\n );\n\n const jobs = rows.map((row) =>\n this.parseJobRow(row as Record<string, unknown>),\n );\n\n for (const job of jobs) {\n await this.emitEvent('job.started', job);\n }\n\n return jobs;\n }\n\n async update(id: string, updates: Partial<Job>): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const setClause: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n const fieldMap: Record<string, string> = {\n queue: 'queue',\n payload: 'payload',\n status: 'status',\n priority: 'priority',\n attempts: 'attempts',\n maxAttempts: 'max_attempts',\n runAt: 'run_at',\n startedAt: 'started_at',\n completedAt: 'completed_at',\n timeout: 'timeout',\n timeoutBehavior: 'timeout_behavior',\n lastError: 'last_error',\n resultPointer: 'result_pointer',\n retryStrategy: 'retry_strategy',\n workerId: 'worker_id',\n workerHeartbeat: 'worker_heartbeat',\n };\n\n for (const [key, value] of Object.entries(updates)) {\n const column = fieldMap[key];\n if (!column) continue;\n\n setClause.push(`${column} = $${paramIndex}`);\n\n if (value instanceof Date) {\n params.push(value.toISOString());\n } else if (typeof value === 'object' && value !== null) {\n params.push(JSON.stringify(value));\n } else {\n params.push(value);\n }\n paramIndex++;\n }\n\n if (setClause.length === 0) {\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n return job;\n }\n\n setClause.push(`updated_at = $${paramIndex}`);\n params.push(new Date().toISOString());\n paramIndex++;\n\n params.push(id);\n\n await this.db.query(\n `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE id = $${paramIndex}`,\n params,\n );\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found after update: ${id}`);\n\n if (updates.status === 'completed') {\n await this.emitEvent('job.completed', job, {\n resultPointer: job.resultPointer ?? undefined,\n });\n } else if (updates.status === 'failed') {\n await this.emitEvent('job.failed', job, {\n error: job.lastError ?? undefined,\n });\n } else if (updates.status === 'cancelled') {\n await this.emitEvent('job.cancelled', job);\n }\n\n return job;\n }\n\n async get(id: string): Promise<Job | null> {\n if (!this.db) throw new Error('Store not initialized');\n\n const row = await this.db.get(this.tableName, { id });\n if (!row) return null;\n\n return this.parseJobRow(row as Record<string, unknown>);\n }\n\n async list(filter: JobFilter): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n // Build query with PostgreSQL parameter syntax\n const conditions: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n if (filter.queue) {\n conditions.push(`queue = $${paramIndex++}`);\n params.push(filter.queue);\n }\n\n if (filter.status) {\n if (Array.isArray(filter.status)) {\n const placeholders = filter.status\n .map(() => `$${paramIndex++}`)\n .join(', ');\n conditions.push(`status IN (${placeholders})`);\n params.push(...filter.status);\n } else {\n conditions.push(`status = $${paramIndex++}`);\n params.push(filter.status);\n }\n }\n\n if (filter.objectType) {\n conditions.push(`payload->>'objectType' = $${paramIndex++}`);\n params.push(filter.objectType);\n }\n\n if (filter.method) {\n conditions.push(`payload->>'method' = $${paramIndex++}`);\n params.push(filter.method);\n }\n\n if (filter.createdAfter) {\n conditions.push(`created_at > $${paramIndex++}`);\n params.push(filter.createdAfter.toISOString());\n }\n\n if (filter.createdBefore) {\n conditions.push(`created_at < $${paramIndex++}`);\n params.push(filter.createdBefore.toISOString());\n }\n\n const where =\n conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const orderBy = this.buildOrderBy(filter);\n\n let limitOffset = '';\n if (filter.limit) {\n limitOffset = `LIMIT $${paramIndex++}`;\n params.push(filter.limit);\n\n if (filter.offset) {\n limitOffset += ` OFFSET $${paramIndex++}`;\n params.push(filter.offset);\n }\n }\n\n const { rows } = await this.db.query(\n `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,\n params,\n );\n\n return rows.map((row) => this.parseJobRow(row as Record<string, unknown>));\n }\n\n async cancel(id: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n\n if (job.status === 'completed' || job.status === 'cancelled') {\n throw new Error(`Cannot cancel job with status: ${job.status}`);\n }\n\n await this.update(id, {\n status: 'cancelled',\n completedAt: new Date(),\n });\n }\n\n async cleanup(options: CleanupOptions): Promise<number> {\n if (!this.db) throw new Error('Store not initialized');\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n if (options.completedBefore) {\n conditions.push(\n `(status = 'completed' AND completed_at < $${paramIndex++})`,\n );\n params.push(options.completedBefore.toISOString());\n }\n\n if (options.failedBefore) {\n conditions.push(\n `(status = 'failed' AND completed_at < $${paramIndex++})`,\n );\n params.push(options.failedBefore.toISOString());\n }\n\n if (options.cancelledBefore) {\n conditions.push(\n `(status = 'cancelled' AND completed_at < $${paramIndex++})`,\n );\n params.push(options.cancelledBefore.toISOString());\n }\n\n if (conditions.length === 0) return 0;\n\n let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(' OR ')})`;\n\n if (options.limit) {\n query = `\n DELETE FROM ${this.tableName}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE (${conditions.join(' OR ')})\n LIMIT $${paramIndex}\n )\n `;\n params.push(options.limit);\n }\n\n const result = await this.db.query(query, params);\n\n return result.rowCount ?? 0;\n }\n\n async heartbeat(jobId: string, workerId: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET worker_heartbeat = $1, updated_at = $1\n WHERE id = $2 AND worker_id = $3 AND status = 'running'\n `,\n [now, jobId, workerId],\n );\n }\n\n async stats(queue?: string): Promise<QueueStats> {\n if (!this.db) throw new Error('Store not initialized');\n\n const params: unknown[] = [];\n let paramIndex = 1;\n const queueFilter = queue ? `WHERE queue = $${paramIndex++}` : '';\n if (queue) params.push(queue);\n\n const { rows: countRows } = await this.db.query(\n `\n SELECT status, COUNT(*)::int as count\n FROM ${this.tableName}\n ${queueFilter}\n GROUP BY status\n `,\n params,\n );\n\n const counts: Record<string, number> = {};\n for (const row of countRows) {\n counts[row.status as string] = row.count as number;\n }\n\n const { rows: durationRows } = await this.db.query(\n `\n SELECT AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) * 1000)::float as avg_duration\n FROM ${this.tableName}\n WHERE status = 'completed'\n AND started_at IS NOT NULL\n AND completed_at IS NOT NULL\n ${queue ? `AND queue = $1` : ''}\n `,\n queue ? [queue] : [],\n );\n\n const avgDuration =\n (durationRows[0] as { avg_duration: number | null })?.avg_duration ??\n null;\n\n return {\n pending: counts['pending'] ?? 0,\n running: counts['running'] ?? 0,\n completed: counts['completed'] ?? 0,\n failed: counts['failed'] ?? 0,\n cancelled: counts['cancelled'] ?? 0,\n avgDuration: avgDuration ? Math.round(avgDuration) : null,\n };\n }\n\n async close(): Promise<void> {\n await this.stopListening();\n // The SDK's DatabaseInterface doesn't have a close method\n this.db = null;\n this.initialized = false;\n }\n}\n\nexport default PostgresJobStore;\n"],"mappings":"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"}
1
+ {"version":3,"file":"postgres.js","names":[],"sources":["../../src/adapters/postgres.ts"],"sourcesContent":["import { type DatabaseInterface, getDatabase } from '@happyvertical/sql';\nimport { BaseJobStore, validateTableName } from '../base-store.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobEventListener,\n JobFilter,\n QueueStats,\n} from '../types.js';\n\n/**\n * PostgreSQL job store configuration\n */\nexport interface PostgresJobStoreConfig {\n /** Database connection URL */\n url?: string;\n /** Existing database instance to use */\n db?: DatabaseInterface;\n /** Table name for jobs (default: '_jobs') */\n tableName?: string;\n /** Enable NOTIFY/LISTEN for push-based job retrieval */\n enableNotify?: boolean;\n /** Channel name for notifications (default: 'job_events') */\n notifyChannel?: string;\n}\n\n/**\n * PostgreSQL-based job store with NOTIFY/LISTEN support\n *\n * Uses PostgreSQL for job persistence with optional push-based\n * notifications via NOTIFY/LISTEN for efficient job retrieval.\n */\nexport class PostgresJobStore extends BaseJobStore {\n private db: DatabaseInterface | null = null;\n private readonly url: string;\n private readonly externalDb: DatabaseInterface | null;\n private readonly tableName: string;\n private readonly enableNotify: boolean;\n private readonly notifyChannel: string;\n private notifyListeners: Set<JobEventListener> = new Set();\n private listening = false;\n\n constructor(config: PostgresJobStoreConfig = {}) {\n super();\n this.url = config.url ?? process.env.DATABASE_URL ?? '';\n this.externalDb = config.db ?? null;\n this.tableName = validateTableName(config.tableName ?? '_jobs');\n this.enableNotify = config.enableNotify ?? true;\n this.notifyChannel = validateTableName(\n config.notifyChannel ?? 'job_events',\n );\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Use existing database or create new one\n this.db =\n this.externalDb ??\n (await getDatabase({ type: 'postgres', url: this.url }));\n\n // Create jobs table using raw query\n await this.db.query(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL DEFAULT 'default',\n payload JSONB NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n priority INTEGER NOT NULL DEFAULT 50,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n run_at TIMESTAMPTZ NOT NULL,\n started_at TIMESTAMPTZ,\n completed_at TIMESTAMPTZ,\n timeout INTEGER NOT NULL DEFAULT 300000,\n timeout_behavior TEXT NOT NULL DEFAULT 'fail',\n last_error TEXT,\n result_pointer TEXT,\n retry_strategy JSONB NOT NULL,\n worker_id TEXT,\n worker_heartbeat TIMESTAMPTZ,\n created_at TIMESTAMPTZ NOT NULL,\n updated_at TIMESTAMPTZ NOT NULL\n )\n `);\n\n // Create indexes\n await this.db.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_dequeue\n ON ${this.tableName} (status, queue, run_at, priority DESC)\n WHERE status = 'pending'\n `);\n\n await this.db.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_created_at\n ON ${this.tableName} (created_at)\n `);\n\n await this.db.query(`\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_queue\n ON ${this.tableName} (queue)\n `);\n\n // Create notify functions and triggers if enabled\n if (this.enableNotify) {\n await this.setupNotifyTriggers();\n }\n\n this.initialized = true;\n }\n\n private async setupNotifyTriggers(): Promise<void> {\n if (!this.db) return;\n\n // Create notify function for job creation\n await this.db.query(`\n CREATE OR REPLACE FUNCTION ${this.tableName}_notify_created()\n RETURNS TRIGGER AS $$\n BEGIN\n PERFORM pg_notify('${this.notifyChannel}', json_build_object(\n 'event', 'created',\n 'id', NEW.id,\n 'queue', NEW.queue,\n 'priority', NEW.priority,\n 'run_at', NEW.run_at\n )::text);\n RETURN NEW;\n END;\n $$ LANGUAGE plpgsql\n `);\n\n // Create trigger for inserts\n await this.db.query(\n `DROP TRIGGER IF EXISTS ${this.tableName}_created_trigger ON ${this.tableName}`,\n );\n await this.db.query(`\n CREATE TRIGGER ${this.tableName}_created_trigger\n AFTER INSERT ON ${this.tableName}\n FOR EACH ROW EXECUTE FUNCTION ${this.tableName}_notify_created()\n `);\n\n // Create notify function for job ready (when run_at passes)\n await this.db.query(`\n CREATE OR REPLACE FUNCTION ${this.tableName}_notify_ready()\n RETURNS TRIGGER AS $$\n BEGIN\n IF NEW.status = 'pending' AND NEW.run_at <= NOW() AND\n (OLD.run_at > NOW() OR OLD IS NULL) THEN\n PERFORM pg_notify('${this.notifyChannel}', json_build_object(\n 'event', 'ready',\n 'id', NEW.id,\n 'queue', NEW.queue,\n 'priority', NEW.priority\n )::text);\n END IF;\n RETURN NEW;\n END;\n $$ LANGUAGE plpgsql\n `);\n\n // Create trigger for updates\n await this.db.query(\n `DROP TRIGGER IF EXISTS ${this.tableName}_ready_trigger ON ${this.tableName}`,\n );\n await this.db.query(`\n CREATE TRIGGER ${this.tableName}_ready_trigger\n AFTER UPDATE ON ${this.tableName}\n FOR EACH ROW EXECUTE FUNCTION ${this.tableName}_notify_ready()\n `);\n }\n\n /**\n * Start listening for PostgreSQL notifications\n * This enables push-based job retrieval\n */\n async startListening(): Promise<void> {\n if (!this.db || !this.enableNotify || this.listening) return;\n\n // Note: This requires the underlying pg client to support LISTEN\n // The @happyvertical/sql package may need to expose this functionality\n try {\n await this.db.query(`LISTEN ${this.notifyChannel}`);\n this.listening = true;\n } catch (error) {\n console.warn('Could not start LISTEN, falling back to polling:', error);\n }\n }\n\n async stopListening(): Promise<void> {\n if (!this.db || !this.listening) return;\n\n try {\n await this.db.query(`UNLISTEN ${this.notifyChannel}`);\n this.listening = false;\n } catch (error) {\n // Ignore errors when stopping\n }\n }\n\n async enqueue(options: JobCreateOptions): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = this.createJobRecord(options);\n\n await this.db.insert(this.tableName, {\n id: job.id,\n queue: job.queue,\n payload: JSON.stringify(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n max_attempts: job.maxAttempts,\n run_at: job.runAt.toISOString(),\n started_at: job.startedAt?.toISOString() ?? null,\n completed_at: job.completedAt?.toISOString() ?? null,\n timeout: job.timeout,\n timeout_behavior: job.timeoutBehavior,\n last_error: job.lastError,\n result_pointer: job.resultPointer,\n retry_strategy: JSON.stringify(job.retryStrategy),\n worker_id: job.workerId,\n worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,\n created_at: job.createdAt.toISOString(),\n updated_at: job.updatedAt.toISOString(),\n });\n\n await this.emitEvent('job.created', job);\n\n if (job.runAt <= new Date()) {\n await this.emitEvent('job.ready', job);\n }\n\n return job;\n }\n\n async dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n\n // Use FOR UPDATE SKIP LOCKED for concurrent-safe dequeue\n // Build PostgreSQL parameterized query\n const queuePlaceholders = queues.map((_, i) => `$${i + 1}`).join(', ');\n\n const { rows } = await this.db.query(\n `\n UPDATE ${this.tableName}\n SET status = 'running',\n worker_id = $${queues.length + 1},\n worker_heartbeat = $${queues.length + 2},\n started_at = $${queues.length + 2},\n attempts = attempts + 1,\n updated_at = $${queues.length + 2}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE status = 'pending'\n AND queue IN (${queuePlaceholders})\n AND run_at <= $${queues.length + 2}\n ORDER BY priority DESC, run_at ASC\n LIMIT $${queues.length + 3}\n FOR UPDATE SKIP LOCKED\n )\n RETURNING *\n `,\n [...queues, workerId, now, limit],\n );\n\n const jobs = rows.map((row) =>\n this.parseJobRow(row as Record<string, unknown>),\n );\n\n for (const job of jobs) {\n await this.emitEvent('job.started', job);\n }\n\n return jobs;\n }\n\n async update(id: string, updates: Partial<Job>): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const setClause: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n const fieldMap: Record<string, string> = {\n queue: 'queue',\n payload: 'payload',\n status: 'status',\n priority: 'priority',\n attempts: 'attempts',\n maxAttempts: 'max_attempts',\n runAt: 'run_at',\n startedAt: 'started_at',\n completedAt: 'completed_at',\n timeout: 'timeout',\n timeoutBehavior: 'timeout_behavior',\n lastError: 'last_error',\n resultPointer: 'result_pointer',\n retryStrategy: 'retry_strategy',\n workerId: 'worker_id',\n workerHeartbeat: 'worker_heartbeat',\n };\n\n for (const [key, value] of Object.entries(updates)) {\n // `hasOwn`, not `if (!fieldMap[key])`: the fieldMap literal inherits\n // from Object.prototype, so keys like 'constructor' resolve to a truthy\n // inherited value whose column would be interpolated into the SET clause.\n // An own-property check drops unknown keys instead of injecting them.\n if (!Object.hasOwn(fieldMap, key)) continue;\n const column = fieldMap[key];\n\n setClause.push(`${column} = $${paramIndex}`);\n\n if (value instanceof Date) {\n params.push(value.toISOString());\n } else if (typeof value === 'object' && value !== null) {\n params.push(JSON.stringify(value));\n } else {\n params.push(value);\n }\n paramIndex++;\n }\n\n if (setClause.length === 0) {\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n return job;\n }\n\n setClause.push(`updated_at = $${paramIndex}`);\n params.push(new Date().toISOString());\n paramIndex++;\n\n params.push(id);\n\n await this.db.query(\n `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE id = $${paramIndex}`,\n params,\n );\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found after update: ${id}`);\n\n // Emit events based on status changes. Read `status` only when it is an\n // own key of `updates`: a bare `updates.status` traverses the prototype\n // chain, so a polluted Object.prototype.status could make an unrelated\n // update emit a spurious lifecycle event.\n const statusUpdate = Object.hasOwn(updates, 'status')\n ? updates.status\n : undefined;\n if (statusUpdate === 'completed') {\n await this.emitEvent('job.completed', job, {\n resultPointer: job.resultPointer ?? undefined,\n });\n } else if (statusUpdate === 'failed') {\n await this.emitEvent('job.failed', job, {\n error: job.lastError ?? undefined,\n });\n } else if (statusUpdate === 'cancelled') {\n await this.emitEvent('job.cancelled', job);\n }\n\n return job;\n }\n\n async get(id: string): Promise<Job | null> {\n if (!this.db) throw new Error('Store not initialized');\n\n const row = await this.db.get(this.tableName, { id });\n if (!row) return null;\n\n return this.parseJobRow(row as Record<string, unknown>);\n }\n\n async list(filter: JobFilter): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n // Build query with PostgreSQL parameter syntax\n const conditions: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n if (filter.queue) {\n conditions.push(`queue = $${paramIndex++}`);\n params.push(filter.queue);\n }\n\n if (filter.status) {\n if (Array.isArray(filter.status)) {\n const placeholders = filter.status\n .map(() => `$${paramIndex++}`)\n .join(', ');\n conditions.push(`status IN (${placeholders})`);\n params.push(...filter.status);\n } else {\n conditions.push(`status = $${paramIndex++}`);\n params.push(filter.status);\n }\n }\n\n if (filter.objectType) {\n conditions.push(`payload->>'objectType' = $${paramIndex++}`);\n params.push(filter.objectType);\n }\n\n if (filter.method) {\n conditions.push(`payload->>'method' = $${paramIndex++}`);\n params.push(filter.method);\n }\n\n if (filter.createdAfter) {\n conditions.push(`created_at > $${paramIndex++}`);\n params.push(filter.createdAfter.toISOString());\n }\n\n if (filter.createdBefore) {\n conditions.push(`created_at < $${paramIndex++}`);\n params.push(filter.createdBefore.toISOString());\n }\n\n const where =\n conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';\n const orderBy = this.buildOrderBy(filter);\n\n let limitOffset = '';\n if (filter.limit) {\n limitOffset = `LIMIT $${paramIndex++}`;\n params.push(filter.limit);\n\n if (filter.offset) {\n limitOffset += ` OFFSET $${paramIndex++}`;\n params.push(filter.offset);\n }\n }\n\n const { rows } = await this.db.query(\n `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,\n params,\n );\n\n return rows.map((row) => this.parseJobRow(row as Record<string, unknown>));\n }\n\n async cancel(id: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n\n if (job.status === 'completed' || job.status === 'cancelled') {\n throw new Error(`Cannot cancel job with status: ${job.status}`);\n }\n\n await this.update(id, {\n status: 'cancelled',\n completedAt: new Date(),\n });\n }\n\n async cleanup(options: CleanupOptions): Promise<number> {\n if (!this.db) throw new Error('Store not initialized');\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n let paramIndex = 1;\n\n if (options.completedBefore) {\n conditions.push(\n `(status = 'completed' AND completed_at < $${paramIndex++})`,\n );\n params.push(options.completedBefore.toISOString());\n }\n\n if (options.failedBefore) {\n conditions.push(\n `(status = 'failed' AND completed_at < $${paramIndex++})`,\n );\n params.push(options.failedBefore.toISOString());\n }\n\n if (options.cancelledBefore) {\n conditions.push(\n `(status = 'cancelled' AND completed_at < $${paramIndex++})`,\n );\n params.push(options.cancelledBefore.toISOString());\n }\n\n if (conditions.length === 0) return 0;\n\n let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(' OR ')})`;\n\n if (options.limit) {\n query = `\n DELETE FROM ${this.tableName}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE (${conditions.join(' OR ')})\n LIMIT $${paramIndex}\n )\n `;\n params.push(options.limit);\n }\n\n const result = await this.db.query(query, params);\n\n return result.rowCount ?? 0;\n }\n\n async heartbeat(jobId: string, workerId: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET worker_heartbeat = $1, updated_at = $1\n WHERE id = $2 AND worker_id = $3 AND status = 'running'\n `,\n [now, jobId, workerId],\n );\n }\n\n async stats(queue?: string): Promise<QueueStats> {\n if (!this.db) throw new Error('Store not initialized');\n\n const params: unknown[] = [];\n let paramIndex = 1;\n const queueFilter = queue ? `WHERE queue = $${paramIndex++}` : '';\n if (queue) params.push(queue);\n\n const { rows: countRows } = await this.db.query(\n `\n SELECT status, COUNT(*)::int as count\n FROM ${this.tableName}\n ${queueFilter}\n GROUP BY status\n `,\n params,\n );\n\n const counts: Record<string, number> = {};\n for (const row of countRows) {\n counts[row.status as string] = row.count as number;\n }\n\n const { rows: durationRows } = await this.db.query(\n `\n SELECT AVG(EXTRACT(EPOCH FROM (completed_at - started_at)) * 1000)::float as avg_duration\n FROM ${this.tableName}\n WHERE status = 'completed'\n AND started_at IS NOT NULL\n AND completed_at IS NOT NULL\n ${queue ? `AND queue = $1` : ''}\n `,\n queue ? [queue] : [],\n );\n\n const avgDuration =\n (durationRows[0] as { avg_duration: number | null })?.avg_duration ??\n null;\n\n return {\n pending: counts['pending'] ?? 0,\n running: counts['running'] ?? 0,\n completed: counts['completed'] ?? 0,\n failed: counts['failed'] ?? 0,\n cancelled: counts['cancelled'] ?? 0,\n avgDuration: avgDuration ? Math.round(avgDuration) : null,\n };\n }\n\n async close(): Promise<void> {\n await this.stopListening();\n // The SDK's DatabaseInterface doesn't have a close method\n this.db = null;\n this.initialized = false;\n }\n}\n\nexport default PostgresJobStore;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,yBAAyB,EAE/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAqB,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAChB,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,YAAY,CAAC,EAAE,yBAAyB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwC;gBAEzD,MAAM,GAAE,oBAAyB;IAQvC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C3B,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAqChD,OAAO,CACX,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IA2DX,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IA6EvD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IASpC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAgBvC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IA2CjD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmD1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAU1D;AAED,eAAe,cAAc,CAAC"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/adapters/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,iBAAiB,EAEtB,KAAK,yBAAyB,EAE/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAqB,MAAM,kBAAkB,CAAC;AACnE,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAChB,SAAS,EACT,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,EAAE,CAAC,EAAE,iBAAiB,CAAC;IACvB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,YAAY,CAAC,EAAE,yBAAyB,CAAC;CAC1C;AAED;;;;;GAKG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwC;gBAEzD,MAAM,GAAE,oBAAyB;IAQvC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA8C3B,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IAqChD,OAAO,CACX,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IA2DX,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAuFvD,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IASpC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAgBvC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IA2CjD,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazD,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAmD1C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQtB,aAAa,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAU1D;AAED,eAAe,cAAc,CAAC"}
@@ -1,4 +1,4 @@
1
- import { r as validateTableName, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
+ import { r as validateTableName, t as BaseJobStore } from "../chunks/base-store-Cac1FH1X.js";
2
2
  import { getDatabase, syncSchema } from "@happyvertical/sql";
3
3
  //#region src/adapters/sqlite.ts
4
4
  /**
@@ -159,8 +159,8 @@ var SqliteJobStore = class extends BaseJobStore {
159
159
  workerHeartbeat: "worker_heartbeat"
160
160
  };
161
161
  for (const [key, value] of Object.entries(updates)) {
162
+ if (!Object.hasOwn(fieldMap, key)) continue;
162
163
  const column = fieldMap[key];
163
- if (!column) continue;
164
164
  setClause.push(`${column} = ?`);
165
165
  if (value instanceof Date) params.push(value.toISOString());
166
166
  else if (typeof value === "object" && value !== null) params.push(JSON.stringify(value));
@@ -177,9 +177,10 @@ var SqliteJobStore = class extends BaseJobStore {
177
177
  await this.db.query(`UPDATE ${this.tableName} SET ${setClause.join(", ")} WHERE id = ?`, params);
178
178
  const job = await this.get(id);
179
179
  if (!job) throw new Error(`Job not found after update: ${id}`);
180
- if (updates.status === "completed") await this.emitEvent("job.completed", job, { resultPointer: job.resultPointer ?? void 0 });
181
- else if (updates.status === "failed") await this.emitEvent("job.failed", job, { error: job.lastError ?? void 0 });
182
- else if (updates.status === "cancelled") await this.emitEvent("job.cancelled", job);
180
+ const statusUpdate = Object.hasOwn(updates, "status") ? updates.status : void 0;
181
+ if (statusUpdate === "completed") await this.emitEvent("job.completed", job, { resultPointer: job.resultPointer ?? void 0 });
182
+ else if (statusUpdate === "failed") await this.emitEvent("job.failed", job, { error: job.lastError ?? void 0 });
183
+ else if (statusUpdate === "cancelled") await this.emitEvent("job.cancelled", job);
183
184
  return job;
184
185
  }
185
186
  async get(id) {
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.js","names":[],"sources":["../../src/adapters/sqlite.ts"],"sourcesContent":["import {\n type DatabaseInterface,\n getDatabase,\n type SqliteCapabilitiesOptions,\n syncSchema,\n} from '@happyvertical/sql';\nimport { BaseJobStore, validateTableName } from '../base-store.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobFilter,\n QueueStats,\n} from '../types.js';\n\n/**\n * SQLite job store configuration\n */\nexport interface SqliteJobStoreConfig {\n /** Database URL or path (default: ':memory:') */\n url?: string;\n /** Existing database instance to use */\n db?: DatabaseInterface;\n /** Table name for jobs (default: '_jobs') */\n tableName?: string;\n /** Optional SQLite native capabilities for development/test modes */\n capabilities?: SqliteCapabilitiesOptions;\n}\n\n/**\n * SQLite-based job store\n *\n * Uses SQLite for job persistence. Supports polling-based job retrieval.\n * Good for single-instance deployments or development.\n */\nexport class SqliteJobStore extends BaseJobStore {\n private db: DatabaseInterface | null = null;\n private readonly url: string;\n private readonly externalDb: DatabaseInterface | null;\n private readonly tableName: string;\n private readonly capabilities: SqliteCapabilitiesOptions | undefined;\n\n constructor(config: SqliteJobStoreConfig = {}) {\n super();\n this.url = config.url ?? ':memory:';\n this.externalDb = config.db ?? null;\n this.tableName = validateTableName(config.tableName ?? '_jobs');\n this.capabilities = config.capabilities;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Use existing database or create new one\n this.db =\n this.externalDb ??\n (await getDatabase({\n type: 'sqlite',\n url: this.url,\n capabilities: this.capabilities,\n }));\n\n // Create jobs table and indexes using syncSchema\n const schema = `\n CREATE TABLE IF NOT EXISTS \"${this.tableName}\" (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL DEFAULT 'default',\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n priority INTEGER NOT NULL DEFAULT 50,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n run_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n timeout INTEGER NOT NULL DEFAULT 300000,\n timeout_behavior TEXT NOT NULL DEFAULT 'fail',\n last_error TEXT,\n result_pointer TEXT,\n retry_strategy TEXT NOT NULL,\n worker_id TEXT,\n worker_heartbeat TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_status_queue\" ON \"${this.tableName}\" (status, queue, run_at, priority DESC);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_created_at\" ON \"${this.tableName}\" (created_at);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_queue\" ON \"${this.tableName}\" (queue);\n `;\n\n await syncSchema({ db: this.db, schema });\n\n this.initialized = true;\n }\n\n async enqueue(options: JobCreateOptions): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = this.createJobRecord(options);\n\n await this.db.insert(this.tableName, {\n id: job.id,\n queue: job.queue,\n payload: JSON.stringify(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n max_attempts: job.maxAttempts,\n run_at: job.runAt.toISOString(),\n started_at: job.startedAt?.toISOString() ?? null,\n completed_at: job.completedAt?.toISOString() ?? null,\n timeout: job.timeout,\n timeout_behavior: job.timeoutBehavior,\n last_error: job.lastError,\n result_pointer: job.resultPointer,\n retry_strategy: JSON.stringify(job.retryStrategy),\n worker_id: job.workerId,\n worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,\n created_at: job.createdAt.toISOString(),\n updated_at: job.updatedAt.toISOString(),\n });\n\n await this.emitEvent('job.created', job);\n\n // Also emit ready if job should run immediately\n if (job.runAt <= new Date()) {\n await this.emitEvent('job.ready', job);\n }\n\n return job;\n }\n\n async dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n const queuePlaceholders = queues.map(() => '?').join(', ');\n\n // Find jobs ready to process\n const { rows } = await this.db.query(\n `\n SELECT * FROM ${this.tableName}\n WHERE status = 'pending'\n AND queue IN (${queuePlaceholders})\n AND run_at <= ?\n ORDER BY priority DESC, run_at ASC\n LIMIT ?\n `,\n [...queues, now, limit],\n );\n\n if (!rows.length) return [];\n\n // Claim the jobs by updating their status\n const jobIds = rows.map((r) => r.id as string);\n const idPlaceholders = jobIds.map(() => '?').join(', ');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET status = 'running',\n worker_id = ?,\n worker_heartbeat = ?,\n started_at = ?,\n attempts = attempts + 1,\n updated_at = ?\n WHERE id IN (${idPlaceholders})\n AND status = 'pending'\n `,\n [workerId, now, now, now, ...jobIds],\n );\n\n // Return the claimed jobs\n const jobs = rows.map((row) => {\n const job = this.parseJobRow(row as Record<string, unknown>);\n job.status = 'running';\n job.workerId = workerId;\n job.workerHeartbeat = new Date();\n job.startedAt = new Date();\n job.attempts += 1;\n return job;\n });\n\n // Emit started events\n for (const job of jobs) {\n await this.emitEvent('job.started', job);\n }\n\n return jobs;\n }\n\n async update(id: string, updates: Partial<Job>): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const setClause: string[] = [];\n const params: unknown[] = [];\n\n // Map Job fields to database columns\n const fieldMap: Record<string, string> = {\n queue: 'queue',\n payload: 'payload',\n status: 'status',\n priority: 'priority',\n attempts: 'attempts',\n maxAttempts: 'max_attempts',\n runAt: 'run_at',\n startedAt: 'started_at',\n completedAt: 'completed_at',\n timeout: 'timeout',\n timeoutBehavior: 'timeout_behavior',\n lastError: 'last_error',\n resultPointer: 'result_pointer',\n retryStrategy: 'retry_strategy',\n workerId: 'worker_id',\n workerHeartbeat: 'worker_heartbeat',\n };\n\n for (const [key, value] of Object.entries(updates)) {\n const column = fieldMap[key];\n if (!column) continue;\n\n setClause.push(`${column} = ?`);\n\n if (value instanceof Date) {\n params.push(value.toISOString());\n } else if (typeof value === 'object' && value !== null) {\n params.push(JSON.stringify(value));\n } else {\n params.push(value);\n }\n }\n\n if (setClause.length === 0) {\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n return job;\n }\n\n // Always update updated_at\n setClause.push('updated_at = ?');\n params.push(new Date().toISOString());\n\n params.push(id);\n\n await this.db.query(\n `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE id = ?`,\n params,\n );\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found after update: ${id}`);\n\n // Emit events based on status changes\n if (updates.status === 'completed') {\n await this.emitEvent('job.completed', job, {\n resultPointer: job.resultPointer ?? undefined,\n });\n } else if (updates.status === 'failed') {\n await this.emitEvent('job.failed', job, {\n error: job.lastError ?? undefined,\n });\n } else if (updates.status === 'cancelled') {\n await this.emitEvent('job.cancelled', job);\n }\n\n return job;\n }\n\n async get(id: string): Promise<Job | null> {\n if (!this.db) throw new Error('Store not initialized');\n\n const row = await this.db.get(this.tableName, { id });\n if (!row) return null;\n\n return this.parseJobRow(row as Record<string, unknown>);\n }\n\n async list(filter: JobFilter): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const { where, params: whereParams } = this.buildFilterWhere(filter);\n const orderBy = this.buildOrderBy(filter);\n const { clause: limitOffset, params: limitParams } =\n this.buildLimitOffset(filter);\n\n const { rows } = await this.db.query(\n `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,\n [...whereParams, ...limitParams],\n );\n\n return rows.map((row) => this.parseJobRow(row as Record<string, unknown>));\n }\n\n async cancel(id: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n\n if (job.status === 'completed' || job.status === 'cancelled') {\n throw new Error(`Cannot cancel job with status: ${job.status}`);\n }\n\n await this.update(id, {\n status: 'cancelled',\n completedAt: new Date(),\n });\n }\n\n async cleanup(options: CleanupOptions): Promise<number> {\n if (!this.db) throw new Error('Store not initialized');\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (options.completedBefore) {\n conditions.push(\"(status = 'completed' AND completed_at < ?)\");\n params.push(options.completedBefore.toISOString());\n }\n\n if (options.failedBefore) {\n conditions.push(\"(status = 'failed' AND completed_at < ?)\");\n params.push(options.failedBefore.toISOString());\n }\n\n if (options.cancelledBefore) {\n conditions.push(\"(status = 'cancelled' AND completed_at < ?)\");\n params.push(options.cancelledBefore.toISOString());\n }\n\n if (conditions.length === 0) return 0;\n\n let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(' OR ')})`;\n\n if (options.limit) {\n // SQLite doesn't support LIMIT in DELETE directly, so we need a subquery\n query = `\n DELETE FROM ${this.tableName}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE (${conditions.join(' OR ')})\n LIMIT ?\n )\n `;\n params.push(options.limit);\n }\n\n const result = await this.db.query(query, params);\n\n return result.rowCount ?? 0;\n }\n\n async heartbeat(jobId: string, workerId: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET worker_heartbeat = ?, updated_at = ?\n WHERE id = ? AND worker_id = ? AND status = 'running'\n `,\n [new Date().toISOString(), new Date().toISOString(), jobId, workerId],\n );\n }\n\n async stats(queue?: string): Promise<QueueStats> {\n if (!this.db) throw new Error('Store not initialized');\n\n const queueFilter = queue ? 'WHERE queue = ?' : '';\n const params = queue ? [queue] : [];\n\n // Get status counts\n const { rows: countRows } = await this.db.query(\n `\n SELECT status, COUNT(*) as count\n FROM ${this.tableName}\n ${queueFilter}\n GROUP BY status\n `,\n params,\n );\n\n const counts: Record<string, number> = {};\n for (const row of countRows) {\n counts[row.status as string] = row.count as number;\n }\n\n // Get average duration for completed jobs\n const { rows: durationRows } = await this.db.query(\n `\n SELECT AVG(\n (julianday(completed_at) - julianday(started_at)) * 86400000\n ) as avg_duration\n FROM ${this.tableName}\n WHERE status = 'completed'\n AND started_at IS NOT NULL\n AND completed_at IS NOT NULL\n ${queue ? 'AND queue = ?' : ''}\n `,\n params,\n );\n\n const avgDuration =\n (durationRows[0] as { avg_duration: number | null })?.avg_duration ??\n null;\n\n return {\n pending: counts['pending'] ?? 0,\n running: counts['running'] ?? 0,\n completed: counts['completed'] ?? 0,\n failed: counts['failed'] ?? 0,\n cancelled: counts['cancelled'] ?? 0,\n avgDuration: avgDuration ? Math.round(avgDuration) : null,\n };\n }\n\n async close(): Promise<void> {\n if (this.db && !this.externalDb) {\n await this.db.close?.();\n }\n this.db = null;\n this.initialized = false;\n }\n\n async waitForUpdate(timeoutMs?: number): Promise<boolean> {\n if (!this.db?.notifications) {\n if (timeoutMs && timeoutMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, timeoutMs));\n }\n return false;\n }\n\n return this.db.notifications.waitForUpdate({ timeoutMs });\n }\n}\n\nexport default SqliteJobStore;\n"],"mappings":";;;;;;;;;AAmCA,IAAa,iBAAb,cAAoC,aAAa;CAC/C,KAAuC;CACvC;CACA;CACA;CACA;CAEA,YAAY,SAA+B,CAAC,GAAG;EAC7C,MAAM;EACN,KAAK,MAAM,OAAO,OAAO;EACzB,KAAK,aAAa,OAAO,MAAM;EAC/B,KAAK,YAAY,kBAAkB,OAAO,aAAa,OAAO;EAC9D,KAAK,eAAe,OAAO;CAC7B;CAEA,MAAM,aAA4B;EAChC,IAAI,KAAK,aAAa;EAGtB,KAAK,KACH,KAAK,cACJ,MAAM,YAAY;GACjB,MAAM;GACN,KAAK,KAAK;GACV,cAAc,KAAK;EACrB,CAAC;EAGH,MAAM,SAAS;oCACiB,KAAK,UAAU;;;;;;;;;;;;;;;;;;;;;;wCAsBX,KAAK,UAAU,qBAAqB,KAAK,UAAU;wCACnD,KAAK,UAAU,mBAAmB,KAAK,UAAU;wCACjD,KAAK,UAAU,cAAc,KAAK,UAAU;;EAGhF,MAAM,WAAW;GAAE,IAAI,KAAK;GAAI;EAAO,CAAC;EAExC,KAAK,cAAc;CACrB;CAEA,MAAM,QAAQ,SAAyC;EACrD,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,MAAM,KAAK,gBAAgB,OAAO;EAExC,MAAM,KAAK,GAAG,OAAO,KAAK,WAAW;GACnC,IAAI,IAAI;GACR,OAAO,IAAI;GACX,SAAS,KAAK,UAAU,IAAI,OAAO;GACnC,QAAQ,IAAI;GACZ,UAAU,IAAI;GACd,UAAU,IAAI;GACd,cAAc,IAAI;GAClB,QAAQ,IAAI,MAAM,YAAY;GAC9B,YAAY,IAAI,WAAW,YAAY,KAAK;GAC5C,cAAc,IAAI,aAAa,YAAY,KAAK;GAChD,SAAS,IAAI;GACb,kBAAkB,IAAI;GACtB,YAAY,IAAI;GAChB,gBAAgB,IAAI;GACpB,gBAAgB,KAAK,UAAU,IAAI,aAAa;GAChD,WAAW,IAAI;GACf,kBAAkB,IAAI,iBAAiB,YAAY,KAAK;GACxD,YAAY,IAAI,UAAU,YAAY;GACtC,YAAY,IAAI,UAAU,YAAY;EACxC,CAAC;EAED,MAAM,KAAK,UAAU,eAAe,GAAG;EAGvC,IAAI,IAAI,yBAAS,IAAI,KAAK,GACxB,MAAM,KAAK,UAAU,aAAa,GAAG;EAGvC,OAAO;CACT;CAEA,MAAM,QACJ,QACA,OACA,UACgB;EAChB,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;EACnC,MAAM,oBAAoB,OAAO,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI;EAGzD,MAAM,EAAE,SAAS,MAAM,KAAK,GAAG,MAC7B;sBACgB,KAAK,UAAU;;wBAEb,kBAAkB;;;;OAKpC;GAAC,GAAG;GAAQ;GAAK;EAAK,CACxB;EAEA,IAAI,CAAC,KAAK,QAAQ,OAAO,CAAC;EAG1B,MAAM,SAAS,KAAK,KAAK,MAAM,EAAE,EAAY;EAC7C,MAAM,iBAAiB,OAAO,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI;EAEtD,MAAM,KAAK,GAAG,MACZ;eACS,KAAK,UAAU;;;;;;;qBAOT,eAAe;;OAG9B;GAAC;GAAU;GAAK;GAAK;GAAK,GAAG;EAAM,CACrC;EAGA,MAAM,OAAO,KAAK,KAAK,QAAQ;GAC7B,MAAM,MAAM,KAAK,YAAY,GAA8B;GAC3D,IAAI,SAAS;GACb,IAAI,WAAW;GACf,IAAI,kCAAkB,IAAI,KAAK;GAC/B,IAAI,4BAAY,IAAI,KAAK;GACzB,IAAI,YAAY;GAChB,OAAO;EACT,CAAC;EAGD,KAAK,MAAM,OAAO,MAChB,MAAM,KAAK,UAAU,eAAe,GAAG;EAGzC,OAAO;CACT;CAEA,MAAM,OAAO,IAAY,SAAqC;EAC5D,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,YAAsB,CAAC;EAC7B,MAAM,SAAoB,CAAC;EAG3B,MAAM,WAAmC;GACvC,OAAO;GACP,SAAS;GACT,QAAQ;GACR,UAAU;GACV,UAAU;GACV,aAAa;GACb,OAAO;GACP,WAAW;GACX,aAAa;GACb,SAAS;GACT,iBAAiB;GACjB,WAAW;GACX,eAAe;GACf,eAAe;GACf,UAAU;GACV,iBAAiB;EACnB;EAEA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,GAAG;GAClD,MAAM,SAAS,SAAS;GACxB,IAAI,CAAC,QAAQ;GAEb,UAAU,KAAK,GAAG,OAAO,KAAK;GAE9B,IAAI,iBAAiB,MACnB,OAAO,KAAK,MAAM,YAAY,CAAC;QAC1B,IAAI,OAAO,UAAU,YAAY,UAAU,MAChD,OAAO,KAAK,KAAK,UAAU,KAAK,CAAC;QAEjC,OAAO,KAAK,KAAK;EAErB;EAEA,IAAI,UAAU,WAAW,GAAG;GAC1B,MAAM,MAAM,MAAM,KAAK,IAAI,EAAE;GAC7B,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,kBAAkB,IAAI;GAChD,OAAO;EACT;EAGA,UAAU,KAAK,gBAAgB;EAC/B,OAAO,sBAAK,IAAI,KAAK,EAAA,CAAE,YAAY,CAAC;EAEpC,OAAO,KAAK,EAAE;EAEd,MAAM,KAAK,GAAG,MACZ,UAAU,KAAK,UAAU,OAAO,UAAU,KAAK,IAAI,EAAE,gBACrD,MACF;EAEA,MAAM,MAAM,MAAM,KAAK,IAAI,EAAE;EAC7B,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,+BAA+B,IAAI;EAG7D,IAAI,QAAQ,WAAW,aACrB,MAAM,KAAK,UAAU,iBAAiB,KAAK,EACzC,eAAe,IAAI,iBAAiB,KAAA,EACtC,CAAC;OACI,IAAI,QAAQ,WAAW,UAC5B,MAAM,KAAK,UAAU,cAAc,KAAK,EACtC,OAAO,IAAI,aAAa,KAAA,EAC1B,CAAC;OACI,IAAI,QAAQ,WAAW,aAC5B,MAAM,KAAK,UAAU,iBAAiB,GAAG;EAG3C,OAAO;CACT;CAEA,MAAM,IAAI,IAAiC;EACzC,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,MAAM,MAAM,KAAK,GAAG,IAAI,KAAK,WAAW,EAAE,GAAG,CAAC;EACpD,IAAI,CAAC,KAAK,OAAO;EAEjB,OAAO,KAAK,YAAY,GAA8B;CACxD;CAEA,MAAM,KAAK,QAAmC;EAC5C,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,EAAE,OAAO,QAAQ,gBAAgB,KAAK,iBAAiB,MAAM;EACnE,MAAM,UAAU,KAAK,aAAa,MAAM;EACxC,MAAM,EAAE,QAAQ,aAAa,QAAQ,gBACnC,KAAK,iBAAiB,MAAM;EAE9B,MAAM,EAAE,SAAS,MAAM,KAAK,GAAG,MAC7B,iBAAiB,KAAK,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,eACvD,CAAC,GAAG,aAAa,GAAG,WAAW,CACjC;EAEA,OAAO,KAAK,KAAK,QAAQ,KAAK,YAAY,GAA8B,CAAC;CAC3E;CAEA,MAAM,OAAO,IAA2B;EACtC,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,MAAM,MAAM,KAAK,IAAI,EAAE;EAC7B,IAAI,CAAC,KAAK,MAAM,IAAI,MAAM,kBAAkB,IAAI;EAEhD,IAAI,IAAI,WAAW,eAAe,IAAI,WAAW,aAC/C,MAAM,IAAI,MAAM,kCAAkC,IAAI,QAAQ;EAGhE,MAAM,KAAK,OAAO,IAAI;GACpB,QAAQ;GACR,6BAAa,IAAI,KAAK;EACxB,CAAC;CACH;CAEA,MAAM,QAAQ,SAA0C;EACtD,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,aAAuB,CAAC;EAC9B,MAAM,SAAoB,CAAC;EAE3B,IAAI,QAAQ,iBAAiB;GAC3B,WAAW,KAAK,6CAA6C;GAC7D,OAAO,KAAK,QAAQ,gBAAgB,YAAY,CAAC;EACnD;EAEA,IAAI,QAAQ,cAAc;GACxB,WAAW,KAAK,0CAA0C;GAC1D,OAAO,KAAK,QAAQ,aAAa,YAAY,CAAC;EAChD;EAEA,IAAI,QAAQ,iBAAiB;GAC3B,WAAW,KAAK,6CAA6C;GAC7D,OAAO,KAAK,QAAQ,gBAAgB,YAAY,CAAC;EACnD;EAEA,IAAI,WAAW,WAAW,GAAG,OAAO;EAEpC,IAAI,QAAQ,eAAe,KAAK,UAAU,UAAU,WAAW,KAAK,MAAM,EAAE;EAE5E,IAAI,QAAQ,OAAO;GAEjB,QAAQ;sBACQ,KAAK,UAAU;;2BAEV,KAAK,UAAU;mBACvB,WAAW,KAAK,MAAM,EAAE;;;;GAIrC,OAAO,KAAK,QAAQ,KAAK;EAC3B;EAIA,QAAO,MAFc,KAAK,GAAG,MAAM,OAAO,MAAM,EAAA,CAElC,YAAY;CAC5B;CAEA,MAAM,UAAU,OAAe,UAAiC;EAC9D,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,KAAK,GAAG,MACZ;eACS,KAAK,UAAU;;;OAIxB;oBAAC,IAAI,KAAK,EAAA,CAAE,YAAY;oBAAG,IAAI,KAAK,EAAA,CAAE,YAAY;GAAG;GAAO;EAAQ,CACtE;CACF;CAEA,MAAM,MAAM,OAAqC;EAC/C,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,MAAM,uBAAuB;EAErD,MAAM,cAAc,QAAQ,oBAAoB;EAChD,MAAM,SAAS,QAAQ,CAAC,KAAK,IAAI,CAAC;EAGlC,MAAM,EAAE,MAAM,cAAc,MAAM,KAAK,GAAG,MACxC;;aAEO,KAAK,UAAU;QACpB,YAAY;;OAGd,MACF;EAEA,MAAM,SAAiC,CAAC;EACxC,KAAK,MAAM,OAAO,WAChB,OAAO,IAAI,UAAoB,IAAI;EAIrC,MAAM,EAAE,MAAM,iBAAiB,MAAM,KAAK,GAAG,MAC3C;;;;aAIO,KAAK,UAAU;;;;UAIlB,QAAQ,kBAAkB,GAAG;OAEjC,MACF;EAEA,MAAM,cACH,aAAa,EAAE,EAAsC,gBACtD;EAEF,OAAO;GACL,SAAS,OAAO,cAAc;GAC9B,SAAS,OAAO,cAAc;GAC9B,WAAW,OAAO,gBAAgB;GAClC,QAAQ,OAAO,aAAa;GAC5B,WAAW,OAAO,gBAAgB;GAClC,aAAa,cAAc,KAAK,MAAM,WAAW,IAAI;EACvD;CACF;CAEA,MAAM,QAAuB;EAC3B,IAAI,KAAK,MAAM,CAAC,KAAK,YACnB,MAAM,KAAK,GAAG,QAAQ;EAExB,KAAK,KAAK;EACV,KAAK,cAAc;CACrB;CAEA,MAAM,cAAc,WAAsC;EACxD,IAAI,CAAC,KAAK,IAAI,eAAe;GAC3B,IAAI,aAAa,YAAY,GAC3B,MAAM,IAAI,SAAS,YAAY,WAAW,SAAS,SAAS,CAAC;GAE/D,OAAO;EACT;EAEA,OAAO,KAAK,GAAG,cAAc,cAAc,EAAE,UAAU,CAAC;CAC1D;AACF"}
1
+ {"version":3,"file":"sqlite.js","names":[],"sources":["../../src/adapters/sqlite.ts"],"sourcesContent":["import {\n type DatabaseInterface,\n getDatabase,\n type SqliteCapabilitiesOptions,\n syncSchema,\n} from '@happyvertical/sql';\nimport { BaseJobStore, validateTableName } from '../base-store.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobFilter,\n QueueStats,\n} from '../types.js';\n\n/**\n * SQLite job store configuration\n */\nexport interface SqliteJobStoreConfig {\n /** Database URL or path (default: ':memory:') */\n url?: string;\n /** Existing database instance to use */\n db?: DatabaseInterface;\n /** Table name for jobs (default: '_jobs') */\n tableName?: string;\n /** Optional SQLite native capabilities for development/test modes */\n capabilities?: SqliteCapabilitiesOptions;\n}\n\n/**\n * SQLite-based job store\n *\n * Uses SQLite for job persistence. Supports polling-based job retrieval.\n * Good for single-instance deployments or development.\n */\nexport class SqliteJobStore extends BaseJobStore {\n private db: DatabaseInterface | null = null;\n private readonly url: string;\n private readonly externalDb: DatabaseInterface | null;\n private readonly tableName: string;\n private readonly capabilities: SqliteCapabilitiesOptions | undefined;\n\n constructor(config: SqliteJobStoreConfig = {}) {\n super();\n this.url = config.url ?? ':memory:';\n this.externalDb = config.db ?? null;\n this.tableName = validateTableName(config.tableName ?? '_jobs');\n this.capabilities = config.capabilities;\n }\n\n async initialize(): Promise<void> {\n if (this.initialized) return;\n\n // Use existing database or create new one\n this.db =\n this.externalDb ??\n (await getDatabase({\n type: 'sqlite',\n url: this.url,\n capabilities: this.capabilities,\n }));\n\n // Create jobs table and indexes using syncSchema\n const schema = `\n CREATE TABLE IF NOT EXISTS \"${this.tableName}\" (\n id TEXT PRIMARY KEY,\n queue TEXT NOT NULL DEFAULT 'default',\n payload TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'pending',\n priority INTEGER NOT NULL DEFAULT 50,\n attempts INTEGER NOT NULL DEFAULT 0,\n max_attempts INTEGER NOT NULL DEFAULT 3,\n run_at TEXT NOT NULL,\n started_at TEXT,\n completed_at TEXT,\n timeout INTEGER NOT NULL DEFAULT 300000,\n timeout_behavior TEXT NOT NULL DEFAULT 'fail',\n last_error TEXT,\n result_pointer TEXT,\n retry_strategy TEXT NOT NULL,\n worker_id TEXT,\n worker_heartbeat TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_status_queue\" ON \"${this.tableName}\" (status, queue, run_at, priority DESC);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_created_at\" ON \"${this.tableName}\" (created_at);\n CREATE INDEX IF NOT EXISTS \"idx_${this.tableName}_queue\" ON \"${this.tableName}\" (queue);\n `;\n\n await syncSchema({ db: this.db, schema });\n\n this.initialized = true;\n }\n\n async enqueue(options: JobCreateOptions): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = this.createJobRecord(options);\n\n await this.db.insert(this.tableName, {\n id: job.id,\n queue: job.queue,\n payload: JSON.stringify(job.payload),\n status: job.status,\n priority: job.priority,\n attempts: job.attempts,\n max_attempts: job.maxAttempts,\n run_at: job.runAt.toISOString(),\n started_at: job.startedAt?.toISOString() ?? null,\n completed_at: job.completedAt?.toISOString() ?? null,\n timeout: job.timeout,\n timeout_behavior: job.timeoutBehavior,\n last_error: job.lastError,\n result_pointer: job.resultPointer,\n retry_strategy: JSON.stringify(job.retryStrategy),\n worker_id: job.workerId,\n worker_heartbeat: job.workerHeartbeat?.toISOString() ?? null,\n created_at: job.createdAt.toISOString(),\n updated_at: job.updatedAt.toISOString(),\n });\n\n await this.emitEvent('job.created', job);\n\n // Also emit ready if job should run immediately\n if (job.runAt <= new Date()) {\n await this.emitEvent('job.ready', job);\n }\n\n return job;\n }\n\n async dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const now = new Date().toISOString();\n const queuePlaceholders = queues.map(() => '?').join(', ');\n\n // Find jobs ready to process\n const { rows } = await this.db.query(\n `\n SELECT * FROM ${this.tableName}\n WHERE status = 'pending'\n AND queue IN (${queuePlaceholders})\n AND run_at <= ?\n ORDER BY priority DESC, run_at ASC\n LIMIT ?\n `,\n [...queues, now, limit],\n );\n\n if (!rows.length) return [];\n\n // Claim the jobs by updating their status\n const jobIds = rows.map((r) => r.id as string);\n const idPlaceholders = jobIds.map(() => '?').join(', ');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET status = 'running',\n worker_id = ?,\n worker_heartbeat = ?,\n started_at = ?,\n attempts = attempts + 1,\n updated_at = ?\n WHERE id IN (${idPlaceholders})\n AND status = 'pending'\n `,\n [workerId, now, now, now, ...jobIds],\n );\n\n // Return the claimed jobs\n const jobs = rows.map((row) => {\n const job = this.parseJobRow(row as Record<string, unknown>);\n job.status = 'running';\n job.workerId = workerId;\n job.workerHeartbeat = new Date();\n job.startedAt = new Date();\n job.attempts += 1;\n return job;\n });\n\n // Emit started events\n for (const job of jobs) {\n await this.emitEvent('job.started', job);\n }\n\n return jobs;\n }\n\n async update(id: string, updates: Partial<Job>): Promise<Job> {\n if (!this.db) throw new Error('Store not initialized');\n\n const setClause: string[] = [];\n const params: unknown[] = [];\n\n // Map Job fields to database columns\n const fieldMap: Record<string, string> = {\n queue: 'queue',\n payload: 'payload',\n status: 'status',\n priority: 'priority',\n attempts: 'attempts',\n maxAttempts: 'max_attempts',\n runAt: 'run_at',\n startedAt: 'started_at',\n completedAt: 'completed_at',\n timeout: 'timeout',\n timeoutBehavior: 'timeout_behavior',\n lastError: 'last_error',\n resultPointer: 'result_pointer',\n retryStrategy: 'retry_strategy',\n workerId: 'worker_id',\n workerHeartbeat: 'worker_heartbeat',\n };\n\n for (const [key, value] of Object.entries(updates)) {\n // `hasOwn`, not `if (!fieldMap[key])`: the fieldMap literal inherits\n // from Object.prototype, so keys like 'constructor' resolve to a truthy\n // inherited value whose column would be interpolated into the SET clause.\n // An own-property check drops unknown keys instead of injecting them.\n if (!Object.hasOwn(fieldMap, key)) continue;\n const column = fieldMap[key];\n\n setClause.push(`${column} = ?`);\n\n if (value instanceof Date) {\n params.push(value.toISOString());\n } else if (typeof value === 'object' && value !== null) {\n params.push(JSON.stringify(value));\n } else {\n params.push(value);\n }\n }\n\n if (setClause.length === 0) {\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n return job;\n }\n\n // Always update updated_at\n setClause.push('updated_at = ?');\n params.push(new Date().toISOString());\n\n params.push(id);\n\n await this.db.query(\n `UPDATE ${this.tableName} SET ${setClause.join(', ')} WHERE id = ?`,\n params,\n );\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found after update: ${id}`);\n\n // Emit events based on status changes. Read `status` only when it is an\n // own key of `updates`: a bare `updates.status` traverses the prototype\n // chain, so a polluted Object.prototype.status could make an unrelated\n // update emit a spurious lifecycle event.\n const statusUpdate = Object.hasOwn(updates, 'status')\n ? updates.status\n : undefined;\n if (statusUpdate === 'completed') {\n await this.emitEvent('job.completed', job, {\n resultPointer: job.resultPointer ?? undefined,\n });\n } else if (statusUpdate === 'failed') {\n await this.emitEvent('job.failed', job, {\n error: job.lastError ?? undefined,\n });\n } else if (statusUpdate === 'cancelled') {\n await this.emitEvent('job.cancelled', job);\n }\n\n return job;\n }\n\n async get(id: string): Promise<Job | null> {\n if (!this.db) throw new Error('Store not initialized');\n\n const row = await this.db.get(this.tableName, { id });\n if (!row) return null;\n\n return this.parseJobRow(row as Record<string, unknown>);\n }\n\n async list(filter: JobFilter): Promise<Job[]> {\n if (!this.db) throw new Error('Store not initialized');\n\n const { where, params: whereParams } = this.buildFilterWhere(filter);\n const orderBy = this.buildOrderBy(filter);\n const { clause: limitOffset, params: limitParams } =\n this.buildLimitOffset(filter);\n\n const { rows } = await this.db.query(\n `SELECT * FROM ${this.tableName} ${where} ${orderBy} ${limitOffset}`,\n [...whereParams, ...limitParams],\n );\n\n return rows.map((row) => this.parseJobRow(row as Record<string, unknown>));\n }\n\n async cancel(id: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n const job = await this.get(id);\n if (!job) throw new Error(`Job not found: ${id}`);\n\n if (job.status === 'completed' || job.status === 'cancelled') {\n throw new Error(`Cannot cancel job with status: ${job.status}`);\n }\n\n await this.update(id, {\n status: 'cancelled',\n completedAt: new Date(),\n });\n }\n\n async cleanup(options: CleanupOptions): Promise<number> {\n if (!this.db) throw new Error('Store not initialized');\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (options.completedBefore) {\n conditions.push(\"(status = 'completed' AND completed_at < ?)\");\n params.push(options.completedBefore.toISOString());\n }\n\n if (options.failedBefore) {\n conditions.push(\"(status = 'failed' AND completed_at < ?)\");\n params.push(options.failedBefore.toISOString());\n }\n\n if (options.cancelledBefore) {\n conditions.push(\"(status = 'cancelled' AND completed_at < ?)\");\n params.push(options.cancelledBefore.toISOString());\n }\n\n if (conditions.length === 0) return 0;\n\n let query = `DELETE FROM ${this.tableName} WHERE (${conditions.join(' OR ')})`;\n\n if (options.limit) {\n // SQLite doesn't support LIMIT in DELETE directly, so we need a subquery\n query = `\n DELETE FROM ${this.tableName}\n WHERE id IN (\n SELECT id FROM ${this.tableName}\n WHERE (${conditions.join(' OR ')})\n LIMIT ?\n )\n `;\n params.push(options.limit);\n }\n\n const result = await this.db.query(query, params);\n\n return result.rowCount ?? 0;\n }\n\n async heartbeat(jobId: string, workerId: string): Promise<void> {\n if (!this.db) throw new Error('Store not initialized');\n\n await this.db.query(\n `\n UPDATE ${this.tableName}\n SET worker_heartbeat = ?, updated_at = ?\n WHERE id = ? AND worker_id = ? AND status = 'running'\n `,\n [new Date().toISOString(), new Date().toISOString(), jobId, workerId],\n );\n }\n\n async stats(queue?: string): Promise<QueueStats> {\n if (!this.db) throw new Error('Store not initialized');\n\n const queueFilter = queue ? 'WHERE queue = ?' : '';\n const params = queue ? [queue] : [];\n\n // Get status counts\n const { rows: countRows } = await this.db.query(\n `\n SELECT status, COUNT(*) as count\n FROM ${this.tableName}\n ${queueFilter}\n GROUP BY status\n `,\n params,\n );\n\n const counts: Record<string, number> = {};\n for (const row of countRows) {\n counts[row.status as string] = row.count as number;\n }\n\n // Get average duration for completed jobs\n const { rows: durationRows } = await this.db.query(\n `\n SELECT AVG(\n (julianday(completed_at) - julianday(started_at)) * 86400000\n ) as avg_duration\n FROM ${this.tableName}\n WHERE status = 'completed'\n AND started_at IS NOT NULL\n AND completed_at IS NOT NULL\n ${queue ? 'AND queue = ?' : ''}\n `,\n params,\n );\n\n const avgDuration =\n (durationRows[0] as { avg_duration: number | null })?.avg_duration ??\n null;\n\n return {\n pending: counts['pending'] ?? 0,\n running: counts['running'] ?? 0,\n completed: counts['completed'] ?? 0,\n failed: counts['failed'] ?? 0,\n cancelled: counts['cancelled'] ?? 0,\n avgDuration: avgDuration ? Math.round(avgDuration) : null,\n };\n }\n\n async close(): Promise<void> {\n if (this.db && !this.externalDb) {\n await this.db.close?.();\n }\n this.db = null;\n this.initialized = false;\n }\n\n async waitForUpdate(timeoutMs?: number): Promise<boolean> {\n if (!this.db?.notifications) {\n if (timeoutMs && timeoutMs > 0) {\n await new Promise((resolve) => setTimeout(resolve, timeoutMs));\n }\n return false;\n }\n\n return this.db.notifications.waitForUpdate({ timeoutMs });\n }\n}\n\nexport default SqliteJobStore;\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-DIasEzL0.js";
1
+ import { n as priorityToNumber, t as BaseJobStore } from "../chunks/base-store-Cac1FH1X.js";
2
2
  import { createId } from "@happyvertical/utils";
3
3
  //#region src/adapters/sqs.ts
4
4
  /**
@@ -42,6 +42,11 @@ export declare abstract class BaseJobStore implements JobStore {
42
42
  };
43
43
  /**
44
44
  * Build ORDER BY clause for filters
45
+ *
46
+ * Both halves are interpolated rather than bound, and `JobFilter`'s literal
47
+ * types are erased at runtime, so both are resolved against an allowlist.
48
+ *
49
+ * @throws Error if the order direction is neither blank nor `asc`/`desc`
45
50
  */
46
51
  protected buildOrderBy(filter: JobFilter): string;
47
52
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"base-store.d.ts","sourceRoot":"","sources":["../src/base-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAEhB,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,WAAW,EACX,QAAQ,EACR,UAAU,EAEX,MAAM,YAAY,CAAC;AAOpB;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAa3D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GACzC,MAAM,CAcR;AAED;;GAEG;AACH,8BAAsB,YAAa,YAAW,QAAQ;IACpD,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAa;IACvD,SAAS,CAAC,WAAW,UAAS;IAE9B;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpC;;OAEG;IACH,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,GAAG;IA+BzD;;OAEG;cACa,SAAS,CACvB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,GAAG,EACR,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,IAAI,CAAC;IAiBhB;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAOjD;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG;QAC7C,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,EAAE,CAAC;KACnB;IA8CD;;OAEG;IACH,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM;IAcjD;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG;QAC7C,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,OAAO,EAAE,CAAC;KACnB;IAiBD;;OAEG;IACH,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,GAAG;IAiCxD,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IACzD,QAAQ,CAAC,OAAO,CACd,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IACjB,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAChE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAC1D,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAClE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IACnD,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAChC"}
1
+ {"version":3,"file":"base-store.d.ts","sourceRoot":"","sources":["../src/base-store.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,cAAc,EACd,GAAG,EACH,gBAAgB,EAEhB,gBAAgB,EAChB,YAAY,EACZ,SAAS,EACT,WAAW,EACX,QAAQ,EACR,UAAU,EAEX,MAAM,YAAY,CAAC;AAYpB;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAa3D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,GACzC,MAAM,CAcR;AAED;;GAEG;AACH,8BAAsB,YAAa,YAAW,QAAQ;IACpD,SAAS,CAAC,SAAS,EAAE,GAAG,CAAC,gBAAgB,CAAC,CAAa;IACvD,SAAS,CAAC,WAAW,UAAS;IAE9B;;OAEG;IACH,QAAQ,CAAC,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAEpC;;OAEG;IACH,SAAS,CAAC,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,GAAG;IA+BzD;;OAEG;cACa,SAAS,CACvB,IAAI,EAAE,YAAY,EAClB,GAAG,EAAE,GAAG,EACR,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,IAAI,CAAC;IAiBhB;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAOjD;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG;QAC7C,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,OAAO,EAAE,CAAC;KACnB;IA8CD;;;;;;;OAOG;IACH,SAAS,CAAC,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM;IA0CjD;;OAEG;IACH,SAAS,CAAC,gBAAgB,CAAC,MAAM,EAAE,SAAS,GAAG;QAC7C,MAAM,EAAE,MAAM,CAAC;QACf,MAAM,EAAE,OAAO,EAAE,CAAC;KACnB;IAiBD;;OAEG;IACH,SAAS,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,GAAG;IAiCxD,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC;IACzD,QAAQ,CAAC,OAAO,CACd,MAAM,EAAE,MAAM,EAAE,EAChB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,GAAG,EAAE,CAAC;IACjB,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC;IAChE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;IAC7C,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAC1C,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC;IAC1D,QAAQ,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAClE,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IACnD,QAAQ,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAChC"}
@@ -202,6 +202,10 @@ var DEFAULT_RETRY_STRATEGY = exponential({
202
202
  */
203
203
  var TABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
204
204
  /**
205
+ * Directions accepted in an ORDER BY clause
206
+ */
207
+ var VALID_DIRECTIONS = /* @__PURE__ */ new Set(["ASC", "DESC"]);
208
+ /**
205
209
  * Validate table name to prevent SQL injection
206
210
  * @throws Error if table name contains invalid characters
207
211
  */
@@ -323,16 +327,27 @@ var BaseJobStore = class {
323
327
  }
324
328
  /**
325
329
  * Build ORDER BY clause for filters
330
+ *
331
+ * Both halves are interpolated rather than bound, and `JobFilter`'s literal
332
+ * types are erased at runtime, so both are resolved against an allowlist.
333
+ *
334
+ * @throws Error if the order direction is neither blank nor `asc`/`desc`
326
335
  */
327
336
  buildOrderBy(filter) {
328
337
  const field = filter.orderBy ?? "createdAt";
329
338
  const dir = filter.orderDir ?? "desc";
330
- return `ORDER BY ${{
339
+ const fieldMap = {
331
340
  createdAt: "created_at",
332
341
  runAt: "run_at",
333
342
  priority: "priority",
334
343
  attempts: "attempts"
335
- }[field] ?? "created_at"} ${dir.toUpperCase()}`;
344
+ };
345
+ const column = typeof field === "string" && Object.hasOwn(fieldMap, field) ? fieldMap[field] : "created_at";
346
+ if (typeof dir !== "string") throw new Error(`Invalid job order direction: ${typeof dir}`);
347
+ const trimmedDir = dir.trim();
348
+ const normalizedDir = trimmedDir === "" ? "DESC" : trimmedDir.toUpperCase();
349
+ if (!VALID_DIRECTIONS.has(normalizedDir)) throw new Error(`Invalid job order direction: ${JSON.stringify(trimmedDir.slice(0, 32))}`);
350
+ return `ORDER BY ${column} ${normalizedDir}`;
336
351
  }
337
352
  /**
338
353
  * Build LIMIT/OFFSET clause
@@ -383,4 +398,4 @@ var BaseJobStore = class {
383
398
  //#endregion
384
399
  export { custom as a, linear as c, DEFAULT_RETRY_STRATEGY as i, noRetry as l, priorityToNumber as n, exponential as o, validateTableName as r, fromConfig as s, BaseJobStore as t };
385
400
 
386
- //# sourceMappingURL=base-store-DIasEzL0.js.map
401
+ //# sourceMappingURL=base-store-Cac1FH1X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base-store-Cac1FH1X.js","names":[],"sources":["../../src/retry.ts","../../src/base-store.ts"],"sourcesContent":["import type {\n RetryDecision,\n RetryStrategy,\n RetryStrategyConfig,\n} from './types.js';\n\n/**\n * Options for exponential backoff retry strategy\n */\nexport interface ExponentialBackoffOptions {\n /** Initial delay in milliseconds (default: 1000) */\n initialDelay?: number;\n /** Maximum delay in milliseconds (default: 300000 = 5 minutes) */\n maxDelay?: number;\n /** Multiplier for each attempt (default: 2) */\n multiplier?: number;\n /** Add random jitter to prevent thundering herd (default: true) */\n jitter?: boolean;\n /** Maximum attempts (optional, can also be set on job) */\n maxAttempts?: number;\n}\n\n/**\n * Exponential backoff retry strategy\n *\n * Delay increases exponentially: initialDelay * multiplier^(attempt-1)\n * With optional jitter to prevent thundering herd problem.\n */\nclass ExponentialBackoffStrategy implements RetryStrategy {\n private readonly initialDelay: number;\n private readonly maxDelay: number;\n private readonly multiplier: number;\n private readonly jitter: boolean;\n private readonly maxAttempts: number | null;\n\n constructor(options: ExponentialBackoffOptions = {}) {\n this.initialDelay = options.initialDelay ?? 1000;\n this.maxDelay = options.maxDelay ?? 300000;\n this.multiplier = options.multiplier ?? 2;\n this.jitter = options.jitter ?? true;\n this.maxAttempts = options.maxAttempts ?? null;\n }\n\n shouldRetry(attempt: number, _error: Error): RetryDecision {\n // Check max attempts if configured\n if (this.maxAttempts !== null && attempt >= this.maxAttempts) {\n return { shouldRetry: false, delay: 0 };\n }\n\n // Calculate base delay: initialDelay * multiplier^(attempt-1)\n let delay = this.initialDelay * this.multiplier ** (attempt - 1);\n\n // Cap at maxDelay\n delay = Math.min(delay, this.maxDelay);\n\n // Add jitter (±25% randomization)\n if (this.jitter) {\n const jitterRange = delay * 0.25;\n delay = delay - jitterRange + Math.random() * jitterRange * 2;\n }\n\n return { shouldRetry: true, delay: Math.round(delay) };\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'exponential',\n config: {\n initialDelay: this.initialDelay,\n maxDelay: this.maxDelay,\n multiplier: this.multiplier,\n jitter: this.jitter,\n maxAttempts: this.maxAttempts,\n },\n };\n }\n}\n\n/**\n * Options for linear retry strategy\n */\nexport interface LinearBackoffOptions {\n /** Fixed delay between retries in milliseconds (default: 5000) */\n delay?: number;\n /** Maximum attempts (optional) */\n maxAttempts?: number;\n}\n\n/**\n * Linear retry strategy\n *\n * Uses a fixed delay between all retry attempts.\n */\nclass LinearBackoffStrategy implements RetryStrategy {\n private readonly delay: number;\n private readonly maxAttempts: number | null;\n\n constructor(options: LinearBackoffOptions = {}) {\n this.delay = options.delay ?? 5000;\n this.maxAttempts = options.maxAttempts ?? null;\n }\n\n shouldRetry(attempt: number, _error: Error): RetryDecision {\n if (this.maxAttempts !== null && attempt >= this.maxAttempts) {\n return { shouldRetry: false, delay: 0 };\n }\n\n return { shouldRetry: true, delay: this.delay };\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'linear',\n config: {\n delay: this.delay,\n maxAttempts: this.maxAttempts,\n },\n };\n }\n}\n\n/**\n * Custom retry decision function\n */\nexport type CustomRetryFn = (attempt: number, error: Error) => RetryDecision;\n\n/**\n * Custom retry strategy\n *\n * Allows full control over retry logic via a custom function.\n */\nclass CustomRetryStrategy implements RetryStrategy {\n private readonly fn: CustomRetryFn;\n private readonly fnString: string;\n\n constructor(fn: CustomRetryFn) {\n this.fn = fn;\n // Store string representation for serialization\n this.fnString = fn.toString();\n }\n\n shouldRetry(attempt: number, error: Error): RetryDecision {\n return this.fn(attempt, error);\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'custom',\n config: {\n fn: this.fnString,\n },\n };\n }\n}\n\n/**\n * No retry strategy - never retry\n */\nclass NoRetryStrategy implements RetryStrategy {\n shouldRetry(_attempt: number, _error: Error): RetryDecision {\n return { shouldRetry: false, delay: 0 };\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'linear',\n config: { maxAttempts: 1 },\n };\n }\n}\n\n// Factory functions\n\n/**\n * Create an exponential backoff retry strategy\n *\n * @example\n * ```typescript\n * const strategy = exponential({\n * initialDelay: 1000,\n * maxDelay: 300000,\n * multiplier: 2,\n * jitter: true,\n * });\n * ```\n */\nexport function exponential(\n options?: ExponentialBackoffOptions,\n): RetryStrategy {\n return new ExponentialBackoffStrategy(options);\n}\n\n/**\n * Create a linear retry strategy with fixed delay\n *\n * @example\n * ```typescript\n * const strategy = linear({ delay: 5000 });\n * ```\n */\nexport function linear(options?: LinearBackoffOptions): RetryStrategy {\n return new LinearBackoffStrategy(options);\n}\n\n/**\n * Create a custom retry strategy\n *\n * @example\n * ```typescript\n * const strategy = custom((attempt, error) => {\n * if (error.message.includes('RATE_LIMITED')) {\n * return { shouldRetry: true, delay: 60000 };\n * }\n * return { shouldRetry: attempt < 3, delay: attempt * 1000 };\n * });\n * ```\n */\nexport function custom(fn: CustomRetryFn): RetryStrategy {\n return new CustomRetryStrategy(fn);\n}\n\n/**\n * Create a no-retry strategy\n *\n * @example\n * ```typescript\n * const strategy = noRetry();\n * ```\n */\nexport function noRetry(): RetryStrategy {\n return new NoRetryStrategy();\n}\n\n/**\n * Reconstruct a retry strategy from its config\n */\nexport function fromConfig(config: RetryStrategyConfig): RetryStrategy {\n switch (config.type) {\n case 'exponential':\n return new ExponentialBackoffStrategy(\n config.config as ExponentialBackoffOptions,\n );\n case 'linear':\n return new LinearBackoffStrategy(config.config as LinearBackoffOptions);\n case 'custom':\n // For custom strategies loaded from config, we can't reconstruct the function\n // So we fall back to exponential\n console.warn(\n 'Custom retry strategy cannot be reconstructed from config, using exponential',\n );\n return new ExponentialBackoffStrategy();\n default:\n return new ExponentialBackoffStrategy();\n }\n}\n\n/**\n * Default retry strategy\n */\nexport const DEFAULT_RETRY_STRATEGY = exponential({\n initialDelay: 1000,\n maxDelay: 300000,\n multiplier: 2,\n jitter: true,\n});\n","import { createId } from '@happyvertical/utils';\nimport { DEFAULT_RETRY_STRATEGY } from './retry.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobEvent,\n JobEventListener,\n JobEventType,\n JobFilter,\n JobPriority,\n JobStore,\n QueueStats,\n RetryStrategyConfig,\n} from './types.js';\n\n/**\n * Valid table name pattern: alphanumeric and underscores, must start with letter or underscore\n */\nconst TABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Directions accepted in an ORDER BY clause\n */\nconst VALID_DIRECTIONS = new Set(['ASC', 'DESC']);\n\n/**\n * Validate table name to prevent SQL injection\n * @throws Error if table name contains invalid characters\n */\nexport function validateTableName(tableName: string): string {\n if (!tableName || tableName.length === 0) {\n throw new Error('Table name cannot be empty');\n }\n if (tableName.length > 128) {\n throw new Error('Table name cannot exceed 128 characters');\n }\n if (!TABLE_NAME_PATTERN.test(tableName)) {\n throw new Error(\n `Invalid table name \"${tableName}\": must contain only alphanumeric characters and underscores, and must start with a letter or underscore`,\n );\n }\n return tableName;\n}\n\n/**\n * Convert priority string to number\n */\nexport function priorityToNumber(\n priority: JobPriority | number | undefined,\n): number {\n if (typeof priority === 'number') return priority;\n switch (priority) {\n case 'critical':\n return 100;\n case 'high':\n return 75;\n case 'normal':\n return 50;\n case 'low':\n return 25;\n default:\n return 50;\n }\n}\n\n/**\n * Base job store with common functionality\n */\nexport abstract class BaseJobStore implements JobStore {\n protected listeners: Set<JobEventListener> = new Set();\n protected initialized = false;\n\n /**\n * Initialize the store - must be implemented by subclasses\n */\n abstract initialize(): Promise<void>;\n\n /**\n * Create a new job record\n */\n protected createJobRecord(options: JobCreateOptions): Job {\n const now = new Date();\n const retryConfig: RetryStrategyConfig =\n options.retryStrategy && 'toConfig' in options.retryStrategy\n ? options.retryStrategy.toConfig()\n : ((options.retryStrategy as RetryStrategyConfig) ??\n DEFAULT_RETRY_STRATEGY.toConfig());\n\n return {\n id: createId(),\n queue: options.queue ?? 'default',\n payload: options.payload,\n status: 'pending',\n priority: priorityToNumber(options.priority),\n attempts: 0,\n maxAttempts: options.maxAttempts ?? 3,\n runAt: options.runAt ?? now,\n startedAt: null,\n completedAt: null,\n timeout: options.timeout ?? 300000,\n timeoutBehavior: options.timeoutBehavior ?? 'fail',\n lastError: null,\n resultPointer: null,\n retryStrategy: retryConfig,\n workerId: null,\n workerHeartbeat: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * Emit a job event to all listeners\n */\n protected async emitEvent(\n type: JobEventType,\n job: Job,\n extra?: { error?: string; resultPointer?: string },\n ): Promise<void> {\n const event: JobEvent = {\n type,\n job,\n timestamp: new Date(),\n ...extra,\n };\n\n const promises = Array.from(this.listeners).map((listener) =>\n Promise.resolve(listener(event)).catch((err) => {\n console.error('Job event listener error:', err);\n }),\n );\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Subscribe to job events\n */\n subscribe(listener: JobEventListener): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Build WHERE clause for filters\n */\n protected buildFilterWhere(filter: JobFilter): {\n where: string;\n params: unknown[];\n } {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (filter.queue) {\n conditions.push('queue = ?');\n params.push(filter.queue);\n }\n\n if (filter.status) {\n if (Array.isArray(filter.status)) {\n const placeholders = filter.status.map(() => '?').join(', ');\n conditions.push(`status IN (${placeholders})`);\n params.push(...filter.status);\n } else {\n conditions.push('status = ?');\n params.push(filter.status);\n }\n }\n\n if (filter.objectType) {\n conditions.push(\"json_extract(payload, '$.objectType') = ?\");\n params.push(filter.objectType);\n }\n\n if (filter.method) {\n conditions.push(\"json_extract(payload, '$.method') = ?\");\n params.push(filter.method);\n }\n\n if (filter.createdAfter) {\n conditions.push('created_at > ?');\n params.push(filter.createdAfter.toISOString());\n }\n\n if (filter.createdBefore) {\n conditions.push('created_at < ?');\n params.push(filter.createdBefore.toISOString());\n }\n\n return {\n where: conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '',\n params,\n };\n }\n\n /**\n * Build ORDER BY clause for filters\n *\n * Both halves are interpolated rather than bound, and `JobFilter`'s literal\n * types are erased at runtime, so both are resolved against an allowlist.\n *\n * @throws Error if the order direction is neither blank nor `asc`/`desc`\n */\n protected buildOrderBy(filter: JobFilter): string {\n const field = filter.orderBy ?? 'createdAt';\n const dir = filter.orderDir ?? 'desc';\n\n const fieldMap: Record<string, string> = {\n createdAt: 'created_at',\n runAt: 'run_at',\n priority: 'priority',\n attempts: 'attempts',\n };\n\n // `hasOwn`, not `fieldMap[field] ?? …`: a plain object literal inherits\n // from Object.prototype, so 'constructor' and friends resolve to a truthy\n // inherited value and never reach the fallback. The `typeof` conjunct\n // keeps the check and the read from coercing a non-string key twice — a\n // stateful `toString` could otherwise return a different key to each.\n const column =\n typeof field === 'string' && Object.hasOwn(fieldMap, field)\n ? fieldMap[field]\n : 'created_at';\n\n if (typeof dir !== 'string') {\n // Report the type, not the value: interpolating a symbol or a\n // null-prototype object would itself throw a TypeError.\n throw new Error(`Invalid job order direction: ${typeof dir}`);\n }\n\n // A blank direction — `?orderDir=` from an unfilled field — means\n // unspecified, not hostile; the `??` above only catches null/undefined.\n const trimmedDir = dir.trim();\n const normalizedDir = trimmedDir === '' ? 'DESC' : trimmedDir.toUpperCase();\n if (!VALID_DIRECTIONS.has(normalizedDir)) {\n // Quoted and bounded: the raw value reaches logs, where an unescaped\n // newline would let a caller forge a log line.\n throw new Error(\n `Invalid job order direction: ${JSON.stringify(trimmedDir.slice(0, 32))}`,\n );\n }\n\n return `ORDER BY ${column} ${normalizedDir}`;\n }\n\n /**\n * Build LIMIT/OFFSET clause\n */\n protected buildLimitOffset(filter: JobFilter): {\n clause: string;\n params: unknown[];\n } {\n const params: unknown[] = [];\n let clause = '';\n\n if (filter.limit) {\n clause = 'LIMIT ?';\n params.push(filter.limit);\n\n if (filter.offset) {\n clause += ' OFFSET ?';\n params.push(filter.offset);\n }\n }\n\n return { clause, params };\n }\n\n /**\n * Parse a job row from the database\n */\n protected parseJobRow(row: Record<string, unknown>): Job {\n return {\n id: row.id as string,\n queue: row.queue as string,\n payload:\n typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n status: row.status as Job['status'],\n priority: row.priority as number,\n attempts: row.attempts as number,\n maxAttempts: row.max_attempts as number,\n runAt: new Date(row.run_at as string),\n startedAt: row.started_at ? new Date(row.started_at as string) : null,\n completedAt: row.completed_at\n ? new Date(row.completed_at as string)\n : null,\n timeout: row.timeout as number,\n timeoutBehavior: row.timeout_behavior as Job['timeoutBehavior'],\n lastError: row.last_error as string | null,\n resultPointer: row.result_pointer as string | null,\n retryStrategy:\n typeof row.retry_strategy === 'string'\n ? JSON.parse(row.retry_strategy)\n : row.retry_strategy,\n workerId: row.worker_id as string | null,\n workerHeartbeat: row.worker_heartbeat\n ? new Date(row.worker_heartbeat as string)\n : null,\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string),\n };\n }\n\n // Abstract methods that must be implemented by subclasses\n abstract enqueue(options: JobCreateOptions): Promise<Job>;\n abstract dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]>;\n abstract update(id: string, updates: Partial<Job>): Promise<Job>;\n abstract get(id: string): Promise<Job | null>;\n abstract list(filter: JobFilter): Promise<Job[]>;\n abstract cancel(id: string): Promise<void>;\n abstract cleanup(options: CleanupOptions): Promise<number>;\n abstract heartbeat(jobId: string, workerId: string): Promise<void>;\n abstract stats(queue?: string): Promise<QueueStats>;\n abstract close(): Promise<void>;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as custom, c as linear, i as DEFAULT_RETRY_STRATEGY, l as noRetry, n as priorityToNumber, o as exponential, s as fromConfig, t as BaseJobStore } from "./chunks/base-store-DIasEzL0.js";
1
+ import { a as custom, c as linear, i as DEFAULT_RETRY_STRATEGY, l as noRetry, n as priorityToNumber, o as exponential, s as fromConfig, t as BaseJobStore } from "./chunks/base-store-Cac1FH1X.js";
2
2
  import { BullJobStore } from "./adapters/bull.js";
3
3
  import { BullMQJobStore } from "./adapters/bullmq.js";
4
4
  import { CloudTasksJobStore } from "./adapters/cloud-tasks.js";
package/dist/types.d.ts CHANGED
@@ -136,9 +136,12 @@ export interface JobFilter {
136
136
  limit?: number;
137
137
  /** Offset for pagination */
138
138
  offset?: number;
139
- /** Order by field */
139
+ /** Order by field; an unrecognized field falls back to `createdAt` */
140
140
  orderBy?: 'createdAt' | 'runAt' | 'priority' | 'attempts';
141
- /** Order direction */
141
+ /**
142
+ * Order direction, case-insensitive and trimmed; omitted or blank means
143
+ * `desc`. Any other value throws.
144
+ */
142
145
  orderDir?: 'asc' | 'desc';
143
146
  }
144
147
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,SAAS,GACT,WAAW,GACX,QAAQ,GACR,WAAW,CAAC;AAEhB;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,aAAa,CAAC;IAE1D;;OAEG;IACH,QAAQ,IAAI,mBAAmB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,qDAAqD;IACrD,UAAU,EAAE,MAAM,CAAC;IACnB,sDAAsD;IACtD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,GAAG;IAClB,4BAA4B;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,0BAA0B;IAC1B,OAAO,EAAE,UAAU,CAAC;IACpB,yBAAyB;IACzB,MAAM,EAAE,SAAS,CAAC;IAClB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,KAAK,EAAE,IAAI,CAAC;IACZ,qCAAqC;IACrC,SAAS,EAAE,IAAI,GAAG,IAAI,CAAC;IACvB,oCAAoC;IACpC,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;IACzB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,0CAA0C;IAC1C,eAAe,EAAE,eAAe,CAAC;IACjC,mCAAmC;IACnC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oCAAoC;IACpC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,aAAa,EAAE,mBAAmB,CAAC;IACnC,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iCAAiC;IACjC,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;IAC7B,+BAA+B;IAC/B,SAAS,EAAE,IAAI,CAAC;IAChB,oCAAoC;IACpC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,OAAO,EAAE,UAAU,CAAC;IACpB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAChC,iCAAiC;IACjC,KAAK,CAAC,EAAE,IAAI,CAAC;IACb,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,qBAAqB;IACrB,aAAa,CAAC,EAAE,aAAa,GAAG,mBAAmB,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;IACjC,4BAA4B;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mCAAmC;IACnC,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,oCAAoC;IACpC,aAAa,CAAC,EAAE,IAAI,CAAC;IACrB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;IAC1D,sBAAsB;IACtB,QAAQ,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,yCAAyC;IACzC,YAAY,CAAC,EAAE,IAAI,CAAC;IACpB,4CAA4C;IAC5C,eAAe,CAAC,EAAE,IAAI,CAAC;IACvB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,aAAa,GACb,WAAW,GACX,aAAa,GACb,eAAe,GACf,YAAY,GACZ,eAAe,GACf,cAAc,CAAC;AAEnB;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,EAAE,GAAG,CAAC;IACT,SAAS,EAAE,IAAI,CAAC;IAChB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,aAAa;IACb,EAAE,EAAE,MAAM,CAAC;IACX,6BAA6B;IAC7B,MAAM,IAAI,OAAO,CAAC,SAAS,CAAC,CAAC;IAC7B,2BAA2B;IAC3B,GAAG,IAAI,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAC3B,8BAA8B;IAC9B,IAAI,CAAC,OAAO,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,qBAAqB;IACrB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB;;OAEG;IACH,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5B;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAEjD;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAE3E;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAExD;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;IAErC;;OAEG;IACH,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAExC;;OAEG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElC;;OAEG;IACH,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAElD;;OAEG;IACH,SAAS,CAAC,QAAQ,EAAE,gBAAgB,GAAG,WAAW,CAAC;IAEnD;;;OAGG;IACH,aAAa,CAAC,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErD;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE1D;;OAEG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE3C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uBAAuB;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,6CAA6C;IAC7C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4BAA4B;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CACvB,GAAG,EAAE,GAAG,KACL,OAAO,CAAC;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,gBAAgB;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,4BAA4B;IAC5B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,+CAA+C;IAC/C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,iCAAiC;IACjC,SAAS,IAAI,OAAO,CAAC;IACrB,8BAA8B;IAC9B,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAChE,4BAA4B;IAC5B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,IAAI,CAAC;CAClE"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/jobs",
3
- "version": "0.81.0",
3
+ "version": "0.82.0",
4
4
  "description": "Job queue abstraction with multiple backend adapters (SQLite, PostgreSQL, Bull, SQS)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -68,8 +68,8 @@
68
68
  "author": "willgriffin@gmail.com",
69
69
  "license": "ISC",
70
70
  "dependencies": {
71
- "@happyvertical/sql": "0.81.0",
72
- "@happyvertical/utils": "0.81.0"
71
+ "@happyvertical/sql": "0.82.0",
72
+ "@happyvertical/utils": "0.82.0"
73
73
  },
74
74
  "devDependencies": {
75
75
  "@aws-sdk/client-sqs": "^3.1034.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"base-store-DIasEzL0.js","names":[],"sources":["../../src/retry.ts","../../src/base-store.ts"],"sourcesContent":["import type {\n RetryDecision,\n RetryStrategy,\n RetryStrategyConfig,\n} from './types.js';\n\n/**\n * Options for exponential backoff retry strategy\n */\nexport interface ExponentialBackoffOptions {\n /** Initial delay in milliseconds (default: 1000) */\n initialDelay?: number;\n /** Maximum delay in milliseconds (default: 300000 = 5 minutes) */\n maxDelay?: number;\n /** Multiplier for each attempt (default: 2) */\n multiplier?: number;\n /** Add random jitter to prevent thundering herd (default: true) */\n jitter?: boolean;\n /** Maximum attempts (optional, can also be set on job) */\n maxAttempts?: number;\n}\n\n/**\n * Exponential backoff retry strategy\n *\n * Delay increases exponentially: initialDelay * multiplier^(attempt-1)\n * With optional jitter to prevent thundering herd problem.\n */\nclass ExponentialBackoffStrategy implements RetryStrategy {\n private readonly initialDelay: number;\n private readonly maxDelay: number;\n private readonly multiplier: number;\n private readonly jitter: boolean;\n private readonly maxAttempts: number | null;\n\n constructor(options: ExponentialBackoffOptions = {}) {\n this.initialDelay = options.initialDelay ?? 1000;\n this.maxDelay = options.maxDelay ?? 300000;\n this.multiplier = options.multiplier ?? 2;\n this.jitter = options.jitter ?? true;\n this.maxAttempts = options.maxAttempts ?? null;\n }\n\n shouldRetry(attempt: number, _error: Error): RetryDecision {\n // Check max attempts if configured\n if (this.maxAttempts !== null && attempt >= this.maxAttempts) {\n return { shouldRetry: false, delay: 0 };\n }\n\n // Calculate base delay: initialDelay * multiplier^(attempt-1)\n let delay = this.initialDelay * this.multiplier ** (attempt - 1);\n\n // Cap at maxDelay\n delay = Math.min(delay, this.maxDelay);\n\n // Add jitter (±25% randomization)\n if (this.jitter) {\n const jitterRange = delay * 0.25;\n delay = delay - jitterRange + Math.random() * jitterRange * 2;\n }\n\n return { shouldRetry: true, delay: Math.round(delay) };\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'exponential',\n config: {\n initialDelay: this.initialDelay,\n maxDelay: this.maxDelay,\n multiplier: this.multiplier,\n jitter: this.jitter,\n maxAttempts: this.maxAttempts,\n },\n };\n }\n}\n\n/**\n * Options for linear retry strategy\n */\nexport interface LinearBackoffOptions {\n /** Fixed delay between retries in milliseconds (default: 5000) */\n delay?: number;\n /** Maximum attempts (optional) */\n maxAttempts?: number;\n}\n\n/**\n * Linear retry strategy\n *\n * Uses a fixed delay between all retry attempts.\n */\nclass LinearBackoffStrategy implements RetryStrategy {\n private readonly delay: number;\n private readonly maxAttempts: number | null;\n\n constructor(options: LinearBackoffOptions = {}) {\n this.delay = options.delay ?? 5000;\n this.maxAttempts = options.maxAttempts ?? null;\n }\n\n shouldRetry(attempt: number, _error: Error): RetryDecision {\n if (this.maxAttempts !== null && attempt >= this.maxAttempts) {\n return { shouldRetry: false, delay: 0 };\n }\n\n return { shouldRetry: true, delay: this.delay };\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'linear',\n config: {\n delay: this.delay,\n maxAttempts: this.maxAttempts,\n },\n };\n }\n}\n\n/**\n * Custom retry decision function\n */\nexport type CustomRetryFn = (attempt: number, error: Error) => RetryDecision;\n\n/**\n * Custom retry strategy\n *\n * Allows full control over retry logic via a custom function.\n */\nclass CustomRetryStrategy implements RetryStrategy {\n private readonly fn: CustomRetryFn;\n private readonly fnString: string;\n\n constructor(fn: CustomRetryFn) {\n this.fn = fn;\n // Store string representation for serialization\n this.fnString = fn.toString();\n }\n\n shouldRetry(attempt: number, error: Error): RetryDecision {\n return this.fn(attempt, error);\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'custom',\n config: {\n fn: this.fnString,\n },\n };\n }\n}\n\n/**\n * No retry strategy - never retry\n */\nclass NoRetryStrategy implements RetryStrategy {\n shouldRetry(_attempt: number, _error: Error): RetryDecision {\n return { shouldRetry: false, delay: 0 };\n }\n\n toConfig(): RetryStrategyConfig {\n return {\n type: 'linear',\n config: { maxAttempts: 1 },\n };\n }\n}\n\n// Factory functions\n\n/**\n * Create an exponential backoff retry strategy\n *\n * @example\n * ```typescript\n * const strategy = exponential({\n * initialDelay: 1000,\n * maxDelay: 300000,\n * multiplier: 2,\n * jitter: true,\n * });\n * ```\n */\nexport function exponential(\n options?: ExponentialBackoffOptions,\n): RetryStrategy {\n return new ExponentialBackoffStrategy(options);\n}\n\n/**\n * Create a linear retry strategy with fixed delay\n *\n * @example\n * ```typescript\n * const strategy = linear({ delay: 5000 });\n * ```\n */\nexport function linear(options?: LinearBackoffOptions): RetryStrategy {\n return new LinearBackoffStrategy(options);\n}\n\n/**\n * Create a custom retry strategy\n *\n * @example\n * ```typescript\n * const strategy = custom((attempt, error) => {\n * if (error.message.includes('RATE_LIMITED')) {\n * return { shouldRetry: true, delay: 60000 };\n * }\n * return { shouldRetry: attempt < 3, delay: attempt * 1000 };\n * });\n * ```\n */\nexport function custom(fn: CustomRetryFn): RetryStrategy {\n return new CustomRetryStrategy(fn);\n}\n\n/**\n * Create a no-retry strategy\n *\n * @example\n * ```typescript\n * const strategy = noRetry();\n * ```\n */\nexport function noRetry(): RetryStrategy {\n return new NoRetryStrategy();\n}\n\n/**\n * Reconstruct a retry strategy from its config\n */\nexport function fromConfig(config: RetryStrategyConfig): RetryStrategy {\n switch (config.type) {\n case 'exponential':\n return new ExponentialBackoffStrategy(\n config.config as ExponentialBackoffOptions,\n );\n case 'linear':\n return new LinearBackoffStrategy(config.config as LinearBackoffOptions);\n case 'custom':\n // For custom strategies loaded from config, we can't reconstruct the function\n // So we fall back to exponential\n console.warn(\n 'Custom retry strategy cannot be reconstructed from config, using exponential',\n );\n return new ExponentialBackoffStrategy();\n default:\n return new ExponentialBackoffStrategy();\n }\n}\n\n/**\n * Default retry strategy\n */\nexport const DEFAULT_RETRY_STRATEGY = exponential({\n initialDelay: 1000,\n maxDelay: 300000,\n multiplier: 2,\n jitter: true,\n});\n","import { createId } from '@happyvertical/utils';\nimport { DEFAULT_RETRY_STRATEGY } from './retry.js';\nimport type {\n CleanupOptions,\n Job,\n JobCreateOptions,\n JobEvent,\n JobEventListener,\n JobEventType,\n JobFilter,\n JobPriority,\n JobStore,\n QueueStats,\n RetryStrategyConfig,\n} from './types.js';\n\n/**\n * Valid table name pattern: alphanumeric and underscores, must start with letter or underscore\n */\nconst TABLE_NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Validate table name to prevent SQL injection\n * @throws Error if table name contains invalid characters\n */\nexport function validateTableName(tableName: string): string {\n if (!tableName || tableName.length === 0) {\n throw new Error('Table name cannot be empty');\n }\n if (tableName.length > 128) {\n throw new Error('Table name cannot exceed 128 characters');\n }\n if (!TABLE_NAME_PATTERN.test(tableName)) {\n throw new Error(\n `Invalid table name \"${tableName}\": must contain only alphanumeric characters and underscores, and must start with a letter or underscore`,\n );\n }\n return tableName;\n}\n\n/**\n * Convert priority string to number\n */\nexport function priorityToNumber(\n priority: JobPriority | number | undefined,\n): number {\n if (typeof priority === 'number') return priority;\n switch (priority) {\n case 'critical':\n return 100;\n case 'high':\n return 75;\n case 'normal':\n return 50;\n case 'low':\n return 25;\n default:\n return 50;\n }\n}\n\n/**\n * Base job store with common functionality\n */\nexport abstract class BaseJobStore implements JobStore {\n protected listeners: Set<JobEventListener> = new Set();\n protected initialized = false;\n\n /**\n * Initialize the store - must be implemented by subclasses\n */\n abstract initialize(): Promise<void>;\n\n /**\n * Create a new job record\n */\n protected createJobRecord(options: JobCreateOptions): Job {\n const now = new Date();\n const retryConfig: RetryStrategyConfig =\n options.retryStrategy && 'toConfig' in options.retryStrategy\n ? options.retryStrategy.toConfig()\n : ((options.retryStrategy as RetryStrategyConfig) ??\n DEFAULT_RETRY_STRATEGY.toConfig());\n\n return {\n id: createId(),\n queue: options.queue ?? 'default',\n payload: options.payload,\n status: 'pending',\n priority: priorityToNumber(options.priority),\n attempts: 0,\n maxAttempts: options.maxAttempts ?? 3,\n runAt: options.runAt ?? now,\n startedAt: null,\n completedAt: null,\n timeout: options.timeout ?? 300000,\n timeoutBehavior: options.timeoutBehavior ?? 'fail',\n lastError: null,\n resultPointer: null,\n retryStrategy: retryConfig,\n workerId: null,\n workerHeartbeat: null,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n /**\n * Emit a job event to all listeners\n */\n protected async emitEvent(\n type: JobEventType,\n job: Job,\n extra?: { error?: string; resultPointer?: string },\n ): Promise<void> {\n const event: JobEvent = {\n type,\n job,\n timestamp: new Date(),\n ...extra,\n };\n\n const promises = Array.from(this.listeners).map((listener) =>\n Promise.resolve(listener(event)).catch((err) => {\n console.error('Job event listener error:', err);\n }),\n );\n\n await Promise.allSettled(promises);\n }\n\n /**\n * Subscribe to job events\n */\n subscribe(listener: JobEventListener): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Build WHERE clause for filters\n */\n protected buildFilterWhere(filter: JobFilter): {\n where: string;\n params: unknown[];\n } {\n const conditions: string[] = [];\n const params: unknown[] = [];\n\n if (filter.queue) {\n conditions.push('queue = ?');\n params.push(filter.queue);\n }\n\n if (filter.status) {\n if (Array.isArray(filter.status)) {\n const placeholders = filter.status.map(() => '?').join(', ');\n conditions.push(`status IN (${placeholders})`);\n params.push(...filter.status);\n } else {\n conditions.push('status = ?');\n params.push(filter.status);\n }\n }\n\n if (filter.objectType) {\n conditions.push(\"json_extract(payload, '$.objectType') = ?\");\n params.push(filter.objectType);\n }\n\n if (filter.method) {\n conditions.push(\"json_extract(payload, '$.method') = ?\");\n params.push(filter.method);\n }\n\n if (filter.createdAfter) {\n conditions.push('created_at > ?');\n params.push(filter.createdAfter.toISOString());\n }\n\n if (filter.createdBefore) {\n conditions.push('created_at < ?');\n params.push(filter.createdBefore.toISOString());\n }\n\n return {\n where: conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '',\n params,\n };\n }\n\n /**\n * Build ORDER BY clause for filters\n */\n protected buildOrderBy(filter: JobFilter): string {\n const field = filter.orderBy ?? 'createdAt';\n const dir = filter.orderDir ?? 'desc';\n\n const fieldMap: Record<string, string> = {\n createdAt: 'created_at',\n runAt: 'run_at',\n priority: 'priority',\n attempts: 'attempts',\n };\n\n return `ORDER BY ${fieldMap[field] ?? 'created_at'} ${dir.toUpperCase()}`;\n }\n\n /**\n * Build LIMIT/OFFSET clause\n */\n protected buildLimitOffset(filter: JobFilter): {\n clause: string;\n params: unknown[];\n } {\n const params: unknown[] = [];\n let clause = '';\n\n if (filter.limit) {\n clause = 'LIMIT ?';\n params.push(filter.limit);\n\n if (filter.offset) {\n clause += ' OFFSET ?';\n params.push(filter.offset);\n }\n }\n\n return { clause, params };\n }\n\n /**\n * Parse a job row from the database\n */\n protected parseJobRow(row: Record<string, unknown>): Job {\n return {\n id: row.id as string,\n queue: row.queue as string,\n payload:\n typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n status: row.status as Job['status'],\n priority: row.priority as number,\n attempts: row.attempts as number,\n maxAttempts: row.max_attempts as number,\n runAt: new Date(row.run_at as string),\n startedAt: row.started_at ? new Date(row.started_at as string) : null,\n completedAt: row.completed_at\n ? new Date(row.completed_at as string)\n : null,\n timeout: row.timeout as number,\n timeoutBehavior: row.timeout_behavior as Job['timeoutBehavior'],\n lastError: row.last_error as string | null,\n resultPointer: row.result_pointer as string | null,\n retryStrategy:\n typeof row.retry_strategy === 'string'\n ? JSON.parse(row.retry_strategy)\n : row.retry_strategy,\n workerId: row.worker_id as string | null,\n workerHeartbeat: row.worker_heartbeat\n ? new Date(row.worker_heartbeat as string)\n : null,\n createdAt: new Date(row.created_at as string),\n updatedAt: new Date(row.updated_at as string),\n };\n }\n\n // Abstract methods that must be implemented by subclasses\n abstract enqueue(options: JobCreateOptions): Promise<Job>;\n abstract dequeue(\n queues: string[],\n limit: number,\n workerId: string,\n ): Promise<Job[]>;\n abstract update(id: string, updates: Partial<Job>): Promise<Job>;\n abstract get(id: string): Promise<Job | null>;\n abstract list(filter: JobFilter): Promise<Job[]>;\n abstract cancel(id: string): Promise<void>;\n abstract cleanup(options: CleanupOptions): Promise<number>;\n abstract heartbeat(jobId: string, workerId: string): Promise<void>;\n abstract stats(queue?: string): Promise<QueueStats>;\n abstract close(): Promise<void>;\n}\n"],"mappings":"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"}