@boringnode/queue 0.5.0 → 0.5.1

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.
Files changed (37) hide show
  1. package/README.md +71 -0
  2. package/build/{chunk-WOUYSNK2.js → chunk-KI47AJ6U.js} +2 -2
  3. package/build/chunk-PZ5AY32C.js +10 -0
  4. package/build/chunk-PZ5AY32C.js.map +1 -0
  5. package/build/{chunk-ZZFSQY36.js → chunk-QEFYHCL7.js} +4 -6
  6. package/build/{chunk-ZZFSQY36.js.map → chunk-QEFYHCL7.js.map} +1 -1
  7. package/build/{chunk-OVYXMSSU.js → chunk-VHN3XZDC.js} +54 -16
  8. package/build/chunk-VHN3XZDC.js.map +1 -0
  9. package/build/chunk-WVLSICD4.js +20 -0
  10. package/build/chunk-WVLSICD4.js.map +1 -0
  11. package/build/index.d.ts +37 -4
  12. package/build/index.js +64 -51
  13. package/build/index.js.map +1 -1
  14. package/build/{index-B1XdqWpN.d.ts → job-DImdhRFO.d.ts} +16 -1
  15. package/build/src/contracts/adapter.d.ts +1 -1
  16. package/build/src/drivers/fake_adapter.d.ts +1 -1
  17. package/build/src/drivers/fake_adapter.js +4 -2
  18. package/build/src/drivers/knex_adapter.d.ts +1 -1
  19. package/build/src/drivers/knex_adapter.js +2 -1
  20. package/build/src/drivers/knex_adapter.js.map +1 -1
  21. package/build/src/drivers/redis_adapter.d.ts +1 -1
  22. package/build/src/drivers/redis_adapter.js +2 -1
  23. package/build/src/drivers/redis_adapter.js.map +1 -1
  24. package/build/src/drivers/sync_adapter.d.ts +1 -1
  25. package/build/src/drivers/sync_adapter.js +38 -18
  26. package/build/src/drivers/sync_adapter.js.map +1 -1
  27. package/build/src/otel.d.ts +63 -0
  28. package/build/src/otel.js +242 -0
  29. package/build/src/otel.js.map +1 -0
  30. package/build/src/types/index.d.ts +6 -1
  31. package/build/src/types/main.d.ts +1 -1
  32. package/build/src/types/tracing_channels.d.ts +34 -0
  33. package/build/src/types/tracing_channels.js +1 -0
  34. package/build/src/types/tracing_channels.js.map +1 -0
  35. package/package.json +35 -12
  36. package/build/chunk-OVYXMSSU.js.map +0 -1
  37. /package/build/{chunk-WOUYSNK2.js.map → chunk-KI47AJ6U.js.map} +0 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobData,\n JobRecord,\n JobRetention,\n JobStatus,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore, resolveRetention } from '../utils.js'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n schedulesTableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\ntype DbRow = Record<string, unknown>\n\ninterface ScheduleRow extends DbRow {\n id: string\n name: string\n payload: unknown\n cron_expression: string | null\n every_ms: number | string | null\n timezone: string | null\n from_date: Date | string | number | null\n to_date: Date | string | number | null\n run_limit: number | string | null\n run_count: number | string | null\n next_run_at: Date | string | number | null\n last_run_at: Date | string | number | null\n status: string\n created_at: Date | string | number | null\n}\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #jobsTable: string\n readonly #schedulesTable: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#jobsTable = config.tableName ?? 'queue_jobs'\n this.#schedulesTable = config.schedulesTableName ?? 'queue_schedules'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Build the query for highest priority job (lowest score)\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const job = await query.first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n // For SQLite (no SKIP LOCKED), add status='pending' guard to prevent double-claim\n const updateQuery = trx(this.#jobsTable)\n .where('id', job.id)\n .where('queue', queue)\n\n if (!this.#supportsSkipLocked()) {\n updateQuery.where('status', 'pending')\n }\n\n const updated = await updateQuery.update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n // Another worker already claimed this job\n if (updated === 0) {\n return null\n }\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n /**\n * Check if the database supports FOR UPDATE SKIP LOCKED.\n * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.\n * SQLite does not, but it's single-writer so it doesn't need it.\n */\n #supportsSkipLocked(): boolean {\n const client = this.#connection.client.config.client\n return client === 'pg' || client === 'mysql' || client === 'mysql2' || client === 'mariadb'\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Use a transaction with row locking to prevent race conditions\n await this.#connection.transaction(async (trx) => {\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const delayedJobs = await query\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n })\n }\n\n async completeJob(jobId: string, queue: string, removeOnComplete?: JobRetention): Promise<void> {\n const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'completed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'completed', maxAge, maxCount, now)\n }\n\n async failJob(\n jobId: string,\n queue: string,\n error?: Error,\n removeOnFail?: JobRetention\n ): Promise<void> {\n const { keep, maxAge, maxCount } = resolveRetention(removeOnFail)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'failed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n error: error?.message || null,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'failed', maxAge, maxCount, now)\n }\n\n async getJob(jobId: string, queue: string): Promise<JobRecord | null> {\n const row = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .first()\n\n if (!row) {\n return null\n }\n\n const jobData: JobData = JSON.parse(row.data)\n\n return {\n status: row.status as JobStatus,\n data: jobData,\n finishedAt: row.finished_at ? Number(row.finished_at) : undefined,\n error: row.error || undefined,\n }\n }\n\n async #pruneHistory(\n queue: string,\n status: 'completed' | 'failed',\n maxAge: number,\n maxCount: number,\n now: number\n ): Promise<void> {\n if (maxAge > 0) {\n const cutoff = now - maxAge\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .where('finished_at', '<', cutoff)\n .delete()\n }\n\n if (maxCount > 0) {\n const toKeep = this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .orderBy('finished_at', 'desc')\n .limit(maxCount)\n .select('id')\n\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .whereNotIn('id', toKeep)\n .delete()\n }\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n if (jobs.length === 0) return\n\n const now = Date.now()\n const rows = jobs.map((job) => ({\n id: job.id,\n queue,\n status: 'pending' as const,\n data: JSON.stringify(job),\n score: calculateScore(job.priority ?? DEFAULT_PRIORITY, now),\n }))\n\n await this.#connection(this.#jobsTable).insert(rows)\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n const result = await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const now = Date.now()\n const stalledCutoff = now - stalledThreshold\n\n // Use a transaction with row locking to prevent race conditions\n return this.#connection.transaction(async (trx) => {\n let recovered = 0\n\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'active')\n .where('acquired_at', '<', stalledCutoff)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const stalledJobs = await query\n\n for (const row of stalledJobs) {\n const jobData: JobData = JSON.parse(row.data)\n const currentStalledCount = jobData.stalledCount ?? 0\n\n if (currentStalledCount >= maxStalledCount) {\n // Fail permanently - remove the job\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .delete()\n } else {\n // Recover: increment stalledCount and put back in pending\n jobData.stalledCount = currentStalledCount + 1\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: JSON.stringify(jobData),\n worker_id: null,\n acquired_at: null,\n score,\n })\n\n recovered++\n }\n }\n\n return recovered\n })\n }\n\n async upsertSchedule(config: ScheduleConfig): Promise<string> {\n const id = config.id ?? randomUUID()\n\n const data = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n cron_expression: config.cronExpression ?? null,\n every_ms: config.everyMs ?? null,\n timezone: config.timezone,\n from_date: config.from ?? null,\n to_date: config.to ?? null,\n run_limit: config.limit ?? null,\n status: 'active',\n }\n\n // Atomic upsert\n await this.#connection(this.#schedulesTable)\n .insert({\n ...data,\n run_count: 0,\n created_at: this.#connection.fn.now(),\n })\n .onConflict('id')\n .merge({\n name: data.name,\n payload: data.payload,\n cron_expression: data.cron_expression,\n every_ms: data.every_ms,\n timezone: data.timezone,\n from_date: data.from_date,\n to_date: data.to_date,\n run_limit: data.run_limit,\n status: 'active',\n })\n\n return id\n }\n\n /**\n * @deprecated Use `upsertSchedule` instead.\n */\n createSchedule(config: ScheduleConfig): Promise<string> {\n return this.upsertSchedule(config)\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n const row = (await this.#connection(this.#schedulesTable)\n .where('id', id)\n .first()) as ScheduleRow | undefined\n if (!row) return null\n\n return this.#rowToScheduleData(row)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n let query = this.#connection(this.#schedulesTable).whereNot('status', 'cancelled')\n\n if (options?.status) {\n query = query.where('status', options.status)\n }\n\n const rows = (await query) as ScheduleRow[]\n return rows.map((row) => this.#rowToScheduleData(row))\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n const data: Record<string, unknown> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) data.next_run_at = updates.nextRunAt\n if (updates.lastRunAt !== undefined) data.last_run_at = updates.lastRunAt\n if (updates.runCount !== undefined) data.run_count = updates.runCount\n\n if (Object.keys(data).length > 0) {\n await this.#connection(this.#schedulesTable)\n .where('id', id)\n .update(data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n await this.#connection(this.#schedulesTable)\n .where('id', id)\n .delete()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n const now = new Date()\n\n return this.#connection.transaction(async (trx) => {\n // Find one due schedule with row locking\n let query = trx(this.#schedulesTable)\n .where('status', 'active')\n .whereNotNull('next_run_at')\n .where('next_run_at', '<=', now)\n .where((builder) => {\n builder.whereNull('run_limit').orWhereRaw('run_count < run_limit')\n })\n .where((builder) => {\n builder.whereNull('to_date').orWhere('to_date', '>=', now)\n })\n .orderBy('next_run_at', 'asc')\n .limit(1)\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const row = (await query.first()) as ScheduleRow | undefined\n if (!row) return null\n\n // Calculate next run time\n let nextRunAt: Date | null = null\n const newRunCount = Number(row.run_count ?? 0) + 1\n\n if (row.every_ms) {\n nextRunAt = new Date(now.getTime() + Number(row.every_ms))\n } else if (row.cron_expression) {\n // Import cron-parser dynamically to calculate next run\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(row.cron_expression, {\n currentDate: now,\n tz: row.timezone || 'UTC',\n })\n nextRunAt = cron.next().toDate()\n }\n\n // Check if limit will be reached\n if (row.run_limit !== null && newRunCount >= Number(row.run_limit)) {\n nextRunAt = null\n }\n\n // Check if past end date\n if (nextRunAt && row.to_date && nextRunAt > new Date(row.to_date)) {\n nextRunAt = null\n }\n\n // Update atomically\n await trx(this.#schedulesTable)\n .where('id', row.id)\n .update({\n next_run_at: nextRunAt,\n last_run_at: now,\n run_count: newRunCount,\n })\n\n // Return schedule data (before update state for payload)\n return this.#rowToScheduleData(row)\n })\n }\n\n #rowToScheduleData(row: ScheduleRow): ScheduleData {\n return {\n id: row.id,\n name: row.name,\n payload: typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n cronExpression: row.cron_expression ?? null,\n everyMs: row.every_ms ? Number(row.every_ms) : null,\n timezone: row.timezone ?? 'UTC',\n from: row.from_date ? new Date(row.from_date) : null,\n to: row.to_date ? new Date(row.to_date) : null,\n limit: row.run_limit ? Number(row.run_limit) : null,\n runCount: Number(row.run_count ?? 0),\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,\n status: row.status === 'paused' || row.status === 'cancelled' ? 'paused' : 'active',\n createdAt: row.created_at ? new Date(row.created_at) : new Date(),\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/drivers/knex_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport KnexPkg from 'knex'\nimport type { Knex } from 'knex'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobData,\n JobRecord,\n JobRetention,\n JobStatus,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore, resolveRetention } from '../utils.js'\n\nexport interface KnexAdapterOptions {\n connection: Knex\n tableName?: string\n schedulesTableName?: string\n ownsConnection?: boolean\n}\n\ntype KnexConfig = Knex | Knex.Config\ntype DbRow = Record<string, unknown>\n\ninterface ScheduleRow extends DbRow {\n id: string\n name: string\n payload: unknown\n cron_expression: string | null\n every_ms: number | string | null\n timezone: string | null\n from_date: Date | string | number | null\n to_date: Date | string | number | null\n run_limit: number | string | null\n run_count: number | string | null\n next_run_at: Date | string | number | null\n last_run_at: Date | string | number | null\n status: string\n created_at: Date | string | number | null\n}\n\n/**\n * Create a new Knex adapter factory.\n * Accepts either a Knex instance or a Knex configuration object.\n *\n * When passing a config object, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Knex instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function knex(config: KnexConfig, tableName?: string) {\n return () => {\n const isKnexInstance = typeof config === 'function'\n const connection = isKnexInstance ? config : KnexPkg(config)\n return new KnexAdapter({ connection, tableName, ownsConnection: !isKnexInstance })\n }\n}\n\n/**\n * Knex adapter for the queue system.\n * Stores jobs in a SQL database using Knex.\n */\nexport class KnexAdapter implements Adapter {\n readonly #connection: Knex\n readonly #jobsTable: string\n readonly #schedulesTable: string\n readonly #ownsConnection: boolean\n #workerId: string = ''\n\n constructor(config: KnexAdapterOptions) {\n this.#connection = config.connection\n this.#jobsTable = config.tableName ?? 'queue_jobs'\n this.#schedulesTable = config.schedulesTableName ?? 'queue_schedules'\n this.#ownsConnection = config.ownsConnection ?? false\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.destroy()\n }\n }\n\n async pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const now = Date.now()\n\n // First, move ready delayed jobs to pending\n await this.#processDelayedJobs(queue, now)\n\n // Use a transaction to atomically pop a job\n return this.#connection.transaction(async (trx) => {\n // Build the query for highest priority job (lowest score)\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .orderBy('score', 'asc')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const job = await query.first()\n\n if (!job) {\n return null\n }\n\n // Update job to active status\n // For SQLite (no SKIP LOCKED), add status='pending' guard to prevent double-claim\n const updateQuery = trx(this.#jobsTable)\n .where('id', job.id)\n .where('queue', queue)\n\n if (!this.#supportsSkipLocked()) {\n updateQuery.where('status', 'pending')\n }\n\n const updated = await updateQuery.update({\n status: 'active',\n worker_id: this.#workerId,\n acquired_at: now,\n })\n\n // Another worker already claimed this job\n if (updated === 0) {\n return null\n }\n\n const jobData: JobData = JSON.parse(job.data)\n\n return {\n ...jobData,\n acquiredAt: now,\n }\n })\n }\n\n /**\n * Check if the database supports FOR UPDATE SKIP LOCKED.\n * PostgreSQL 9.5+, MySQL 8.0+, and MariaDB 10.6+ support it.\n * SQLite does not, but it's single-writer so it doesn't need it.\n */\n #supportsSkipLocked(): boolean {\n const client = this.#connection.client.config.client\n return client === 'pg' || client === 'mysql' || client === 'mysql2' || client === 'mariadb'\n }\n\n async #processDelayedJobs(queue: string, now: number): Promise<void> {\n // Use a transaction with row locking to prevent race conditions\n await this.#connection.transaction(async (trx) => {\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'delayed')\n .where('execute_at', '<=', now)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const delayedJobs = await query\n\n if (delayedJobs.length === 0) return\n\n // Move them to pending\n for (const job of delayedJobs) {\n const jobData: JobData = JSON.parse(job.data)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', job.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n score,\n execute_at: null,\n })\n }\n })\n }\n\n async completeJob(jobId: string, queue: string, removeOnComplete?: JobRetention): Promise<void> {\n const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'completed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'completed', maxAge, maxCount, now)\n }\n\n async failJob(\n jobId: string,\n queue: string,\n error?: Error,\n removeOnFail?: JobRetention\n ): Promise<void> {\n const { keep, maxAge, maxCount } = resolveRetention(removeOnFail)\n\n if (!keep) {\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .delete()\n return\n }\n\n const now = Date.now()\n\n const updated = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .update({\n status: 'failed',\n worker_id: null,\n acquired_at: null,\n finished_at: now,\n error: error?.message || null,\n })\n\n if (!updated) {\n return\n }\n\n await this.#pruneHistory(queue, 'failed', maxAge, maxCount, now)\n }\n\n async getJob(jobId: string, queue: string): Promise<JobRecord | null> {\n const row = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .first()\n\n if (!row) {\n return null\n }\n\n const jobData: JobData = JSON.parse(row.data)\n\n return {\n status: row.status as JobStatus,\n data: jobData,\n finishedAt: row.finished_at ? Number(row.finished_at) : undefined,\n error: row.error || undefined,\n }\n }\n\n async #pruneHistory(\n queue: string,\n status: 'completed' | 'failed',\n maxAge: number,\n maxCount: number,\n now: number\n ): Promise<void> {\n if (maxAge > 0) {\n const cutoff = now - maxAge\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .where('finished_at', '<', cutoff)\n .delete()\n }\n\n if (maxCount > 0) {\n const toKeep = this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .orderBy('finished_at', 'desc')\n .limit(maxCount)\n .select('id')\n\n await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', status)\n .whereNotIn('id', toKeep)\n .delete()\n }\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n\n // Get the active job\n const activeJob = await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .where('status', 'active')\n .first()\n\n if (!activeJob) return\n\n const jobData: JobData = JSON.parse(activeJob.data)\n jobData.attempts = (jobData.attempts || 0) + 1\n\n const updatedData = JSON.stringify(jobData)\n\n if (retryAt && retryAt.getTime() > now) {\n // Move to delayed\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'delayed',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score: null,\n execute_at: retryAt.getTime(),\n })\n } else {\n // Move back to pending\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await this.#connection(this.#jobsTable)\n .where('id', jobId)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: updatedData,\n worker_id: null,\n acquired_at: null,\n score,\n execute_at: null,\n })\n }\n }\n\n async push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'pending',\n data: JSON.stringify(jobData),\n score,\n })\n }\n\n async pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const executeAt = Date.now() + delay\n\n await this.#connection(this.#jobsTable).insert({\n id: jobData.id,\n queue,\n status: 'delayed',\n data: JSON.stringify(jobData),\n execute_at: executeAt,\n })\n }\n\n async pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n if (jobs.length === 0) return\n\n const now = Date.now()\n const rows = jobs.map((job) => ({\n id: job.id,\n queue,\n status: 'pending' as const,\n data: JSON.stringify(job),\n score: calculateScore(job.priority ?? DEFAULT_PRIORITY, now),\n }))\n\n await this.#connection(this.#jobsTable).insert(rows)\n }\n\n async size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n async sizeOf(queue: string): Promise<number> {\n const result = await this.#connection(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'pending')\n .count('* as count')\n .first()\n\n return Number(result?.count ?? 0)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const now = Date.now()\n const stalledCutoff = now - stalledThreshold\n\n // Use a transaction with row locking to prevent race conditions\n return this.#connection.transaction(async (trx) => {\n let recovered = 0\n\n let query = trx(this.#jobsTable)\n .where('queue', queue)\n .where('status', 'active')\n .where('acquired_at', '<', stalledCutoff)\n .select('id', 'data')\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const stalledJobs = await query\n\n for (const row of stalledJobs) {\n const jobData: JobData = JSON.parse(row.data)\n const currentStalledCount = jobData.stalledCount ?? 0\n\n if (currentStalledCount >= maxStalledCount) {\n // Fail permanently - remove the job\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .delete()\n } else {\n // Recover: increment stalledCount and put back in pending\n jobData.stalledCount = currentStalledCount + 1\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n\n await trx(this.#jobsTable)\n .where('id', row.id)\n .where('queue', queue)\n .update({\n status: 'pending',\n data: JSON.stringify(jobData),\n worker_id: null,\n acquired_at: null,\n score,\n })\n\n recovered++\n }\n }\n\n return recovered\n })\n }\n\n async upsertSchedule(config: ScheduleConfig): Promise<string> {\n const id = config.id ?? randomUUID()\n\n const data = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n cron_expression: config.cronExpression ?? null,\n every_ms: config.everyMs ?? null,\n timezone: config.timezone,\n from_date: config.from ?? null,\n to_date: config.to ?? null,\n run_limit: config.limit ?? null,\n status: 'active',\n }\n\n // Atomic upsert\n await this.#connection(this.#schedulesTable)\n .insert({\n ...data,\n run_count: 0,\n created_at: this.#connection.fn.now(),\n })\n .onConflict('id')\n .merge({\n name: data.name,\n payload: data.payload,\n cron_expression: data.cron_expression,\n every_ms: data.every_ms,\n timezone: data.timezone,\n from_date: data.from_date,\n to_date: data.to_date,\n run_limit: data.run_limit,\n status: 'active',\n })\n\n return id\n }\n\n /**\n * @deprecated Use `upsertSchedule` instead.\n */\n createSchedule(config: ScheduleConfig): Promise<string> {\n return this.upsertSchedule(config)\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n const row = (await this.#connection(this.#schedulesTable)\n .where('id', id)\n .first()) as ScheduleRow | undefined\n if (!row) return null\n\n return this.#rowToScheduleData(row)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n let query = this.#connection(this.#schedulesTable).whereNot('status', 'cancelled')\n\n if (options?.status) {\n query = query.where('status', options.status)\n }\n\n const rows = (await query) as ScheduleRow[]\n return rows.map((row) => this.#rowToScheduleData(row))\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n const data: Record<string, unknown> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) data.next_run_at = updates.nextRunAt\n if (updates.lastRunAt !== undefined) data.last_run_at = updates.lastRunAt\n if (updates.runCount !== undefined) data.run_count = updates.runCount\n\n if (Object.keys(data).length > 0) {\n await this.#connection(this.#schedulesTable)\n .where('id', id)\n .update(data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n await this.#connection(this.#schedulesTable)\n .where('id', id)\n .delete()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n const now = new Date()\n\n return this.#connection.transaction(async (trx) => {\n // Find one due schedule with row locking\n let query = trx(this.#schedulesTable)\n .where('status', 'active')\n .whereNotNull('next_run_at')\n .where('next_run_at', '<=', now)\n .where((builder) => {\n builder.whereNull('run_limit').orWhereRaw('run_count < run_limit')\n })\n .where((builder) => {\n builder.whereNull('to_date').orWhere('to_date', '>=', now)\n })\n .orderBy('next_run_at', 'asc')\n .limit(1)\n\n if (this.#supportsSkipLocked()) {\n query = query.forUpdate().skipLocked()\n }\n\n const row = (await query.first()) as ScheduleRow | undefined\n if (!row) return null\n\n // Calculate next run time\n let nextRunAt: Date | null = null\n const newRunCount = Number(row.run_count ?? 0) + 1\n\n if (row.every_ms) {\n nextRunAt = new Date(now.getTime() + Number(row.every_ms))\n } else if (row.cron_expression) {\n // Import cron-parser dynamically to calculate next run\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(row.cron_expression, {\n currentDate: now,\n tz: row.timezone || 'UTC',\n })\n nextRunAt = cron.next().toDate()\n }\n\n // Check if limit will be reached\n if (row.run_limit !== null && newRunCount >= Number(row.run_limit)) {\n nextRunAt = null\n }\n\n // Check if past end date\n if (nextRunAt && row.to_date && nextRunAt > new Date(row.to_date)) {\n nextRunAt = null\n }\n\n // Update atomically\n await trx(this.#schedulesTable)\n .where('id', row.id)\n .update({\n next_run_at: nextRunAt,\n last_run_at: now,\n run_count: newRunCount,\n })\n\n // Return schedule data (before update state for payload)\n return this.#rowToScheduleData(row)\n })\n }\n\n #rowToScheduleData(row: ScheduleRow): ScheduleData {\n return {\n id: row.id,\n name: row.name,\n payload: typeof row.payload === 'string' ? JSON.parse(row.payload) : row.payload,\n cronExpression: row.cron_expression ?? null,\n everyMs: row.every_ms ? Number(row.every_ms) : null,\n timezone: row.timezone ?? 'UTC',\n from: row.from_date ? new Date(row.from_date) : null,\n to: row.to_date ? new Date(row.to_date) : null,\n limit: row.run_limit ? Number(row.run_limit) : null,\n runCount: Number(row.run_count ?? 0),\n nextRunAt: row.next_run_at ? new Date(row.next_run_at) : null,\n lastRunAt: row.last_run_at ? new Date(row.last_run_at) : null,\n status: row.status === 'paused' || row.status === 'cancelled' ? 'paused' : 'active',\n createdAt: row.created_at ? new Date(row.created_at) : new Date(),\n }\n }\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,kBAAkB;AAC3B,OAAO,aAAa;AAoDb,SAAS,KAAK,QAAoB,WAAoB;AAC3D,SAAO,MAAM;AACX,UAAM,iBAAiB,OAAO,WAAW;AACzC,UAAM,aAAa,iBAAiB,SAAS,QAAQ,MAAM;AAC3D,WAAO,IAAI,YAAY,EAAE,YAAY,WAAW,gBAAgB,CAAC,eAAe,CAAC;AAAA,EACnF;AACF;AAMO,IAAM,cAAN,MAAqC;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT,YAAoB;AAAA,EAEpB,YAAY,QAA4B;AACtC,SAAK,cAAc,OAAO;AAC1B,SAAK,aAAa,OAAO,aAAa;AACtC,SAAK,kBAAkB,OAAO,sBAAsB;AACpD,SAAK,kBAAkB,OAAO,kBAAkB;AAAA,EAClD;AAAA,EAEA,YAAY,UAAwB;AAClC,SAAK,YAAY;AAAA,EACnB;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,iBAAiB;AACxB,YAAM,KAAK,YAAY,QAAQ;AAAA,IACjC;AAAA,EACF;AAAA,EAEA,MAAM,MAAmC;AACvC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EAEA,MAAM,QAAQ,OAA4C;AACxD,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,KAAK,oBAAoB,OAAO,GAAG;AAGzC,WAAO,KAAK,YAAY,YAAY,OAAO,QAAQ;AAEjD,UAAI,QAAQ,IAAI,KAAK,UAAU,EAC5B,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,QAAQ,SAAS,KAAK;AAEzB,UAAI,KAAK,oBAAoB,GAAG;AAC9B,gBAAQ,MAAM,UAAU,EAAE,WAAW;AAAA,MACvC;AAEA,YAAM,MAAM,MAAM,MAAM,MAAM;AAE9B,UAAI,CAAC,KAAK;AACR,eAAO;AAAA,MACT;AAIA,YAAM,cAAc,IAAI,KAAK,UAAU,EACpC,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK;AAEvB,UAAI,CAAC,KAAK,oBAAoB,GAAG;AAC/B,oBAAY,MAAM,UAAU,SAAS;AAAA,MACvC;AAEA,YAAM,UAAU,MAAM,YAAY,OAAO;AAAA,QACvC,QAAQ;AAAA,QACR,WAAW,KAAK;AAAA,QAChB,aAAa;AAAA,MACf,CAAC;AAGD,UAAI,YAAY,GAAG;AACjB,eAAO;AAAA,MACT;AAEA,YAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAE5C,aAAO;AAAA,QACL,GAAG;AAAA,QACH,YAAY;AAAA,MACd;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,sBAA+B;AAC7B,UAAM,SAAS,KAAK,YAAY,OAAO,OAAO;AAC9C,WAAO,WAAW,QAAQ,WAAW,WAAW,WAAW,YAAY,WAAW;AAAA,EACpF;AAAA,EAEA,MAAM,oBAAoB,OAAe,KAA4B;AAEnE,UAAM,KAAK,YAAY,YAAY,OAAO,QAAQ;AAChD,UAAI,QAAQ,IAAI,KAAK,UAAU,EAC5B,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,MAAM,cAAc,MAAM,GAAG,EAC7B,OAAO,MAAM,MAAM;AAEtB,UAAI,KAAK,oBAAoB,GAAG;AAC9B,gBAAQ,MAAM,UAAU,EAAE,WAAW;AAAA,MACvC;AAEA,YAAM,cAAc,MAAM;AAE1B,UAAI,YAAY,WAAW,EAAG;AAG9B,iBAAW,OAAO,aAAa;AAC7B,cAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAC5C,cAAM,WAAW,QAAQ,YAAY;AACrC,cAAM,QAAQ,eAAe,UAAU,GAAG;AAE1C,cAAM,IAAI,KAAK,UAAU,EACtB,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,UACN,QAAQ;AAAA,UACR;AAAA,UACA,YAAY;AAAA,QACd,CAAC;AAAA,MACL;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,YAAY,OAAe,OAAe,kBAAgD;AAC9F,UAAM,EAAE,MAAM,QAAQ,SAAS,IAAI,iBAAiB,gBAAgB;AAEpE,QAAI,CAAC,MAAM;AACT,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,OAAO;AACV;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,UAAU,MAAM,KAAK,YAAY,KAAK,UAAU,EACnD,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,OAAO;AAAA,MACN,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,aAAa;AAAA,MACb,aAAa;AAAA,IACf,CAAC;AAEH,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,UAAM,KAAK,cAAc,OAAO,aAAa,QAAQ,UAAU,GAAG;AAAA,EACpE;AAAA,EAEA,MAAM,QACJ,OACA,OACA,OACA,cACe;AACf,UAAM,EAAE,MAAM,QAAQ,SAAS,IAAI,iBAAiB,YAAY;AAEhE,QAAI,CAAC,MAAM;AACT,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,OAAO;AACV;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,UAAU,MAAM,KAAK,YAAY,KAAK,UAAU,EACnD,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,OAAO;AAAA,MACN,QAAQ;AAAA,MACR,WAAW;AAAA,MACX,aAAa;AAAA,MACb,aAAa;AAAA,MACb,OAAO,OAAO,WAAW;AAAA,IAC3B,CAAC;AAEH,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,UAAM,KAAK,cAAc,OAAO,UAAU,QAAQ,UAAU,GAAG;AAAA,EACjE;AAAA,EAEA,MAAM,OAAO,OAAe,OAA0C;AACpE,UAAM,MAAM,MAAM,KAAK,YAAY,KAAK,UAAU,EAC/C,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM;AAET,QAAI,CAAC,KAAK;AACR,aAAO;AAAA,IACT;AAEA,UAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAE5C,WAAO;AAAA,MACL,QAAQ,IAAI;AAAA,MACZ,MAAM;AAAA,MACN,YAAY,IAAI,cAAc,OAAO,IAAI,WAAW,IAAI;AAAA,MACxD,OAAO,IAAI,SAAS;AAAA,IACtB;AAAA,EACF;AAAA,EAEA,MAAM,cACJ,OACA,QACA,QACA,UACA,KACe;AACf,QAAI,SAAS,GAAG;AACd,YAAM,SAAS,MAAM;AACrB,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,MAAM,EACtB,MAAM,eAAe,KAAK,MAAM,EAChC,OAAO;AAAA,IACZ;AAEA,QAAI,WAAW,GAAG;AAChB,YAAM,SAAS,KAAK,YAAY,KAAK,UAAU,EAC5C,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,MAAM,EACtB,QAAQ,eAAe,MAAM,EAC7B,MAAM,QAAQ,EACd,OAAO,IAAI;AAEd,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,MAAM,EACtB,WAAW,MAAM,MAAM,EACvB,OAAO;AAAA,IACZ;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,OAAe,OAAe,SAA+B;AAC1E,UAAM,MAAM,KAAK,IAAI;AAGrB,UAAM,YAAY,MAAM,KAAK,YAAY,KAAK,UAAU,EACrD,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,MAAM;AAET,QAAI,CAAC,UAAW;AAEhB,UAAM,UAAmB,KAAK,MAAM,UAAU,IAAI;AAClD,YAAQ,YAAY,QAAQ,YAAY,KAAK;AAE7C,UAAM,cAAc,KAAK,UAAU,OAAO;AAE1C,QAAI,WAAW,QAAQ,QAAQ,IAAI,KAAK;AAEtC,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb,OAAO;AAAA,QACP,YAAY,QAAQ,QAAQ;AAAA,MAC9B,CAAC;AAAA,IACL,OAAO;AAEL,YAAM,WAAW,QAAQ,YAAY;AACrC,YAAM,QAAQ,eAAe,UAAU,GAAG;AAE1C,YAAM,KAAK,YAAY,KAAK,UAAU,EACnC,MAAM,MAAM,KAAK,EACjB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACN,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb;AAAA,QACA,YAAY;AAAA,MACd,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,MAAM,KAAK,SAAiC;AAC1C,WAAO,KAAK,OAAO,WAAW,OAAO;AAAA,EACvC;AAAA,EAEA,MAAM,OAAO,OAAe,SAAiC;AAC3D,UAAM,WAAW,QAAQ,YAAY;AACrC,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,QAAQ,eAAe,UAAU,SAAS;AAEhD,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7C,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAU,SAAkB,OAA8B;AAC9D,WAAO,KAAK,YAAY,WAAW,SAAS,KAAK;AAAA,EACnD;AAAA,EAEA,MAAM,YAAY,OAAe,SAAkB,OAA8B;AAC/E,UAAM,YAAY,KAAK,IAAI,IAAI;AAE/B,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO;AAAA,MAC7C,IAAI,QAAQ;AAAA,MACZ;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,OAAO;AAAA,MAC5B,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,MAAgC;AAC7C,WAAO,KAAK,WAAW,WAAW,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,WAAW,OAAe,MAAgC;AAC9D,QAAI,KAAK,WAAW,EAAG;AAEvB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,OAAO,KAAK,IAAI,CAAC,SAAS;AAAA,MAC9B,IAAI,IAAI;AAAA,MACR;AAAA,MACA,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,GAAG;AAAA,MACxB,OAAO,eAAe,IAAI,YAAY,kBAAkB,GAAG;AAAA,IAC7D,EAAE;AAEF,UAAM,KAAK,YAAY,KAAK,UAAU,EAAE,OAAO,IAAI;AAAA,EACrD;AAAA,EAEA,MAAM,OAAwB;AAC5B,WAAO,KAAK,OAAO,SAAS;AAAA,EAC9B;AAAA,EAEA,MAAM,OAAO,OAAgC;AAC3C,UAAM,SAAS,MAAM,KAAK,YAAY,KAAK,UAAU,EAClD,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,SAAS,EACzB,MAAM,YAAY,EAClB,MAAM;AAET,WAAO,OAAO,QAAQ,SAAS,CAAC;AAAA,EAClC;AAAA,EAEA,MAAM,mBACJ,OACA,kBACA,iBACiB;AACjB,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,gBAAgB,MAAM;AAG5B,WAAO,KAAK,YAAY,YAAY,OAAO,QAAQ;AACjD,UAAI,YAAY;AAEhB,UAAI,QAAQ,IAAI,KAAK,UAAU,EAC5B,MAAM,SAAS,KAAK,EACpB,MAAM,UAAU,QAAQ,EACxB,MAAM,eAAe,KAAK,aAAa,EACvC,OAAO,MAAM,MAAM;AAEtB,UAAI,KAAK,oBAAoB,GAAG;AAC9B,gBAAQ,MAAM,UAAU,EAAE,WAAW;AAAA,MACvC;AAEA,YAAM,cAAc,MAAM;AAE1B,iBAAW,OAAO,aAAa;AAC7B,cAAM,UAAmB,KAAK,MAAM,IAAI,IAAI;AAC5C,cAAM,sBAAsB,QAAQ,gBAAgB;AAEpD,YAAI,uBAAuB,iBAAiB;AAE1C,gBAAM,IAAI,KAAK,UAAU,EACtB,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,QACZ,OAAO;AAEL,kBAAQ,eAAe,sBAAsB;AAC7C,gBAAM,WAAW,QAAQ,YAAY;AACrC,gBAAM,QAAQ,eAAe,UAAU,GAAG;AAE1C,gBAAM,IAAI,KAAK,UAAU,EACtB,MAAM,MAAM,IAAI,EAAE,EAClB,MAAM,SAAS,KAAK,EACpB,OAAO;AAAA,YACN,QAAQ;AAAA,YACR,MAAM,KAAK,UAAU,OAAO;AAAA,YAC5B,WAAW;AAAA,YACX,aAAa;AAAA,YACb;AAAA,UACF,CAAC;AAEH;AAAA,QACF;AAAA,MACF;AAEA,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,eAAe,QAAyC;AAC5D,UAAM,KAAK,OAAO,MAAM,WAAW;AAEnC,UAAM,OAAO;AAAA,MACX;AAAA,MACA,MAAM,OAAO;AAAA,MACb,SAAS,KAAK,UAAU,OAAO,OAAO;AAAA,MACtC,iBAAiB,OAAO,kBAAkB;AAAA,MAC1C,UAAU,OAAO,WAAW;AAAA,MAC5B,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO,QAAQ;AAAA,MAC1B,SAAS,OAAO,MAAM;AAAA,MACtB,WAAW,OAAO,SAAS;AAAA,MAC3B,QAAQ;AAAA,IACV;AAGA,UAAM,KAAK,YAAY,KAAK,eAAe,EACxC,OAAO;AAAA,MACN,GAAG;AAAA,MACH,WAAW;AAAA,MACX,YAAY,KAAK,YAAY,GAAG,IAAI;AAAA,IACtC,CAAC,EACA,WAAW,IAAI,EACf,MAAM;AAAA,MACL,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,iBAAiB,KAAK;AAAA,MACtB,UAAU,KAAK;AAAA,MACf,UAAU,KAAK;AAAA,MACf,WAAW,KAAK;AAAA,MAChB,SAAS,KAAK;AAAA,MACd,WAAW,KAAK;AAAA,MAChB,QAAQ;AAAA,IACV,CAAC;AAEH,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAyC;AACtD,WAAO,KAAK,eAAe,MAAM;AAAA,EACnC;AAAA,EAEA,MAAM,YAAY,IAA0C;AAC1D,UAAM,MAAO,MAAM,KAAK,YAAY,KAAK,eAAe,EACrD,MAAM,MAAM,EAAE,EACd,MAAM;AACT,QAAI,CAAC,IAAK,QAAO;AAEjB,WAAO,KAAK,mBAAmB,GAAG;AAAA,EACpC;AAAA,EAEA,MAAM,cAAc,SAAwD;AAC1E,QAAI,QAAQ,KAAK,YAAY,KAAK,eAAe,EAAE,SAAS,UAAU,WAAW;AAEjF,QAAI,SAAS,QAAQ;AACnB,cAAQ,MAAM,MAAM,UAAU,QAAQ,MAAM;AAAA,IAC9C;AAEA,UAAM,OAAQ,MAAM;AACpB,WAAO,KAAK,IAAI,CAAC,QAAQ,KAAK,mBAAmB,GAAG,CAAC;AAAA,EACvD;AAAA,EAEA,MAAM,eACJ,IACA,SACe;AACf,UAAM,OAAgC,CAAC;AAEvC,QAAI,QAAQ,WAAW,OAAW,MAAK,SAAS,QAAQ;AACxD,QAAI,QAAQ,cAAc,OAAW,MAAK,cAAc,QAAQ;AAChE,QAAI,QAAQ,cAAc,OAAW,MAAK,cAAc,QAAQ;AAChE,QAAI,QAAQ,aAAa,OAAW,MAAK,YAAY,QAAQ;AAE7D,QAAI,OAAO,KAAK,IAAI,EAAE,SAAS,GAAG;AAChC,YAAM,KAAK,YAAY,KAAK,eAAe,EACxC,MAAM,MAAM,EAAE,EACd,OAAO,IAAI;AAAA,IAChB;AAAA,EACF;AAAA,EAEA,MAAM,eAAe,IAA2B;AAC9C,UAAM,KAAK,YAAY,KAAK,eAAe,EACxC,MAAM,MAAM,EAAE,EACd,OAAO;AAAA,EACZ;AAAA,EAEA,MAAM,mBAAiD;AACrD,UAAM,MAAM,oBAAI,KAAK;AAErB,WAAO,KAAK,YAAY,YAAY,OAAO,QAAQ;AAEjD,UAAI,QAAQ,IAAI,KAAK,eAAe,EACjC,MAAM,UAAU,QAAQ,EACxB,aAAa,aAAa,EAC1B,MAAM,eAAe,MAAM,GAAG,EAC9B,MAAM,CAAC,YAAY;AAClB,gBAAQ,UAAU,WAAW,EAAE,WAAW,uBAAuB;AAAA,MACnE,CAAC,EACA,MAAM,CAAC,YAAY;AAClB,gBAAQ,UAAU,SAAS,EAAE,QAAQ,WAAW,MAAM,GAAG;AAAA,MAC3D,CAAC,EACA,QAAQ,eAAe,KAAK,EAC5B,MAAM,CAAC;AAEV,UAAI,KAAK,oBAAoB,GAAG;AAC9B,gBAAQ,MAAM,UAAU,EAAE,WAAW;AAAA,MACvC;AAEA,YAAM,MAAO,MAAM,MAAM,MAAM;AAC/B,UAAI,CAAC,IAAK,QAAO;AAGjB,UAAI,YAAyB;AAC7B,YAAM,cAAc,OAAO,IAAI,aAAa,CAAC,IAAI;AAEjD,UAAI,IAAI,UAAU;AAChB,oBAAY,IAAI,KAAK,IAAI,QAAQ,IAAI,OAAO,IAAI,QAAQ,CAAC;AAAA,MAC3D,WAAW,IAAI,iBAAiB;AAE9B,cAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,aAAa;AAC3D,cAAM,OAAO,qBAAqB,MAAM,IAAI,iBAAiB;AAAA,UAC3D,aAAa;AAAA,UACb,IAAI,IAAI,YAAY;AAAA,QACtB,CAAC;AACD,oBAAY,KAAK,KAAK,EAAE,OAAO;AAAA,MACjC;AAGA,UAAI,IAAI,cAAc,QAAQ,eAAe,OAAO,IAAI,SAAS,GAAG;AAClE,oBAAY;AAAA,MACd;AAGA,UAAI,aAAa,IAAI,WAAW,YAAY,IAAI,KAAK,IAAI,OAAO,GAAG;AACjE,oBAAY;AAAA,MACd;AAGA,YAAM,IAAI,KAAK,eAAe,EAC3B,MAAM,MAAM,IAAI,EAAE,EAClB,OAAO;AAAA,QACN,aAAa;AAAA,QACb,aAAa;AAAA,QACb,WAAW;AAAA,MACb,CAAC;AAGH,aAAO,KAAK,mBAAmB,GAAG;AAAA,IACpC,CAAC;AAAA,EACH;AAAA,EAEA,mBAAmB,KAAgC;AACjD,WAAO;AAAA,MACL,IAAI,IAAI;AAAA,MACR,MAAM,IAAI;AAAA,MACV,SAAS,OAAO,IAAI,YAAY,WAAW,KAAK,MAAM,IAAI,OAAO,IAAI,IAAI;AAAA,MACzE,gBAAgB,IAAI,mBAAmB;AAAA,MACvC,SAAS,IAAI,WAAW,OAAO,IAAI,QAAQ,IAAI;AAAA,MAC/C,UAAU,IAAI,YAAY;AAAA,MAC1B,MAAM,IAAI,YAAY,IAAI,KAAK,IAAI,SAAS,IAAI;AAAA,MAChD,IAAI,IAAI,UAAU,IAAI,KAAK,IAAI,OAAO,IAAI;AAAA,MAC1C,OAAO,IAAI,YAAY,OAAO,IAAI,SAAS,IAAI;AAAA,MAC/C,UAAU,OAAO,IAAI,aAAa,CAAC;AAAA,MACnC,WAAW,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI;AAAA,MACzD,WAAW,IAAI,cAAc,IAAI,KAAK,IAAI,WAAW,IAAI;AAAA,MACzD,QAAQ,IAAI,WAAW,YAAY,IAAI,WAAW,cAAc,WAAW;AAAA,MAC3E,WAAW,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,IAAI,oBAAI,KAAK;AAAA,IAClE;AAAA,EACF;AACF;","names":[]}
@@ -1,5 +1,5 @@
1
1
  import { Redis, RedisOptions } from 'ioredis';
2
- import { A as Adapter, b as AcquiredJob, c as JobRetention, d as JobRecord, J as JobData, S as ScheduleConfig, e as ScheduleData, f as ScheduleListOptions } from '../../index-B1XdqWpN.js';
2
+ import { A as Adapter, b as AcquiredJob, c as JobRetention, d as JobRecord, J as JobData, S as ScheduleConfig, e as ScheduleData, f as ScheduleListOptions } from '../../job-DImdhRFO.js';
3
3
 
4
4
  type RedisConfig = Redis | RedisOptions;
5
5
  /**
@@ -2,7 +2,8 @@ import {
2
2
  DEFAULT_PRIORITY,
3
3
  calculateScore,
4
4
  resolveRetention
5
- } from "../../chunk-ZZFSQY36.js";
5
+ } from "../../chunk-QEFYHCL7.js";
6
+ import "../../chunk-PZ5AY32C.js";
6
7
 
7
8
  // src/drivers/redis_adapter.ts
8
9
  import { randomUUID } from "crypto";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/drivers/redis_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { Redis, type RedisOptions } from 'ioredis'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore } from '../utils.js'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobData,\n JobRecord,\n JobRetention,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { resolveRetention } from '../utils.js'\n\nconst redisKey = 'jobs'\nconst schedulesKey = 'schedules'\nconst schedulesIndexKey = 'schedules::index'\ntype RedisConfig = Redis | RedisOptions\n\n/**\n * Lua script for pushing a job to the queue.\n * Stores job data in the central hash and adds jobId to pending ZSET.\n */\nconst PUSH_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local pending_key = KEYS[2]\n local job_id = ARGV[1]\n local job_data = ARGV[2]\n local score = tonumber(ARGV[3])\n\n redis.call('HSET', data_key, job_id, job_data)\n redis.call('ZADD', pending_key, score, job_id)\n\n return 1\n`\n\n/**\n * Lua script for pushing a delayed job.\n * Stores job data in the central hash and adds jobId to delayed ZSET.\n */\nconst PUSH_DELAYED_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local delayed_key = KEYS[2]\n local job_id = ARGV[1]\n local job_data = ARGV[2]\n local execute_at = tonumber(ARGV[3])\n\n redis.call('HSET', data_key, job_id, job_data)\n redis.call('ZADD', delayed_key, execute_at, job_id)\n\n return 1\n`\n\n/**\n * Lua script for atomic job acquisition.\n * 1. Check and process delayed jobs\n * 2. Pop from pending queue\n * 3. Add to active hash with worker info\n * 4. Return job data\n */\nconst ACQUIRE_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local pending_key = KEYS[2]\n local active_key = KEYS[3]\n local delayed_key = KEYS[4]\n local worker_id = ARGV[1]\n local now = tonumber(ARGV[2])\n\n -- Process delayed jobs: move ready jobs to pending\n local ready_job_ids = redis.call('ZRANGEBYSCORE', delayed_key, 0, now)\n if #ready_job_ids > 0 then\n for i = 1, #ready_job_ids do\n local job_id = ready_job_ids[i]\n local job_data = redis.call('HGET', data_key, job_id)\n if job_data then\n local job = cjson.decode(job_data)\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_id)\n redis.call('ZREM', delayed_key, job_id)\n end\n end\n end\n\n -- Pop highest priority job (lowest score)\n local result = redis.call('ZPOPMIN', pending_key)\n if not result or #result == 0 then\n return nil\n end\n\n local job_id = result[1]\n local job_data = redis.call('HGET', data_key, job_id)\n if not job_data then\n return nil\n end\n\n -- Store in active hash (without data, it's in data_key)\n local active_data = cjson.encode({\n workerId = worker_id,\n acquiredAt = now\n })\n redis.call('HSET', active_key, job_id, active_data)\n\n -- Return job with acquiredAt\n local job = cjson.decode(job_data)\n job.acquiredAt = now\n return cjson.encode(job)\n`\n\n/**\n * Lua script for removing a job completely (no history).\n */\nconst REMOVE_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local job_id = ARGV[1]\n\n if redis.call('HEXISTS', active_key, job_id) == 0 then\n return 0\n end\n\n redis.call('HDEL', active_key, job_id)\n redis.call('HDEL', data_key, job_id)\n\n return 1\n`\n\n/**\n * Lua script for finalizing a job in history.\n * Removes from active, stores finalization info, and prunes old records.\n */\nconst FINALIZE_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local history_key = KEYS[3]\n local index_key = KEYS[4]\n local job_id = ARGV[1]\n local now = tonumber(ARGV[2])\n local max_age = tonumber(ARGV[3])\n local max_count = tonumber(ARGV[4])\n local error_message = ARGV[5]\n\n -- Verify job is active\n if redis.call('HEXISTS', active_key, job_id) == 0 then\n return 0\n end\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Store finalization info (data stays in data_key)\n local record = {\n finishedAt = now\n }\n if error_message and error_message ~= '' then\n record.error = error_message\n end\n redis.call('HSET', history_key, job_id, cjson.encode(record))\n redis.call('ZADD', index_key, now, job_id)\n\n -- Prune by age\n if max_age and max_age > 0 then\n local cutoff = now - max_age\n local expired = redis.call('ZRANGEBYSCORE', index_key, 0, cutoff)\n if #expired > 0 then\n redis.call('ZREM', index_key, unpack(expired))\n redis.call('HDEL', history_key, unpack(expired))\n redis.call('HDEL', data_key, unpack(expired))\n end\n end\n\n -- Prune by count\n if max_count and max_count > 0 then\n local size = tonumber(redis.call('ZCARD', index_key))\n if size > max_count then\n local excess = size - max_count\n local stale = redis.call('ZRANGE', index_key, 0, excess - 1)\n if #stale > 0 then\n redis.call('ZREM', index_key, unpack(stale))\n redis.call('HDEL', history_key, unpack(stale))\n redis.call('HDEL', data_key, unpack(stale))\n end\n end\n end\n\n return 1\n`\n\n/**\n * Lua script for retrying a job.\n * 1. Verify job is active\n * 2. Remove from active hash\n * 3. Increment attempts in data\n * 4. Add back to pending (or delayed if retryAt is set)\n */\nconst RETRY_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local pending_key = KEYS[3]\n local delayed_key = KEYS[4]\n local job_id = ARGV[1]\n local retry_at = tonumber(ARGV[2])\n local now = tonumber(ARGV[3])\n\n -- Verify job is active\n if redis.call('HEXISTS', active_key, job_id) == 0 then\n return 0\n end\n\n -- Get job data\n local job_data = redis.call('HGET', data_key, job_id)\n if not job_data then\n return 0\n end\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Increment attempts and update data\n local job = cjson.decode(job_data)\n job.attempts = (job.attempts or 0) + 1\n redis.call('HSET', data_key, job_id, cjson.encode(job))\n\n -- Add back to pending or delayed\n if retry_at and retry_at > now then\n redis.call('ZADD', delayed_key, retry_at, job_id)\n else\n -- Score = priority * 1e13 + timestamp\n -- Lower score = higher priority, FIFO within same priority\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_id)\n end\n\n return 1\n`\n\n/**\n * Lua script for recovering stalled jobs.\n * Scans the active hash for jobs that have been active too long.\n * - Jobs within maxStalledCount: move back to pending with incremented stalledCount\n * - Jobs exceeding maxStalledCount: remove permanently (fail)\n * Returns the number of recovered jobs (not including failed ones).\n */\nconst RECOVER_STALLED_JOBS_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local pending_key = KEYS[3]\n local now = tonumber(ARGV[1])\n local stalled_threshold = tonumber(ARGV[2])\n local max_stalled_count = tonumber(ARGV[3])\n\n local recovered = 0\n local stalled_cutoff = now - stalled_threshold\n\n -- Get all active jobs\n local active_jobs = redis.call('HGETALL', active_key)\n\n -- HGETALL returns [field1, value1, field2, value2, ...]\n for i = 1, #active_jobs, 2 do\n local job_id = active_jobs[i]\n local active_data = active_jobs[i + 1]\n local active = cjson.decode(active_data)\n\n -- Check if job is stalled\n if active.acquiredAt < stalled_cutoff then\n local job_data = redis.call('HGET', data_key, job_id)\n if job_data then\n local job = cjson.decode(job_data)\n local current_stalled_count = job.stalledCount or 0\n\n -- Remove from active hash\n redis.call('HDEL', active_key, job_id)\n\n -- Check if job has exceeded max stalled count\n if current_stalled_count >= max_stalled_count then\n -- Job failed permanently, remove data too\n redis.call('HDEL', data_key, job_id)\n else\n -- Recover: increment stalledCount and put back in pending\n job.stalledCount = current_stalled_count + 1\n redis.call('HSET', data_key, job_id, cjson.encode(job))\n -- Score = priority * 1e13 + timestamp\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_id)\n recovered = recovered + 1\n end\n end\n end\n end\n\n return recovered\n`\n\n/**\n * Lua script for getting a job record with its status.\n */\nconst GET_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local pending_key = KEYS[2]\n local delayed_key = KEYS[3]\n local active_key = KEYS[4]\n local completed_key = KEYS[5]\n local failed_key = KEYS[6]\n local job_id = ARGV[1]\n\n local job_data = redis.call('HGET', data_key, job_id)\n if not job_data then\n return nil\n end\n\n local status = nil\n local finished_at = nil\n local error_msg = nil\n\n -- Check status in order\n if redis.call('HEXISTS', active_key, job_id) == 1 then\n status = 'active'\n elseif redis.call('ZSCORE', pending_key, job_id) then\n status = 'pending'\n elseif redis.call('ZSCORE', delayed_key, job_id) then\n status = 'delayed'\n else\n local completed_data = redis.call('HGET', completed_key, job_id)\n if completed_data then\n status = 'completed'\n local record = cjson.decode(completed_data)\n finished_at = record.finishedAt\n else\n local failed_data = redis.call('HGET', failed_key, job_id)\n if failed_data then\n status = 'failed'\n local record = cjson.decode(failed_data)\n finished_at = record.finishedAt\n error_msg = record.error\n end\n end\n end\n\n if not status then\n return nil\n end\n\n return cjson.encode({\n status = status,\n data = cjson.decode(job_data),\n finishedAt = finished_at,\n error = error_msg\n })\n`\n\n/**\n * Lua script for atomically claiming a due schedule.\n * Takes a schedule key as KEYS[1] and checks if it's due.\n * Returns the schedule data if claimed, nil otherwise.\n *\n * This script is called per-schedule from the JS side which handles iteration.\n */\nconst CLAIM_SCHEDULE_SCRIPT = `\n local schedule_key = KEYS[1]\n local now = tonumber(ARGV[1])\n\n -- Get schedule data\n local data = redis.call('HGETALL', schedule_key)\n if #data == 0 then\n return nil\n end\n\n -- Convert HGETALL result to table\n local schedule = {}\n for j = 1, #data, 2 do\n schedule[data[j]] = data[j + 1]\n end\n\n -- Check if schedule is due\n if schedule.status ~= 'active' then\n return nil\n end\n\n local next_run_at = tonumber(schedule.next_run_at)\n if not next_run_at or next_run_at > now then\n return nil\n end\n\n local run_count = tonumber(schedule.run_count or '0')\n local run_limit = schedule.run_limit and tonumber(schedule.run_limit) or nil\n local to_date = schedule.to_date and tonumber(schedule.to_date) or nil\n\n -- Check limits\n if run_limit and run_count >= run_limit then\n return nil\n end\n\n if to_date and now > to_date then\n return nil\n end\n\n -- This schedule is claimable - atomically update it\n local new_run_count = run_count + 1\n\n -- Calculate new next_run_at (simple interval-based for now)\n -- Complex cron calculation happens in the caller\n local new_next_run_at = ''\n local every_ms = schedule.every_ms and tonumber(schedule.every_ms) or nil\n if every_ms then\n new_next_run_at = tostring(now + every_ms)\n end\n\n -- Check if we've hit the limit after this run\n if run_limit and new_run_count >= run_limit then\n new_next_run_at = ''\n end\n\n -- Check if past end date\n if to_date and new_next_run_at ~= '' and tonumber(new_next_run_at) > to_date then\n new_next_run_at = ''\n end\n\n -- Update the schedule atomically\n redis.call('HSET', schedule_key,\n 'next_run_at', new_next_run_at,\n 'last_run_at', tostring(now),\n 'run_count', tostring(new_run_count))\n\n -- Return the schedule data (before update) as JSON\n return cjson.encode(schedule)\n`\n\n/**\n * Create a new Redis adapter factory.\n * Accepts either a Redis instance or Redis options.\n *\n * When passing options, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Redis instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function redis(config?: RedisConfig) {\n return () => {\n if (config instanceof Redis) {\n return new RedisAdapter(config, false)\n }\n\n const options: RedisOptions = {\n host: 'localhost',\n port: 6379,\n keyPrefix: 'boringnode::queue::',\n db: 0,\n ...config,\n }\n\n const connection = new Redis(options)\n return new RedisAdapter(connection, true)\n }\n}\n\nexport class RedisAdapter implements Adapter {\n readonly #connection: Redis\n readonly #ownsConnection: boolean\n #workerId: string = ''\n\n constructor(connection: Redis, ownsConnection: boolean = false) {\n this.#connection = connection\n this.#ownsConnection = ownsConnection\n }\n\n #getKeys(queue: string) {\n return {\n data: `${redisKey}::${queue}::data`,\n pending: `${redisKey}::${queue}::pending`,\n delayed: `${redisKey}::${queue}::delayed`,\n active: `${redisKey}::${queue}::active`,\n completed: `${redisKey}::${queue}::completed`,\n completedIndex: `${redisKey}::${queue}::completed::index`,\n failed: `${redisKey}::${queue}::failed`,\n failedIndex: `${redisKey}::${queue}::failed::index`,\n }\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.quit()\n }\n }\n\n pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const keys = this.#getKeys(queue)\n const now = Date.now()\n\n const result = await this.#connection.eval(\n ACQUIRE_JOB_SCRIPT,\n 4,\n keys.data,\n keys.pending,\n keys.active,\n keys.delayed,\n this.#workerId,\n now.toString()\n )\n\n if (!result) {\n return null\n }\n\n return JSON.parse(result as string)\n }\n\n async completeJob(jobId: string, queue: string, removeOnComplete?: JobRetention): Promise<void> {\n const keys = this.#getKeys(queue)\n const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete)\n\n if (!keep) {\n await this.#connection.eval(REMOVE_JOB_SCRIPT, 2, keys.data, keys.active, jobId)\n return\n }\n\n await this.#connection.eval(\n FINALIZE_JOB_SCRIPT,\n 4,\n keys.data,\n keys.active,\n keys.completed,\n keys.completedIndex,\n jobId,\n Date.now().toString(),\n maxAge.toString(),\n maxCount.toString(),\n ''\n )\n }\n\n async failJob(\n jobId: string,\n queue: string,\n error?: Error,\n removeOnFail?: JobRetention\n ): Promise<void> {\n const keys = this.#getKeys(queue)\n const { keep, maxAge, maxCount } = resolveRetention(removeOnFail)\n\n if (!keep) {\n await this.#connection.eval(REMOVE_JOB_SCRIPT, 2, keys.data, keys.active, jobId)\n return\n }\n\n await this.#connection.eval(\n FINALIZE_JOB_SCRIPT,\n 4,\n keys.data,\n keys.active,\n keys.failed,\n keys.failedIndex,\n jobId,\n Date.now().toString(),\n maxAge.toString(),\n maxCount.toString(),\n error?.message || ''\n )\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const keys = this.#getKeys(queue)\n const now = Date.now()\n\n await this.#connection.eval(\n RETRY_JOB_SCRIPT,\n 4,\n keys.data,\n keys.active,\n keys.pending,\n keys.delayed,\n jobId,\n retryAt ? retryAt.getTime().toString() : '0',\n now.toString()\n )\n }\n\n async getJob(jobId: string, queue: string): Promise<JobRecord | null> {\n const keys = this.#getKeys(queue)\n\n const result = await this.#connection.eval(\n GET_JOB_SCRIPT,\n 6,\n keys.data,\n keys.pending,\n keys.delayed,\n keys.active,\n keys.completed,\n keys.failed,\n jobId\n )\n\n if (!result) {\n return null\n }\n\n return JSON.parse(result as string)\n }\n\n push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const keys = this.#getKeys(queue)\n const executeAt = Date.now() + delay\n\n await this.#connection.eval(\n PUSH_DELAYED_JOB_SCRIPT,\n 2,\n keys.data,\n keys.delayed,\n jobData.id,\n JSON.stringify(jobData),\n executeAt.toString()\n )\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const keys = this.#getKeys(queue)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection.eval(\n PUSH_JOB_SCRIPT,\n 2,\n keys.data,\n keys.pending,\n jobData.id,\n JSON.stringify(jobData),\n score.toString()\n )\n }\n\n pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n if (jobs.length === 0) return\n\n const keys = this.#getKeys(queue)\n const now = Date.now()\n const multi = this.#connection.multi()\n\n for (const job of jobs) {\n const priority = job.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n multi.hset(keys.data, job.id, JSON.stringify(job))\n multi.zadd(keys.pending, score, job.id)\n }\n\n await multi.exec()\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(queue: string): Promise<number> {\n const keys = this.#getKeys(queue)\n return this.#connection.zcard(keys.pending)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const keys = this.#getKeys(queue)\n const now = Date.now()\n\n const recovered = await this.#connection.eval(\n RECOVER_STALLED_JOBS_SCRIPT,\n 3,\n keys.data,\n keys.active,\n keys.pending,\n now.toString(),\n stalledThreshold.toString(),\n maxStalledCount.toString()\n )\n\n return recovered as number\n }\n\n async upsertSchedule(config: ScheduleConfig): Promise<string> {\n const id = config.id ?? randomUUID()\n const now = Date.now()\n const scheduleKey = `${schedulesKey}::${id}`\n const [existingRunCount, existingCreatedAt] = await this.#connection.hmget(\n scheduleKey,\n 'run_count',\n 'created_at'\n )\n\n const scheduleData: Record<string, string> = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n timezone: config.timezone,\n status: 'active',\n run_count: existingRunCount ?? '0',\n created_at: existingCreatedAt ?? now.toString(),\n }\n\n if (config.cronExpression !== undefined) scheduleData.cron_expression = config.cronExpression\n if (config.everyMs !== undefined) scheduleData.every_ms = config.everyMs.toString()\n if (config.from !== undefined) scheduleData.from_date = config.from.getTime().toString()\n if (config.to !== undefined) scheduleData.to_date = config.to.getTime().toString()\n if (config.limit !== undefined) scheduleData.run_limit = config.limit.toString()\n\n // Upsert schedule and clear stale optional fields from previous config.\n await this.#connection\n .multi()\n .hdel(scheduleKey, 'cron_expression', 'every_ms', 'from_date', 'to_date', 'run_limit')\n .hset(scheduleKey, scheduleData)\n .sadd(schedulesIndexKey, id)\n .exec()\n\n return id\n }\n\n /**\n * @deprecated Use `upsertSchedule` instead.\n */\n createSchedule(config: ScheduleConfig): Promise<string> {\n return this.upsertSchedule(config)\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n const scheduleKey = `${schedulesKey}::${id}`\n const data = await this.#connection.hgetall(scheduleKey)\n\n if (!data || Object.keys(data).length === 0) {\n return null\n }\n\n return this.#hashToScheduleData(data)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n const ids = await this.#connection.smembers(schedulesIndexKey)\n if (ids.length === 0) {\n return []\n }\n\n const pipeline = this.#connection.pipeline()\n\n for (const id of ids) {\n pipeline.hgetall(`${schedulesKey}::${id}`)\n }\n\n const results = await pipeline.exec()\n if (!results) {\n return []\n }\n\n const schedules: ScheduleData[] = []\n\n for (const [, data] of results) {\n if (!data || Object.keys(data).length === 0) {\n continue\n }\n\n const schedule = this.#hashToScheduleData(data as Record<string, string>)\n\n // Filter by status if provided\n if (options?.status && schedule.status !== options.status) {\n continue\n }\n\n schedules.push(schedule)\n }\n\n return schedules\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n const scheduleKey = `${schedulesKey}::${id}`\n const data: Record<string, string> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) {\n data.next_run_at = updates.nextRunAt ? updates.nextRunAt.getTime().toString() : ''\n }\n if (updates.lastRunAt !== undefined) {\n data.last_run_at = updates.lastRunAt ? updates.lastRunAt.getTime().toString() : ''\n }\n if (updates.runCount !== undefined) data.run_count = updates.runCount.toString()\n\n if (Object.keys(data).length > 0) {\n await this.#connection.hset(scheduleKey, data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n const scheduleKey = `${schedulesKey}::${id}`\n await this.#connection.multi().del(scheduleKey).srem(schedulesIndexKey, id).exec()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n const now = Date.now()\n const ids = await this.#connection.smembers(schedulesIndexKey)\n\n // Try to claim each schedule atomically using Lua script\n for (const id of ids) {\n const scheduleKey = `${schedulesKey}::${id}`\n\n // Use Lua script for atomic check-and-update\n const result = await this.#connection.eval(\n CLAIM_SCHEDULE_SCRIPT,\n 1,\n scheduleKey,\n now.toString()\n )\n\n if (!result) {\n continue\n }\n\n const data = JSON.parse(result as string) as Record<string, string>\n\n // If cron expression, we need to recalculate next_run_at properly\n // The Lua script only handles simple interval; cron needs JS cron-parser\n // This is safe because the schedule is already claimed (run_count incremented)\n if (data.cron_expression) {\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(data.cron_expression, {\n currentDate: new Date(now),\n tz: data.timezone || 'UTC',\n })\n const nextRun = cron.next().toDate().getTime()\n\n // Check limits before updating\n const runCount = Number.parseInt(data.run_count || '0', 10) + 1\n const runLimit = data.run_limit ? Number.parseInt(data.run_limit, 10) : null\n const toDate = data.to_date ? Number.parseInt(data.to_date, 10) : null\n\n let newNextRunAt: number | string = nextRun\n\n if (runLimit !== null && runCount >= runLimit) {\n newNextRunAt = ''\n } else if (toDate && nextRun > toDate) {\n newNextRunAt = ''\n }\n\n await this.#connection.hset(scheduleKey, 'next_run_at', newNextRunAt.toString())\n }\n\n return this.#hashToScheduleData(data)\n }\n\n return null\n }\n\n #hashToScheduleData(data: Record<string, string>): ScheduleData {\n return {\n id: data.id,\n name: data.name,\n payload: JSON.parse(data.payload || '{}'),\n cronExpression: data.cron_expression || null,\n everyMs: data.every_ms ? Number.parseInt(data.every_ms, 10) : null,\n timezone: data.timezone || 'UTC',\n from: data.from_date ? new Date(Number.parseInt(data.from_date, 10)) : null,\n to: data.to_date ? new Date(Number.parseInt(data.to_date, 10)) : null,\n limit: data.run_limit ? Number.parseInt(data.run_limit, 10) : null,\n runCount: Number.parseInt(data.run_count || '0', 10),\n nextRunAt: data.next_run_at ? new Date(Number.parseInt(data.next_run_at, 10)) : null,\n lastRunAt: data.last_run_at ? new Date(Number.parseInt(data.last_run_at, 10)) : null,\n status: (data.status as 'active' | 'paused') || 'active',\n createdAt: data.created_at ? new Date(Number.parseInt(data.created_at, 10)) : new Date(),\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/drivers/redis_adapter.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\nimport { Redis, type RedisOptions } from 'ioredis'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore } from '../utils.js'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobData,\n JobRecord,\n JobRetention,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { resolveRetention } from '../utils.js'\n\nconst redisKey = 'jobs'\nconst schedulesKey = 'schedules'\nconst schedulesIndexKey = 'schedules::index'\ntype RedisConfig = Redis | RedisOptions\n\n/**\n * Lua script for pushing a job to the queue.\n * Stores job data in the central hash and adds jobId to pending ZSET.\n */\nconst PUSH_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local pending_key = KEYS[2]\n local job_id = ARGV[1]\n local job_data = ARGV[2]\n local score = tonumber(ARGV[3])\n\n redis.call('HSET', data_key, job_id, job_data)\n redis.call('ZADD', pending_key, score, job_id)\n\n return 1\n`\n\n/**\n * Lua script for pushing a delayed job.\n * Stores job data in the central hash and adds jobId to delayed ZSET.\n */\nconst PUSH_DELAYED_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local delayed_key = KEYS[2]\n local job_id = ARGV[1]\n local job_data = ARGV[2]\n local execute_at = tonumber(ARGV[3])\n\n redis.call('HSET', data_key, job_id, job_data)\n redis.call('ZADD', delayed_key, execute_at, job_id)\n\n return 1\n`\n\n/**\n * Lua script for atomic job acquisition.\n * 1. Check and process delayed jobs\n * 2. Pop from pending queue\n * 3. Add to active hash with worker info\n * 4. Return job data\n */\nconst ACQUIRE_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local pending_key = KEYS[2]\n local active_key = KEYS[3]\n local delayed_key = KEYS[4]\n local worker_id = ARGV[1]\n local now = tonumber(ARGV[2])\n\n -- Process delayed jobs: move ready jobs to pending\n local ready_job_ids = redis.call('ZRANGEBYSCORE', delayed_key, 0, now)\n if #ready_job_ids > 0 then\n for i = 1, #ready_job_ids do\n local job_id = ready_job_ids[i]\n local job_data = redis.call('HGET', data_key, job_id)\n if job_data then\n local job = cjson.decode(job_data)\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_id)\n redis.call('ZREM', delayed_key, job_id)\n end\n end\n end\n\n -- Pop highest priority job (lowest score)\n local result = redis.call('ZPOPMIN', pending_key)\n if not result or #result == 0 then\n return nil\n end\n\n local job_id = result[1]\n local job_data = redis.call('HGET', data_key, job_id)\n if not job_data then\n return nil\n end\n\n -- Store in active hash (without data, it's in data_key)\n local active_data = cjson.encode({\n workerId = worker_id,\n acquiredAt = now\n })\n redis.call('HSET', active_key, job_id, active_data)\n\n -- Return job with acquiredAt\n local job = cjson.decode(job_data)\n job.acquiredAt = now\n return cjson.encode(job)\n`\n\n/**\n * Lua script for removing a job completely (no history).\n */\nconst REMOVE_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local job_id = ARGV[1]\n\n if redis.call('HEXISTS', active_key, job_id) == 0 then\n return 0\n end\n\n redis.call('HDEL', active_key, job_id)\n redis.call('HDEL', data_key, job_id)\n\n return 1\n`\n\n/**\n * Lua script for finalizing a job in history.\n * Removes from active, stores finalization info, and prunes old records.\n */\nconst FINALIZE_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local history_key = KEYS[3]\n local index_key = KEYS[4]\n local job_id = ARGV[1]\n local now = tonumber(ARGV[2])\n local max_age = tonumber(ARGV[3])\n local max_count = tonumber(ARGV[4])\n local error_message = ARGV[5]\n\n -- Verify job is active\n if redis.call('HEXISTS', active_key, job_id) == 0 then\n return 0\n end\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Store finalization info (data stays in data_key)\n local record = {\n finishedAt = now\n }\n if error_message and error_message ~= '' then\n record.error = error_message\n end\n redis.call('HSET', history_key, job_id, cjson.encode(record))\n redis.call('ZADD', index_key, now, job_id)\n\n -- Prune by age\n if max_age and max_age > 0 then\n local cutoff = now - max_age\n local expired = redis.call('ZRANGEBYSCORE', index_key, 0, cutoff)\n if #expired > 0 then\n redis.call('ZREM', index_key, unpack(expired))\n redis.call('HDEL', history_key, unpack(expired))\n redis.call('HDEL', data_key, unpack(expired))\n end\n end\n\n -- Prune by count\n if max_count and max_count > 0 then\n local size = tonumber(redis.call('ZCARD', index_key))\n if size > max_count then\n local excess = size - max_count\n local stale = redis.call('ZRANGE', index_key, 0, excess - 1)\n if #stale > 0 then\n redis.call('ZREM', index_key, unpack(stale))\n redis.call('HDEL', history_key, unpack(stale))\n redis.call('HDEL', data_key, unpack(stale))\n end\n end\n end\n\n return 1\n`\n\n/**\n * Lua script for retrying a job.\n * 1. Verify job is active\n * 2. Remove from active hash\n * 3. Increment attempts in data\n * 4. Add back to pending (or delayed if retryAt is set)\n */\nconst RETRY_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local pending_key = KEYS[3]\n local delayed_key = KEYS[4]\n local job_id = ARGV[1]\n local retry_at = tonumber(ARGV[2])\n local now = tonumber(ARGV[3])\n\n -- Verify job is active\n if redis.call('HEXISTS', active_key, job_id) == 0 then\n return 0\n end\n\n -- Get job data\n local job_data = redis.call('HGET', data_key, job_id)\n if not job_data then\n return 0\n end\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Increment attempts and update data\n local job = cjson.decode(job_data)\n job.attempts = (job.attempts or 0) + 1\n redis.call('HSET', data_key, job_id, cjson.encode(job))\n\n -- Add back to pending or delayed\n if retry_at and retry_at > now then\n redis.call('ZADD', delayed_key, retry_at, job_id)\n else\n -- Score = priority * 1e13 + timestamp\n -- Lower score = higher priority, FIFO within same priority\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_id)\n end\n\n return 1\n`\n\n/**\n * Lua script for recovering stalled jobs.\n * Scans the active hash for jobs that have been active too long.\n * - Jobs within maxStalledCount: move back to pending with incremented stalledCount\n * - Jobs exceeding maxStalledCount: remove permanently (fail)\n * Returns the number of recovered jobs (not including failed ones).\n */\nconst RECOVER_STALLED_JOBS_SCRIPT = `\n local data_key = KEYS[1]\n local active_key = KEYS[2]\n local pending_key = KEYS[3]\n local now = tonumber(ARGV[1])\n local stalled_threshold = tonumber(ARGV[2])\n local max_stalled_count = tonumber(ARGV[3])\n\n local recovered = 0\n local stalled_cutoff = now - stalled_threshold\n\n -- Get all active jobs\n local active_jobs = redis.call('HGETALL', active_key)\n\n -- HGETALL returns [field1, value1, field2, value2, ...]\n for i = 1, #active_jobs, 2 do\n local job_id = active_jobs[i]\n local active_data = active_jobs[i + 1]\n local active = cjson.decode(active_data)\n\n -- Check if job is stalled\n if active.acquiredAt < stalled_cutoff then\n local job_data = redis.call('HGET', data_key, job_id)\n if job_data then\n local job = cjson.decode(job_data)\n local current_stalled_count = job.stalledCount or 0\n\n -- Remove from active hash\n redis.call('HDEL', active_key, job_id)\n\n -- Check if job has exceeded max stalled count\n if current_stalled_count >= max_stalled_count then\n -- Job failed permanently, remove data too\n redis.call('HDEL', data_key, job_id)\n else\n -- Recover: increment stalledCount and put back in pending\n job.stalledCount = current_stalled_count + 1\n redis.call('HSET', data_key, job_id, cjson.encode(job))\n -- Score = priority * 1e13 + timestamp\n local priority = job.priority or 5\n local score = priority * 10000000000000 + now\n redis.call('ZADD', pending_key, score, job_id)\n recovered = recovered + 1\n end\n end\n end\n end\n\n return recovered\n`\n\n/**\n * Lua script for getting a job record with its status.\n */\nconst GET_JOB_SCRIPT = `\n local data_key = KEYS[1]\n local pending_key = KEYS[2]\n local delayed_key = KEYS[3]\n local active_key = KEYS[4]\n local completed_key = KEYS[5]\n local failed_key = KEYS[6]\n local job_id = ARGV[1]\n\n local job_data = redis.call('HGET', data_key, job_id)\n if not job_data then\n return nil\n end\n\n local status = nil\n local finished_at = nil\n local error_msg = nil\n\n -- Check status in order\n if redis.call('HEXISTS', active_key, job_id) == 1 then\n status = 'active'\n elseif redis.call('ZSCORE', pending_key, job_id) then\n status = 'pending'\n elseif redis.call('ZSCORE', delayed_key, job_id) then\n status = 'delayed'\n else\n local completed_data = redis.call('HGET', completed_key, job_id)\n if completed_data then\n status = 'completed'\n local record = cjson.decode(completed_data)\n finished_at = record.finishedAt\n else\n local failed_data = redis.call('HGET', failed_key, job_id)\n if failed_data then\n status = 'failed'\n local record = cjson.decode(failed_data)\n finished_at = record.finishedAt\n error_msg = record.error\n end\n end\n end\n\n if not status then\n return nil\n end\n\n return cjson.encode({\n status = status,\n data = cjson.decode(job_data),\n finishedAt = finished_at,\n error = error_msg\n })\n`\n\n/**\n * Lua script for atomically claiming a due schedule.\n * Takes a schedule key as KEYS[1] and checks if it's due.\n * Returns the schedule data if claimed, nil otherwise.\n *\n * This script is called per-schedule from the JS side which handles iteration.\n */\nconst CLAIM_SCHEDULE_SCRIPT = `\n local schedule_key = KEYS[1]\n local now = tonumber(ARGV[1])\n\n -- Get schedule data\n local data = redis.call('HGETALL', schedule_key)\n if #data == 0 then\n return nil\n end\n\n -- Convert HGETALL result to table\n local schedule = {}\n for j = 1, #data, 2 do\n schedule[data[j]] = data[j + 1]\n end\n\n -- Check if schedule is due\n if schedule.status ~= 'active' then\n return nil\n end\n\n local next_run_at = tonumber(schedule.next_run_at)\n if not next_run_at or next_run_at > now then\n return nil\n end\n\n local run_count = tonumber(schedule.run_count or '0')\n local run_limit = schedule.run_limit and tonumber(schedule.run_limit) or nil\n local to_date = schedule.to_date and tonumber(schedule.to_date) or nil\n\n -- Check limits\n if run_limit and run_count >= run_limit then\n return nil\n end\n\n if to_date and now > to_date then\n return nil\n end\n\n -- This schedule is claimable - atomically update it\n local new_run_count = run_count + 1\n\n -- Calculate new next_run_at (simple interval-based for now)\n -- Complex cron calculation happens in the caller\n local new_next_run_at = ''\n local every_ms = schedule.every_ms and tonumber(schedule.every_ms) or nil\n if every_ms then\n new_next_run_at = tostring(now + every_ms)\n end\n\n -- Check if we've hit the limit after this run\n if run_limit and new_run_count >= run_limit then\n new_next_run_at = ''\n end\n\n -- Check if past end date\n if to_date and new_next_run_at ~= '' and tonumber(new_next_run_at) > to_date then\n new_next_run_at = ''\n end\n\n -- Update the schedule atomically\n redis.call('HSET', schedule_key,\n 'next_run_at', new_next_run_at,\n 'last_run_at', tostring(now),\n 'run_count', tostring(new_run_count))\n\n -- Return the schedule data (before update) as JSON\n return cjson.encode(schedule)\n`\n\n/**\n * Create a new Redis adapter factory.\n * Accepts either a Redis instance or Redis options.\n *\n * When passing options, the adapter will create and manage\n * the connection lifecycle (closing it on destroy).\n *\n * When passing a Redis instance, the caller is responsible for\n * managing the connection lifecycle.\n */\nexport function redis(config?: RedisConfig) {\n return () => {\n if (config instanceof Redis) {\n return new RedisAdapter(config, false)\n }\n\n const options: RedisOptions = {\n host: 'localhost',\n port: 6379,\n keyPrefix: 'boringnode::queue::',\n db: 0,\n ...config,\n }\n\n const connection = new Redis(options)\n return new RedisAdapter(connection, true)\n }\n}\n\nexport class RedisAdapter implements Adapter {\n readonly #connection: Redis\n readonly #ownsConnection: boolean\n #workerId: string = ''\n\n constructor(connection: Redis, ownsConnection: boolean = false) {\n this.#connection = connection\n this.#ownsConnection = ownsConnection\n }\n\n #getKeys(queue: string) {\n return {\n data: `${redisKey}::${queue}::data`,\n pending: `${redisKey}::${queue}::pending`,\n delayed: `${redisKey}::${queue}::delayed`,\n active: `${redisKey}::${queue}::active`,\n completed: `${redisKey}::${queue}::completed`,\n completedIndex: `${redisKey}::${queue}::completed::index`,\n failed: `${redisKey}::${queue}::failed`,\n failedIndex: `${redisKey}::${queue}::failed::index`,\n }\n }\n\n setWorkerId(workerId: string): void {\n this.#workerId = workerId\n }\n\n async destroy(): Promise<void> {\n if (this.#ownsConnection) {\n await this.#connection.quit()\n }\n }\n\n pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n async popFrom(queue: string): Promise<AcquiredJob | null> {\n const keys = this.#getKeys(queue)\n const now = Date.now()\n\n const result = await this.#connection.eval(\n ACQUIRE_JOB_SCRIPT,\n 4,\n keys.data,\n keys.pending,\n keys.active,\n keys.delayed,\n this.#workerId,\n now.toString()\n )\n\n if (!result) {\n return null\n }\n\n return JSON.parse(result as string)\n }\n\n async completeJob(jobId: string, queue: string, removeOnComplete?: JobRetention): Promise<void> {\n const keys = this.#getKeys(queue)\n const { keep, maxAge, maxCount } = resolveRetention(removeOnComplete)\n\n if (!keep) {\n await this.#connection.eval(REMOVE_JOB_SCRIPT, 2, keys.data, keys.active, jobId)\n return\n }\n\n await this.#connection.eval(\n FINALIZE_JOB_SCRIPT,\n 4,\n keys.data,\n keys.active,\n keys.completed,\n keys.completedIndex,\n jobId,\n Date.now().toString(),\n maxAge.toString(),\n maxCount.toString(),\n ''\n )\n }\n\n async failJob(\n jobId: string,\n queue: string,\n error?: Error,\n removeOnFail?: JobRetention\n ): Promise<void> {\n const keys = this.#getKeys(queue)\n const { keep, maxAge, maxCount } = resolveRetention(removeOnFail)\n\n if (!keep) {\n await this.#connection.eval(REMOVE_JOB_SCRIPT, 2, keys.data, keys.active, jobId)\n return\n }\n\n await this.#connection.eval(\n FINALIZE_JOB_SCRIPT,\n 4,\n keys.data,\n keys.active,\n keys.failed,\n keys.failedIndex,\n jobId,\n Date.now().toString(),\n maxAge.toString(),\n maxCount.toString(),\n error?.message || ''\n )\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const keys = this.#getKeys(queue)\n const now = Date.now()\n\n await this.#connection.eval(\n RETRY_JOB_SCRIPT,\n 4,\n keys.data,\n keys.active,\n keys.pending,\n keys.delayed,\n jobId,\n retryAt ? retryAt.getTime().toString() : '0',\n now.toString()\n )\n }\n\n async getJob(jobId: string, queue: string): Promise<JobRecord | null> {\n const keys = this.#getKeys(queue)\n\n const result = await this.#connection.eval(\n GET_JOB_SCRIPT,\n 6,\n keys.data,\n keys.pending,\n keys.delayed,\n keys.active,\n keys.completed,\n keys.failed,\n jobId\n )\n\n if (!result) {\n return null\n }\n\n return JSON.parse(result as string)\n }\n\n push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n async pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n const keys = this.#getKeys(queue)\n const executeAt = Date.now() + delay\n\n await this.#connection.eval(\n PUSH_DELAYED_JOB_SCRIPT,\n 2,\n keys.data,\n keys.delayed,\n jobData.id,\n JSON.stringify(jobData),\n executeAt.toString()\n )\n }\n\n async pushOn(queue: string, jobData: JobData): Promise<void> {\n const keys = this.#getKeys(queue)\n const priority = jobData.priority ?? DEFAULT_PRIORITY\n const timestamp = Date.now()\n const score = calculateScore(priority, timestamp)\n\n await this.#connection.eval(\n PUSH_JOB_SCRIPT,\n 2,\n keys.data,\n keys.pending,\n jobData.id,\n JSON.stringify(jobData),\n score.toString()\n )\n }\n\n pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n if (jobs.length === 0) return\n\n const keys = this.#getKeys(queue)\n const now = Date.now()\n const multi = this.#connection.multi()\n\n for (const job of jobs) {\n const priority = job.priority ?? DEFAULT_PRIORITY\n const score = calculateScore(priority, now)\n multi.hset(keys.data, job.id, JSON.stringify(job))\n multi.zadd(keys.pending, score, job.id)\n }\n\n await multi.exec()\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(queue: string): Promise<number> {\n const keys = this.#getKeys(queue)\n return this.#connection.zcard(keys.pending)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const keys = this.#getKeys(queue)\n const now = Date.now()\n\n const recovered = await this.#connection.eval(\n RECOVER_STALLED_JOBS_SCRIPT,\n 3,\n keys.data,\n keys.active,\n keys.pending,\n now.toString(),\n stalledThreshold.toString(),\n maxStalledCount.toString()\n )\n\n return recovered as number\n }\n\n async upsertSchedule(config: ScheduleConfig): Promise<string> {\n const id = config.id ?? randomUUID()\n const now = Date.now()\n const scheduleKey = `${schedulesKey}::${id}`\n const [existingRunCount, existingCreatedAt] = await this.#connection.hmget(\n scheduleKey,\n 'run_count',\n 'created_at'\n )\n\n const scheduleData: Record<string, string> = {\n id,\n name: config.name,\n payload: JSON.stringify(config.payload),\n timezone: config.timezone,\n status: 'active',\n run_count: existingRunCount ?? '0',\n created_at: existingCreatedAt ?? now.toString(),\n }\n\n if (config.cronExpression !== undefined) scheduleData.cron_expression = config.cronExpression\n if (config.everyMs !== undefined) scheduleData.every_ms = config.everyMs.toString()\n if (config.from !== undefined) scheduleData.from_date = config.from.getTime().toString()\n if (config.to !== undefined) scheduleData.to_date = config.to.getTime().toString()\n if (config.limit !== undefined) scheduleData.run_limit = config.limit.toString()\n\n // Upsert schedule and clear stale optional fields from previous config.\n await this.#connection\n .multi()\n .hdel(scheduleKey, 'cron_expression', 'every_ms', 'from_date', 'to_date', 'run_limit')\n .hset(scheduleKey, scheduleData)\n .sadd(schedulesIndexKey, id)\n .exec()\n\n return id\n }\n\n /**\n * @deprecated Use `upsertSchedule` instead.\n */\n createSchedule(config: ScheduleConfig): Promise<string> {\n return this.upsertSchedule(config)\n }\n\n async getSchedule(id: string): Promise<ScheduleData | null> {\n const scheduleKey = `${schedulesKey}::${id}`\n const data = await this.#connection.hgetall(scheduleKey)\n\n if (!data || Object.keys(data).length === 0) {\n return null\n }\n\n return this.#hashToScheduleData(data)\n }\n\n async listSchedules(options?: ScheduleListOptions): Promise<ScheduleData[]> {\n const ids = await this.#connection.smembers(schedulesIndexKey)\n if (ids.length === 0) {\n return []\n }\n\n const pipeline = this.#connection.pipeline()\n\n for (const id of ids) {\n pipeline.hgetall(`${schedulesKey}::${id}`)\n }\n\n const results = await pipeline.exec()\n if (!results) {\n return []\n }\n\n const schedules: ScheduleData[] = []\n\n for (const [, data] of results) {\n if (!data || Object.keys(data).length === 0) {\n continue\n }\n\n const schedule = this.#hashToScheduleData(data as Record<string, string>)\n\n // Filter by status if provided\n if (options?.status && schedule.status !== options.status) {\n continue\n }\n\n schedules.push(schedule)\n }\n\n return schedules\n }\n\n async updateSchedule(\n id: string,\n updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n const scheduleKey = `${schedulesKey}::${id}`\n const data: Record<string, string> = {}\n\n if (updates.status !== undefined) data.status = updates.status\n if (updates.nextRunAt !== undefined) {\n data.next_run_at = updates.nextRunAt ? updates.nextRunAt.getTime().toString() : ''\n }\n if (updates.lastRunAt !== undefined) {\n data.last_run_at = updates.lastRunAt ? updates.lastRunAt.getTime().toString() : ''\n }\n if (updates.runCount !== undefined) data.run_count = updates.runCount.toString()\n\n if (Object.keys(data).length > 0) {\n await this.#connection.hset(scheduleKey, data)\n }\n }\n\n async deleteSchedule(id: string): Promise<void> {\n const scheduleKey = `${schedulesKey}::${id}`\n await this.#connection.multi().del(scheduleKey).srem(schedulesIndexKey, id).exec()\n }\n\n async claimDueSchedule(): Promise<ScheduleData | null> {\n const now = Date.now()\n const ids = await this.#connection.smembers(schedulesIndexKey)\n\n // Try to claim each schedule atomically using Lua script\n for (const id of ids) {\n const scheduleKey = `${schedulesKey}::${id}`\n\n // Use Lua script for atomic check-and-update\n const result = await this.#connection.eval(\n CLAIM_SCHEDULE_SCRIPT,\n 1,\n scheduleKey,\n now.toString()\n )\n\n if (!result) {\n continue\n }\n\n const data = JSON.parse(result as string) as Record<string, string>\n\n // If cron expression, we need to recalculate next_run_at properly\n // The Lua script only handles simple interval; cron needs JS cron-parser\n // This is safe because the schedule is already claimed (run_count incremented)\n if (data.cron_expression) {\n const { CronExpressionParser } = await import('cron-parser')\n const cron = CronExpressionParser.parse(data.cron_expression, {\n currentDate: new Date(now),\n tz: data.timezone || 'UTC',\n })\n const nextRun = cron.next().toDate().getTime()\n\n // Check limits before updating\n const runCount = Number.parseInt(data.run_count || '0', 10) + 1\n const runLimit = data.run_limit ? Number.parseInt(data.run_limit, 10) : null\n const toDate = data.to_date ? Number.parseInt(data.to_date, 10) : null\n\n let newNextRunAt: number | string = nextRun\n\n if (runLimit !== null && runCount >= runLimit) {\n newNextRunAt = ''\n } else if (toDate && nextRun > toDate) {\n newNextRunAt = ''\n }\n\n await this.#connection.hset(scheduleKey, 'next_run_at', newNextRunAt.toString())\n }\n\n return this.#hashToScheduleData(data)\n }\n\n return null\n }\n\n #hashToScheduleData(data: Record<string, string>): ScheduleData {\n return {\n id: data.id,\n name: data.name,\n payload: JSON.parse(data.payload || '{}'),\n cronExpression: data.cron_expression || null,\n everyMs: data.every_ms ? Number.parseInt(data.every_ms, 10) : null,\n timezone: data.timezone || 'UTC',\n from: data.from_date ? new Date(Number.parseInt(data.from_date, 10)) : null,\n to: data.to_date ? new Date(Number.parseInt(data.to_date, 10)) : null,\n limit: data.run_limit ? Number.parseInt(data.run_limit, 10) : null,\n runCount: Number.parseInt(data.run_count || '0', 10),\n nextRunAt: data.next_run_at ? new Date(Number.parseInt(data.next_run_at, 10)) : null,\n lastRunAt: data.last_run_at ? new Date(Number.parseInt(data.last_run_at, 10)) : null,\n status: (data.status as 'active' | 'paused') || 'active',\n createdAt: data.created_at ? new Date(Number.parseInt(data.created_at, 10)) : new Date(),\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { A as Adapter, J as JobData, b as AcquiredJob, c as JobRetention, S as ScheduleConfig, e as ScheduleData, f as ScheduleListOptions } from '../../index-B1XdqWpN.js';
1
+ import { A as Adapter, J as JobData, b as AcquiredJob, c as JobRetention, S as ScheduleConfig, e as ScheduleData, f as ScheduleListOptions } from '../../job-DImdhRFO.js';
2
2
 
3
3
  /**
4
4
  * Create a sync adapter factory.
@@ -1,13 +1,17 @@
1
1
  import {
2
2
  JobExecutionRuntime
3
- } from "../../chunk-WOUYSNK2.js";
3
+ } from "../../chunk-KI47AJ6U.js";
4
4
  import {
5
5
  Locator,
6
6
  QueueManager
7
- } from "../../chunk-OVYXMSSU.js";
7
+ } from "../../chunk-VHN3XZDC.js";
8
+ import {
9
+ executeChannel
10
+ } from "../../chunk-WVLSICD4.js";
8
11
  import {
9
12
  DEFAULT_PRIORITY
10
- } from "../../chunk-ZZFSQY36.js";
13
+ } from "../../chunk-QEFYHCL7.js";
14
+ import "../../chunk-PZ5AY32C.js";
11
15
 
12
16
  // src/drivers/sync_adapter.ts
13
17
  import { setTimeout as sleep } from "timers/promises";
@@ -113,33 +117,49 @@ var SyncAdapter = class {
113
117
  defaultTimeout: configResolver.getWorkerTimeout()
114
118
  });
115
119
  const jobFactory = QueueManager.getJobFactory();
120
+ const executionWrapper = QueueManager.getExecutionWrapper();
116
121
  let attempts = jobData.attempts;
117
122
  while (true) {
123
+ const now = Date.now();
124
+ const acquiredJob = { ...jobData, attempts, acquiredAt: now };
118
125
  const context = {
119
126
  jobId: jobData.id,
120
127
  name: jobData.name,
121
128
  attempt: attempts + 1,
122
129
  queue,
123
130
  priority: jobData.priority ?? DEFAULT_PRIORITY,
124
- acquiredAt: /* @__PURE__ */ new Date(),
131
+ acquiredAt: new Date(now),
125
132
  stalledCount: jobData.stalledCount ?? 0
126
133
  };
127
134
  const jobInstance = jobFactory ? await jobFactory(JobClass) : new JobClass();
128
- try {
129
- await runtime.execute(jobInstance, jobData.payload, context);
130
- return;
131
- } catch (error) {
132
- const outcome = runtime.resolveFailure(error, attempts);
133
- if (outcome.type === "failed") {
134
- await jobInstance.failed?.(outcome.hookError);
135
- return;
136
- }
137
- attempts++;
138
- if (outcome.type === "retry" && outcome.retryAt) {
139
- const delay = outcome.retryAt.getTime() - Date.now();
140
- if (delay > 0) {
141
- await sleep(delay);
135
+ const startTime = performance.now();
136
+ const executeMessage = { job: acquiredJob, queue };
137
+ const run = () => {
138
+ return executeChannel.tracePromise(async () => {
139
+ try {
140
+ await runtime.execute(jobInstance, jobData.payload, context);
141
+ executeMessage.status = "completed";
142
+ } catch (error) {
143
+ const outcome = runtime.resolveFailure(error, attempts);
144
+ executeMessage.error = error;
145
+ if (outcome.type === "failed") {
146
+ executeMessage.status = "failed";
147
+ await jobInstance.failed?.(outcome.hookError);
148
+ } else if (outcome.type === "retry") {
149
+ executeMessage.status = "retrying";
150
+ executeMessage.nextRetryAt = outcome.retryAt;
151
+ }
142
152
  }
153
+ executeMessage.duration = Number((performance.now() - startTime).toFixed(2));
154
+ }, executeMessage);
155
+ };
156
+ await executionWrapper(run, acquiredJob, queue);
157
+ if (executeMessage.status !== "retrying") return;
158
+ attempts++;
159
+ if (executeMessage.nextRetryAt) {
160
+ const delay = executeMessage.nextRetryAt.getTime() - Date.now();
161
+ if (delay > 0) {
162
+ await sleep(delay);
143
163
  }
144
164
  }
145
165
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/drivers/sync_adapter.ts"],"sourcesContent":["import { setTimeout as sleep } from 'node:timers/promises'\nimport { Locator } from '../locator.js'\nimport { QueueManager } from '../queue_manager.js'\nimport { JobExecutionRuntime } from '../job_runtime.js'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobContext,\n JobData,\n JobRetention,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\n\n/**\n * Create a sync adapter factory.\n */\nexport function sync() {\n return () => new SyncAdapter()\n}\n\n/**\n * Sync adapter executes jobs immediately when pushed.\n * Pop/complete/fail/retry are not supported as jobs are executed synchronously.\n */\nexport class SyncAdapter implements Adapter {\n setWorkerId(_workerId: string): void {}\n\n push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n pushOn(queue: string, jobData: JobData): Promise<void> {\n return this.#execute(jobData, queue)\n }\n\n pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n setTimeout(() => {\n void this.#execute(jobData, queue).catch((error) => {\n QueueManager.getLogger().error(\n { err: error, jobId: jobData.id, jobName: jobData.name, queue },\n 'Failed to execute delayed sync job'\n )\n })\n }, delay)\n\n return Promise.resolve()\n }\n\n pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n for (const job of jobs) {\n await this.pushOn(queue, job)\n }\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(_queue: string): Promise<number> {\n return Promise.resolve(0)\n }\n\n pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n popFrom(_queue: string): Promise<AcquiredJob | null> {\n throw new Error('SyncAdapter does not support pop - jobs are executed immediately on push')\n }\n\n completeJob(_jobId: string, _queue: string, _removeOnComplete?: JobRetention): Promise<void> {\n return Promise.resolve()\n }\n\n failJob(\n _jobId: string,\n _queue: string,\n _error?: Error,\n _removeOnFail?: JobRetention\n ): Promise<void> {\n return Promise.resolve()\n }\n\n retryJob(_jobId: string, _queue: string, _retryAt?: Date): Promise<void> {\n return Promise.resolve()\n }\n\n recoverStalledJobs(\n _queue: string,\n _stalledThreshold: number,\n _maxStalledCount: number\n ): Promise<number> {\n // SyncAdapter has no stalled jobs - jobs are executed immediately\n return Promise.resolve(0)\n }\n\n getJob(_jobId: string, _queue: string): Promise<null> {\n return Promise.resolve(null)\n }\n\n destroy(): Promise<void> {\n return Promise.resolve()\n }\n\n upsertSchedule(_config: ScheduleConfig): Promise<string> {\n // No-op: schedules don't make sense for sync adapter\n // Return a fake ID so code doesn't break in dev\n return Promise.resolve(`sync-schedule-${Date.now()}`)\n }\n\n /**\n * @deprecated Use `upsertSchedule` instead.\n */\n createSchedule(config: ScheduleConfig): Promise<string> {\n return this.upsertSchedule(config)\n }\n\n getSchedule(_id: string): Promise<ScheduleData | null> {\n return Promise.resolve(null)\n }\n\n listSchedules(_options?: ScheduleListOptions): Promise<ScheduleData[]> {\n return Promise.resolve([])\n }\n\n updateSchedule(\n _id: string,\n _updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n return Promise.resolve()\n }\n\n deleteSchedule(_id: string): Promise<void> {\n return Promise.resolve()\n }\n\n claimDueSchedule(): Promise<ScheduleData | null> {\n // SyncAdapter doesn't support scheduling\n return Promise.resolve(null)\n }\n\n async #execute(jobData: JobData, queue: string = 'default'): Promise<void> {\n const JobClass = Locator.get(jobData.name)\n\n if (!JobClass) {\n throw new Error(`Job class ${jobData.name} not found.`)\n }\n\n const options = JobClass.options || {}\n const configResolver = QueueManager.getConfigResolver()\n const runtime = JobExecutionRuntime.from({\n jobName: jobData.name,\n options,\n retryConfig: configResolver.resolveRetryConfig(queue, options),\n defaultTimeout: configResolver.getWorkerTimeout(),\n })\n const jobFactory = QueueManager.getJobFactory()\n let attempts = jobData.attempts\n\n while (true) {\n const context: JobContext = {\n jobId: jobData.id,\n name: jobData.name,\n attempt: attempts + 1,\n queue,\n priority: jobData.priority ?? DEFAULT_PRIORITY,\n acquiredAt: new Date(),\n stalledCount: jobData.stalledCount ?? 0,\n }\n\n const jobInstance = jobFactory ? await jobFactory(JobClass) : new JobClass()\n\n try {\n await runtime.execute(jobInstance, jobData.payload, context)\n return\n } catch (error) {\n const outcome = runtime.resolveFailure(error as Error, attempts)\n\n if (outcome.type === 'failed') {\n await jobInstance.failed?.(outcome.hookError)\n return\n }\n\n attempts++\n\n if (outcome.type === 'retry' && outcome.retryAt) {\n const delay = outcome.retryAt.getTime() - Date.now()\n\n if (delay > 0) {\n await sleep(delay)\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/drivers/sync_adapter.ts"],"sourcesContent":["import { setTimeout as sleep } from 'node:timers/promises'\nimport { Locator } from '../locator.js'\nimport { QueueManager } from '../queue_manager.js'\nimport { JobExecutionRuntime } from '../job_runtime.js'\nimport { executeChannel } from '../tracing_channels.js'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobContext,\n JobData,\n JobRetention,\n ScheduleConfig,\n ScheduleData,\n ScheduleListOptions,\n} from '../types/main.js'\nimport type { JobExecuteMessage } from '../types/tracing_channels.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\n\n/**\n * Create a sync adapter factory.\n */\nexport function sync() {\n return () => new SyncAdapter()\n}\n\n/**\n * Sync adapter executes jobs immediately when pushed.\n * Pop/complete/fail/retry are not supported as jobs are executed synchronously.\n */\nexport class SyncAdapter implements Adapter {\n setWorkerId(_workerId: string): void {}\n\n push(jobData: JobData): Promise<void> {\n return this.pushOn('default', jobData)\n }\n\n pushOn(queue: string, jobData: JobData): Promise<void> {\n return this.#execute(jobData, queue)\n }\n\n pushLater(jobData: JobData, delay: number): Promise<void> {\n return this.pushLaterOn('default', jobData, delay)\n }\n\n pushLaterOn(queue: string, jobData: JobData, delay: number): Promise<void> {\n setTimeout(() => {\n void this.#execute(jobData, queue).catch((error) => {\n QueueManager.getLogger().error(\n { err: error, jobId: jobData.id, jobName: jobData.name, queue },\n 'Failed to execute delayed sync job'\n )\n })\n }, delay)\n\n return Promise.resolve()\n }\n\n pushMany(jobs: JobData[]): Promise<void> {\n return this.pushManyOn('default', jobs)\n }\n\n async pushManyOn(queue: string, jobs: JobData[]): Promise<void> {\n for (const job of jobs) {\n await this.pushOn(queue, job)\n }\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(_queue: string): Promise<number> {\n return Promise.resolve(0)\n }\n\n pop(): Promise<AcquiredJob | null> {\n return this.popFrom('default')\n }\n\n popFrom(_queue: string): Promise<AcquiredJob | null> {\n throw new Error('SyncAdapter does not support pop - jobs are executed immediately on push')\n }\n\n completeJob(_jobId: string, _queue: string, _removeOnComplete?: JobRetention): Promise<void> {\n return Promise.resolve()\n }\n\n failJob(\n _jobId: string,\n _queue: string,\n _error?: Error,\n _removeOnFail?: JobRetention\n ): Promise<void> {\n return Promise.resolve()\n }\n\n retryJob(_jobId: string, _queue: string, _retryAt?: Date): Promise<void> {\n return Promise.resolve()\n }\n\n recoverStalledJobs(\n _queue: string,\n _stalledThreshold: number,\n _maxStalledCount: number\n ): Promise<number> {\n // SyncAdapter has no stalled jobs - jobs are executed immediately\n return Promise.resolve(0)\n }\n\n getJob(_jobId: string, _queue: string): Promise<null> {\n return Promise.resolve(null)\n }\n\n destroy(): Promise<void> {\n return Promise.resolve()\n }\n\n upsertSchedule(_config: ScheduleConfig): Promise<string> {\n // No-op: schedules don't make sense for sync adapter\n // Return a fake ID so code doesn't break in dev\n return Promise.resolve(`sync-schedule-${Date.now()}`)\n }\n\n /**\n * @deprecated Use `upsertSchedule` instead.\n */\n createSchedule(config: ScheduleConfig): Promise<string> {\n return this.upsertSchedule(config)\n }\n\n getSchedule(_id: string): Promise<ScheduleData | null> {\n return Promise.resolve(null)\n }\n\n listSchedules(_options?: ScheduleListOptions): Promise<ScheduleData[]> {\n return Promise.resolve([])\n }\n\n updateSchedule(\n _id: string,\n _updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>\n ): Promise<void> {\n return Promise.resolve()\n }\n\n deleteSchedule(_id: string): Promise<void> {\n return Promise.resolve()\n }\n\n claimDueSchedule(): Promise<ScheduleData | null> {\n // SyncAdapter doesn't support scheduling\n return Promise.resolve(null)\n }\n\n async #execute(jobData: JobData, queue: string = 'default'): Promise<void> {\n const JobClass = Locator.get(jobData.name)\n\n if (!JobClass) {\n throw new Error(`Job class ${jobData.name} not found.`)\n }\n\n const options = JobClass.options || {}\n const configResolver = QueueManager.getConfigResolver()\n const runtime = JobExecutionRuntime.from({\n jobName: jobData.name,\n options,\n retryConfig: configResolver.resolveRetryConfig(queue, options),\n defaultTimeout: configResolver.getWorkerTimeout(),\n })\n const jobFactory = QueueManager.getJobFactory()\n const executionWrapper = QueueManager.getExecutionWrapper()\n let attempts = jobData.attempts\n\n while (true) {\n const now = Date.now()\n const acquiredJob: AcquiredJob = { ...jobData, attempts, acquiredAt: now }\n\n const context: JobContext = {\n jobId: jobData.id,\n name: jobData.name,\n attempt: attempts + 1,\n queue,\n priority: jobData.priority ?? DEFAULT_PRIORITY,\n acquiredAt: new Date(now),\n stalledCount: jobData.stalledCount ?? 0,\n }\n\n const jobInstance = jobFactory ? await jobFactory(JobClass) : new JobClass()\n\n const startTime = performance.now()\n const executeMessage: JobExecuteMessage = { job: acquiredJob, queue }\n\n const run = () => {\n return executeChannel.tracePromise(async () => {\n try {\n await runtime.execute(jobInstance, jobData.payload, context)\n executeMessage.status = 'completed'\n } catch (error) {\n const outcome = runtime.resolveFailure(error as Error, attempts)\n executeMessage.error = error as Error\n\n if (outcome.type === 'failed') {\n executeMessage.status = 'failed'\n await jobInstance.failed?.(outcome.hookError)\n } else if (outcome.type === 'retry') {\n executeMessage.status = 'retrying'\n executeMessage.nextRetryAt = outcome.retryAt\n }\n }\n\n executeMessage.duration = Number((performance.now() - startTime).toFixed(2))\n }, executeMessage)\n }\n\n await executionWrapper(run, acquiredJob, queue)\n\n if (executeMessage.status !== 'retrying') return\n\n attempts++\n\n if (executeMessage.nextRetryAt) {\n const delay = executeMessage.nextRetryAt.getTime() - Date.now()\n if (delay > 0) {\n await sleep(delay)\n }\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA,SAAS,cAAc,aAAa;AAoB7B,SAAS,OAAO;AACrB,SAAO,MAAM,IAAI,YAAY;AAC/B;AAMO,IAAM,cAAN,MAAqC;AAAA,EAC1C,YAAY,WAAyB;AAAA,EAAC;AAAA,EAEtC,KAAK,SAAiC;AACpC,WAAO,KAAK,OAAO,WAAW,OAAO;AAAA,EACvC;AAAA,EAEA,OAAO,OAAe,SAAiC;AACrD,WAAO,KAAK,SAAS,SAAS,KAAK;AAAA,EACrC;AAAA,EAEA,UAAU,SAAkB,OAA8B;AACxD,WAAO,KAAK,YAAY,WAAW,SAAS,KAAK;AAAA,EACnD;AAAA,EAEA,YAAY,OAAe,SAAkB,OAA8B;AACzE,eAAW,MAAM;AACf,WAAK,KAAK,SAAS,SAAS,KAAK,EAAE,MAAM,CAAC,UAAU;AAClD,qBAAa,UAAU,EAAE;AAAA,UACvB,EAAE,KAAK,OAAO,OAAO,QAAQ,IAAI,SAAS,QAAQ,MAAM,MAAM;AAAA,UAC9D;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,GAAG,KAAK;AAER,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,SAAS,MAAgC;AACvC,WAAO,KAAK,WAAW,WAAW,IAAI;AAAA,EACxC;AAAA,EAEA,MAAM,WAAW,OAAe,MAAgC;AAC9D,eAAW,OAAO,MAAM;AACtB,YAAM,KAAK,OAAO,OAAO,GAAG;AAAA,IAC9B;AAAA,EACF;AAAA,EAEA,OAAwB;AACtB,WAAO,KAAK,OAAO,SAAS;AAAA,EAC9B;AAAA,EAEA,OAAO,QAAiC;AACtC,WAAO,QAAQ,QAAQ,CAAC;AAAA,EAC1B;AAAA,EAEA,MAAmC;AACjC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC/B;AAAA,EAEA,QAAQ,QAA6C;AACnD,UAAM,IAAI,MAAM,0EAA0E;AAAA,EAC5F;AAAA,EAEA,YAAY,QAAgB,QAAgB,mBAAiD;AAC3F,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,QACE,QACA,QACA,QACA,eACe;AACf,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,SAAS,QAAgB,QAAgB,UAAgC;AACvE,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,mBACE,QACA,mBACA,kBACiB;AAEjB,WAAO,QAAQ,QAAQ,CAAC;AAAA,EAC1B;AAAA,EAEA,OAAO,QAAgB,QAA+B;AACpD,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAC7B;AAAA,EAEA,UAAyB;AACvB,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,eAAe,SAA0C;AAGvD,WAAO,QAAQ,QAAQ,iBAAiB,KAAK,IAAI,CAAC,EAAE;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAyC;AACtD,WAAO,KAAK,eAAe,MAAM;AAAA,EACnC;AAAA,EAEA,YAAY,KAA2C;AACrD,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAC7B;AAAA,EAEA,cAAc,UAAyD;AACrE,WAAO,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC3B;AAAA,EAEA,eACE,KACA,UACe;AACf,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,eAAe,KAA4B;AACzC,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,mBAAiD;AAE/C,WAAO,QAAQ,QAAQ,IAAI;AAAA,EAC7B;AAAA,EAEA,MAAM,SAAS,SAAkB,QAAgB,WAA0B;AACzE,UAAM,WAAW,QAAQ,IAAI,QAAQ,IAAI;AAEzC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,aAAa,QAAQ,IAAI,aAAa;AAAA,IACxD;AAEA,UAAM,UAAU,SAAS,WAAW,CAAC;AACrC,UAAM,iBAAiB,aAAa,kBAAkB;AACtD,UAAM,UAAU,oBAAoB,KAAK;AAAA,MACvC,SAAS,QAAQ;AAAA,MACjB;AAAA,MACA,aAAa,eAAe,mBAAmB,OAAO,OAAO;AAAA,MAC7D,gBAAgB,eAAe,iBAAiB;AAAA,IAClD,CAAC;AACD,UAAM,aAAa,aAAa,cAAc;AAC9C,UAAM,mBAAmB,aAAa,oBAAoB;AAC1D,QAAI,WAAW,QAAQ;AAEvB,WAAO,MAAM;AACX,YAAM,MAAM,KAAK,IAAI;AACrB,YAAM,cAA2B,EAAE,GAAG,SAAS,UAAU,YAAY,IAAI;AAEzE,YAAM,UAAsB;AAAA,QAC1B,OAAO,QAAQ;AAAA,QACf,MAAM,QAAQ;AAAA,QACd,SAAS,WAAW;AAAA,QACpB;AAAA,QACA,UAAU,QAAQ,YAAY;AAAA,QAC9B,YAAY,IAAI,KAAK,GAAG;AAAA,QACxB,cAAc,QAAQ,gBAAgB;AAAA,MACxC;AAEA,YAAM,cAAc,aAAa,MAAM,WAAW,QAAQ,IAAI,IAAI,SAAS;AAE3E,YAAM,YAAY,YAAY,IAAI;AAClC,YAAM,iBAAoC,EAAE,KAAK,aAAa,MAAM;AAEpE,YAAM,MAAM,MAAM;AAChB,eAAO,eAAe,aAAa,YAAY;AAC7C,cAAI;AACF,kBAAM,QAAQ,QAAQ,aAAa,QAAQ,SAAS,OAAO;AAC3D,2BAAe,SAAS;AAAA,UAC1B,SAAS,OAAO;AACd,kBAAM,UAAU,QAAQ,eAAe,OAAgB,QAAQ;AAC/D,2BAAe,QAAQ;AAEvB,gBAAI,QAAQ,SAAS,UAAU;AAC7B,6BAAe,SAAS;AACxB,oBAAM,YAAY,SAAS,QAAQ,SAAS;AAAA,YAC9C,WAAW,QAAQ,SAAS,SAAS;AACnC,6BAAe,SAAS;AACxB,6BAAe,cAAc,QAAQ;AAAA,YACvC;AAAA,UACF;AAEA,yBAAe,WAAW,QAAQ,YAAY,IAAI,IAAI,WAAW,QAAQ,CAAC,CAAC;AAAA,QAC7E,GAAG,cAAc;AAAA,MACnB;AAEA,YAAM,iBAAiB,KAAK,aAAa,KAAK;AAE9C,UAAI,eAAe,WAAW,WAAY;AAE1C;AAEA,UAAI,eAAe,aAAa;AAC9B,cAAM,QAAQ,eAAe,YAAY,QAAQ,IAAI,KAAK,IAAI;AAC9D,YAAI,QAAQ,GAAG;AACb,gBAAM,MAAM,KAAK;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,63 @@
1
+ import '../job-DImdhRFO.js';
2
+ import { JobDispatchMessage, JobExecuteMessage } from './types/tracing_channels.js';
3
+ import { Span } from '@opentelemetry/api';
4
+ import { TracingChannelSubscribers } from 'node:diagnostics_channel';
5
+ import { InstrumentationConfig, InstrumentationBase } from '@opentelemetry/instrumentation';
6
+
7
+ interface QueueInstrumentationConfig extends InstrumentationConfig {
8
+ /**
9
+ * How execution spans relate to the dispatch span.
10
+ *
11
+ * - `'link'` (default): Independent trace, linked to dispatch span
12
+ * - `'parent'`: Child of the dispatch span (same trace)
13
+ */
14
+ executionSpanLinkMode?: 'link' | 'parent';
15
+ /**
16
+ * The messaging system identifier.
17
+ *
18
+ * @default 'boringqueue'
19
+ */
20
+ messagingSystem?: string;
21
+ }
22
+ /**
23
+ * OpenTelemetry instrumentation for @boringnode/queue.
24
+ *
25
+ * Creates PRODUCER spans for job dispatch and CONSUMER spans for
26
+ * job execution, following OTel messaging semantic conventions.
27
+ *
28
+ * Uses `diagnostics_channel` for span lifecycle management and
29
+ * patches `QueueManager.init()` to inject wrappers automatically.
30
+ */
31
+ declare class QueueInstrumentation extends InstrumentationBase<QueueInstrumentationConfig> {
32
+ #private;
33
+ protected subscribed: boolean;
34
+ protected executeSpans: Map<string, Span>;
35
+ protected dispatchSpans: WeakMap<JobDispatchMessage, Span>;
36
+ protected executeHandlers?: TracingChannelSubscribers<JobExecuteMessage>;
37
+ protected dispatchHandlers?: TracingChannelSubscribers<JobDispatchMessage>;
38
+ constructor(config?: QueueInstrumentationConfig);
39
+ /**
40
+ * Required by InstrumentationBase. Returns undefined since we use
41
+ * diagnostics_channel instead of module patching.
42
+ */
43
+ protected init(): undefined;
44
+ /**
45
+ * Subscribes to diagnostics_channels for span lifecycle.
46
+ */
47
+ enable(): void;
48
+ /**
49
+ * Unsubscribes from diagnostics_channels and restores patched methods.
50
+ */
51
+ disable(): void;
52
+ /**
53
+ * Patches `QueueManager.init()` to auto-inject OTel wrappers
54
+ * and subscribes to diagnostics_channels.
55
+ */
56
+ manuallyRegister(queueModule: {
57
+ QueueManager: {
58
+ init: (...args: any[]) => any;
59
+ };
60
+ }): void;
61
+ }
62
+
63
+ export { QueueInstrumentation, type QueueInstrumentationConfig };