@boringnode/queue 0.0.1-alpha.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,16 @@
1
1
  import {
2
2
  calculateScore
3
- } from "../../chunk-5PDDRF5O.js";
3
+ } from "../../chunk-NPQKBCCY.js";
4
4
  import {
5
5
  DEFAULT_PRIORITY
6
- } from "../../chunk-HMGNQSSG.js";
6
+ } from "../../chunk-SMOKFZ46.js";
7
7
 
8
8
  // src/drivers/redis_adapter.ts
9
+ import { randomUUID } from "crypto";
9
10
  import { Redis } from "ioredis";
10
11
  var redisKey = "jobs";
12
+ var schedulesKey = "schedules";
13
+ var schedulesIndexKey = "schedules::index";
11
14
  var ACQUIRE_JOB_SCRIPT = `
12
15
  local pending_key = KEYS[1]
13
16
  local active_key = KEYS[2]
@@ -150,6 +153,75 @@ var RECOVER_STALLED_JOBS_SCRIPT = `
150
153
 
151
154
  return recovered
152
155
  `;
156
+ var CLAIM_SCHEDULE_SCRIPT = `
157
+ local schedule_key = KEYS[1]
158
+ local now = tonumber(ARGV[1])
159
+
160
+ -- Get schedule data
161
+ local data = redis.call('HGETALL', schedule_key)
162
+ if #data == 0 then
163
+ return nil
164
+ end
165
+
166
+ -- Convert HGETALL result to table
167
+ local schedule = {}
168
+ for j = 1, #data, 2 do
169
+ schedule[data[j]] = data[j + 1]
170
+ end
171
+
172
+ -- Check if schedule is due
173
+ if schedule.status ~= 'active' then
174
+ return nil
175
+ end
176
+
177
+ local next_run_at = tonumber(schedule.next_run_at)
178
+ if not next_run_at or next_run_at > now then
179
+ return nil
180
+ end
181
+
182
+ local run_count = tonumber(schedule.run_count or '0')
183
+ local run_limit = schedule.run_limit and tonumber(schedule.run_limit) or nil
184
+ local to_date = schedule.to_date and tonumber(schedule.to_date) or nil
185
+
186
+ -- Check limits
187
+ if run_limit and run_count >= run_limit then
188
+ return nil
189
+ end
190
+
191
+ if to_date and now > to_date then
192
+ return nil
193
+ end
194
+
195
+ -- This schedule is claimable - atomically update it
196
+ local new_run_count = run_count + 1
197
+
198
+ -- Calculate new next_run_at (simple interval-based for now)
199
+ -- Complex cron calculation happens in the caller
200
+ local new_next_run_at = ''
201
+ local every_ms = schedule.every_ms and tonumber(schedule.every_ms) or nil
202
+ if every_ms then
203
+ new_next_run_at = tostring(now + every_ms)
204
+ end
205
+
206
+ -- Check if we've hit the limit after this run
207
+ if run_limit and new_run_count >= run_limit then
208
+ new_next_run_at = ''
209
+ end
210
+
211
+ -- Check if past end date
212
+ if to_date and new_next_run_at ~= '' and tonumber(new_next_run_at) > to_date then
213
+ new_next_run_at = ''
214
+ end
215
+
216
+ -- Update the schedule atomically
217
+ redis.call('HSET', schedule_key,
218
+ 'next_run_at', new_next_run_at,
219
+ 'last_run_at', tostring(now),
220
+ 'run_count', tostring(new_run_count))
221
+
222
+ -- Return the schedule data (before update) as JSON
223
+ return cjson.encode(schedule)
224
+ `;
153
225
  function redis(config) {
154
226
  return () => {
155
227
  if (config instanceof Redis) {
@@ -266,6 +338,125 @@ var RedisAdapter = class {
266
338
  );
267
339
  return recovered;
268
340
  }
341
+ async createSchedule(config) {
342
+ const id = config.id ?? randomUUID();
343
+ const now = Date.now();
344
+ const scheduleData = {
345
+ id,
346
+ name: config.name,
347
+ payload: JSON.stringify(config.payload),
348
+ timezone: config.timezone,
349
+ status: "active",
350
+ run_count: "0",
351
+ created_at: now.toString()
352
+ };
353
+ if (config.cronExpression) scheduleData.cron_expression = config.cronExpression;
354
+ if (config.everyMs) scheduleData.every_ms = config.everyMs.toString();
355
+ if (config.from) scheduleData.from_date = config.from.getTime().toString();
356
+ if (config.to) scheduleData.to_date = config.to.getTime().toString();
357
+ if (config.limit) scheduleData.run_limit = config.limit.toString();
358
+ const scheduleKey = `${schedulesKey}::${id}`;
359
+ await this.#connection.hset(scheduleKey, scheduleData);
360
+ await this.#connection.sadd(schedulesIndexKey, id);
361
+ return id;
362
+ }
363
+ async getSchedule(id) {
364
+ const scheduleKey = `${schedulesKey}::${id}`;
365
+ const data = await this.#connection.hgetall(scheduleKey);
366
+ if (!data || Object.keys(data).length === 0) {
367
+ return null;
368
+ }
369
+ return this.#hashToScheduleData(data);
370
+ }
371
+ async listSchedules(options) {
372
+ const ids = await this.#connection.smembers(schedulesIndexKey);
373
+ const schedules = [];
374
+ for (const id of ids) {
375
+ const schedule = await this.getSchedule(id);
376
+ if (schedule) {
377
+ if (options?.status && schedule.status !== options.status) {
378
+ continue;
379
+ }
380
+ schedules.push(schedule);
381
+ }
382
+ }
383
+ return schedules;
384
+ }
385
+ async updateSchedule(id, updates) {
386
+ const scheduleKey = `${schedulesKey}::${id}`;
387
+ const data = {};
388
+ if (updates.status !== void 0) data.status = updates.status;
389
+ if (updates.nextRunAt !== void 0) {
390
+ data.next_run_at = updates.nextRunAt ? updates.nextRunAt.getTime().toString() : "";
391
+ }
392
+ if (updates.lastRunAt !== void 0) {
393
+ data.last_run_at = updates.lastRunAt ? updates.lastRunAt.getTime().toString() : "";
394
+ }
395
+ if (updates.runCount !== void 0) data.run_count = updates.runCount.toString();
396
+ if (Object.keys(data).length > 0) {
397
+ await this.#connection.hset(scheduleKey, data);
398
+ }
399
+ }
400
+ async deleteSchedule(id) {
401
+ const scheduleKey = `${schedulesKey}::${id}`;
402
+ await this.#connection.del(scheduleKey);
403
+ await this.#connection.srem(schedulesIndexKey, id);
404
+ }
405
+ async claimDueSchedule() {
406
+ const now = Date.now();
407
+ const ids = await this.#connection.smembers(schedulesIndexKey);
408
+ for (const id of ids) {
409
+ const scheduleKey = `${schedulesKey}::${id}`;
410
+ const result = await this.#connection.eval(
411
+ CLAIM_SCHEDULE_SCRIPT,
412
+ 1,
413
+ scheduleKey,
414
+ now.toString()
415
+ );
416
+ if (!result) {
417
+ continue;
418
+ }
419
+ const data = JSON.parse(result);
420
+ if (data.cron_expression) {
421
+ const { CronExpressionParser } = await import("cron-parser");
422
+ const cron = CronExpressionParser.parse(data.cron_expression, {
423
+ currentDate: new Date(now),
424
+ tz: data.timezone || "UTC"
425
+ });
426
+ const nextRun = cron.next().toDate().getTime();
427
+ const runCount = Number.parseInt(data.run_count || "0", 10) + 1;
428
+ const runLimit = data.run_limit ? Number.parseInt(data.run_limit, 10) : null;
429
+ const toDate = data.to_date ? Number.parseInt(data.to_date, 10) : null;
430
+ let newNextRunAt = nextRun;
431
+ if (runLimit !== null && runCount >= runLimit) {
432
+ newNextRunAt = "";
433
+ } else if (toDate && nextRun > toDate) {
434
+ newNextRunAt = "";
435
+ }
436
+ await this.#connection.hset(scheduleKey, "next_run_at", newNextRunAt.toString());
437
+ }
438
+ return this.#hashToScheduleData(data);
439
+ }
440
+ return null;
441
+ }
442
+ #hashToScheduleData(data) {
443
+ return {
444
+ id: data.id,
445
+ name: data.name,
446
+ payload: JSON.parse(data.payload || "{}"),
447
+ cronExpression: data.cron_expression || null,
448
+ everyMs: data.every_ms ? Number.parseInt(data.every_ms, 10) : null,
449
+ timezone: data.timezone || "UTC",
450
+ from: data.from_date ? new Date(Number.parseInt(data.from_date, 10)) : null,
451
+ to: data.to_date ? new Date(Number.parseInt(data.to_date, 10)) : null,
452
+ limit: data.run_limit ? Number.parseInt(data.run_limit, 10) : null,
453
+ runCount: Number.parseInt(data.run_count || "0", 10),
454
+ nextRunAt: data.next_run_at ? new Date(Number.parseInt(data.next_run_at, 10)) : null,
455
+ lastRunAt: data.last_run_at ? new Date(Number.parseInt(data.last_run_at, 10)) : null,
456
+ status: data.status || "active",
457
+ createdAt: data.created_at ? new Date(Number.parseInt(data.created_at, 10)) : /* @__PURE__ */ new Date()
458
+ };
459
+ }
269
460
  };
270
461
  export {
271
462
  RedisAdapter,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/drivers/redis_adapter.ts"],"sourcesContent":["import { Redis, type RedisOptions } from 'ioredis'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type { JobData } from '../types/main.js'\nimport { DEFAULT_PRIORITY } from '../constants.js'\nimport { calculateScore } from '../utils.js'\n\nconst redisKey = 'jobs'\ntype RedisConfig = Redis | RedisOptions\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 pending_key = KEYS[1]\n local active_key = KEYS[2]\n local delayed_key = KEYS[3]\n local worker_id = ARGV[1]\n local now = ARGV[2]\n\n -- First, process delayed jobs\n local ready_jobs = redis.call('ZRANGEBYSCORE', delayed_key, 0, now)\n if #ready_jobs > 0 then\n for i = 1, #ready_jobs do\n local job_data = ready_jobs[i]\n local job = cjson.decode(job_data)\n -- Score = priority * 1e13 + timestamp\n -- Lower score = higher priority, FIFO within same priority\n local priority = job.priority or 5\n local timestamp = tonumber(now)\n local score = priority * 10000000000000 + timestamp\n redis.call('ZADD', pending_key, score, job_data)\n redis.call('ZREM', delayed_key, job_data)\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_data = result[1]\n local job = cjson.decode(job_data)\n\n -- Store in active hash: jobId -> {workerId, acquiredAt, data}\n local active_data = cjson.encode({\n workerId = worker_id,\n acquiredAt = tonumber(now),\n data = job\n })\n redis.call('HSET', active_key, job.id, active_data)\n\n -- Return job with acquiredAt\n job.acquiredAt = tonumber(now)\n return cjson.encode(job)\n`\n\n/**\n * Lua script for completing a job.\n * Removes the job from active hash.\n */\nconst COMPLETE_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for failing a job permanently.\n * Removes from active hash.\n */\nconst FAIL_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for retrying a job.\n * 1. Get job from active hash\n * 2. Remove from active hash\n * 3. Increment attempts\n * 4. Add back to pending (or delayed if retryAt is set)\n */\nconst RETRY_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local pending_key = KEYS[2]\n local delayed_key = KEYS[3]\n local job_id = ARGV[1]\n local retry_at = tonumber(ARGV[2])\n local now = tonumber(ARGV[3])\n\n -- Get job from active hash\n local active_data = redis.call('HGET', active_key, job_id)\n if not active_data then\n return 0\n end\n\n local active = cjson.decode(active_data)\n local job = active.data\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Increment attempts\n job.attempts = (job.attempts or 0) + 1\n\n local job_data = 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_data)\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_data)\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 active_key = KEYS[1]\n local pending_key = KEYS[2]\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 = active.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, just remove (already done above)\n else\n -- Recover: increment stalledCount and put back in pending\n job.stalledCount = current_stalled_count + 1\n local job_data = cjson.encode(job)\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_data)\n recovered = recovered + 1\n end\n end\n end\n\n return recovered\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 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 now = Date.now()\n const pendingKey = `${redisKey}::${queue}`\n const activeKey = `${redisKey}::${queue}::active`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n const result = await this.#connection.eval(\n ACQUIRE_JOB_SCRIPT,\n 3,\n pendingKey,\n activeKey,\n delayedKey,\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): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(COMPLETE_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(FAIL_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n const activeKey = `${redisKey}::${queue}::active`\n const pendingKey = `${redisKey}::${queue}`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.eval(\n RETRY_JOB_SCRIPT,\n 3,\n activeKey,\n pendingKey,\n delayedKey,\n jobId,\n retryAt ? retryAt.getTime().toString() : '0',\n now.toString()\n )\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 executeAt = Date.now() + delay\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.zadd(delayedKey, executeAt, JSON.stringify(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.zadd(`${redisKey}::${queue}`, score, JSON.stringify(jobData))\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(queue: string): Promise<number> {\n return this.#connection.zcard(`${redisKey}::${queue}`)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const now = Date.now()\n const activeKey = `${redisKey}::${queue}::active`\n const pendingKey = `${redisKey}::${queue}`\n\n const recovered = await this.#connection.eval(\n RECOVER_STALLED_JOBS_SCRIPT,\n 2,\n activeKey,\n pendingKey,\n now.toString(),\n stalledThreshold.toString(),\n maxStalledCount.toString()\n )\n\n return recovered as number\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 { JobData, ScheduleConfig, ScheduleData, ScheduleListOptions } from '../types/main.js'\n\nconst redisKey = 'jobs'\nconst schedulesKey = 'schedules'\nconst schedulesIndexKey = 'schedules::index'\ntype RedisConfig = Redis | RedisOptions\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 pending_key = KEYS[1]\n local active_key = KEYS[2]\n local delayed_key = KEYS[3]\n local worker_id = ARGV[1]\n local now = ARGV[2]\n\n -- First, process delayed jobs\n local ready_jobs = redis.call('ZRANGEBYSCORE', delayed_key, 0, now)\n if #ready_jobs > 0 then\n for i = 1, #ready_jobs do\n local job_data = ready_jobs[i]\n local job = cjson.decode(job_data)\n -- Score = priority * 1e13 + timestamp\n -- Lower score = higher priority, FIFO within same priority\n local priority = job.priority or 5\n local timestamp = tonumber(now)\n local score = priority * 10000000000000 + timestamp\n redis.call('ZADD', pending_key, score, job_data)\n redis.call('ZREM', delayed_key, job_data)\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_data = result[1]\n local job = cjson.decode(job_data)\n\n -- Store in active hash: jobId -> {workerId, acquiredAt, data}\n local active_data = cjson.encode({\n workerId = worker_id,\n acquiredAt = tonumber(now),\n data = job\n })\n redis.call('HSET', active_key, job.id, active_data)\n\n -- Return job with acquiredAt\n job.acquiredAt = tonumber(now)\n return cjson.encode(job)\n`\n\n/**\n * Lua script for completing a job.\n * Removes the job from active hash.\n */\nconst COMPLETE_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for failing a job permanently.\n * Removes from active hash.\n */\nconst FAIL_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local job_id = ARGV[1]\n\n redis.call('HDEL', active_key, job_id)\n return 1\n`\n\n/**\n * Lua script for retrying a job.\n * 1. Get job from active hash\n * 2. Remove from active hash\n * 3. Increment attempts\n * 4. Add back to pending (or delayed if retryAt is set)\n */\nconst RETRY_JOB_SCRIPT = `\n local active_key = KEYS[1]\n local pending_key = KEYS[2]\n local delayed_key = KEYS[3]\n local job_id = ARGV[1]\n local retry_at = tonumber(ARGV[2])\n local now = tonumber(ARGV[3])\n\n -- Get job from active hash\n local active_data = redis.call('HGET', active_key, job_id)\n if not active_data then\n return 0\n end\n\n local active = cjson.decode(active_data)\n local job = active.data\n\n -- Remove from active\n redis.call('HDEL', active_key, job_id)\n\n -- Increment attempts\n job.attempts = (job.attempts or 0) + 1\n\n local job_data = 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_data)\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_data)\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 active_key = KEYS[1]\n local pending_key = KEYS[2]\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 = active.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, just remove (already done above)\n else\n -- Recover: increment stalledCount and put back in pending\n job.stalledCount = current_stalled_count + 1\n local job_data = cjson.encode(job)\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_data)\n recovered = recovered + 1\n end\n end\n end\n\n return recovered\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 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 now = Date.now()\n const pendingKey = `${redisKey}::${queue}`\n const activeKey = `${redisKey}::${queue}::active`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n const result = await this.#connection.eval(\n ACQUIRE_JOB_SCRIPT,\n 3,\n pendingKey,\n activeKey,\n delayedKey,\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): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(COMPLETE_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async failJob(jobId: string, queue: string, _error?: Error): Promise<void> {\n const activeKey = `${redisKey}::${queue}::active`\n\n await this.#connection.eval(FAIL_JOB_SCRIPT, 1, activeKey, jobId)\n }\n\n async retryJob(jobId: string, queue: string, retryAt?: Date): Promise<void> {\n const now = Date.now()\n const activeKey = `${redisKey}::${queue}::active`\n const pendingKey = `${redisKey}::${queue}`\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.eval(\n RETRY_JOB_SCRIPT,\n 3,\n activeKey,\n pendingKey,\n delayedKey,\n jobId,\n retryAt ? retryAt.getTime().toString() : '0',\n now.toString()\n )\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 executeAt = Date.now() + delay\n const delayedKey = `${redisKey}::delayed::${queue}`\n\n await this.#connection.zadd(delayedKey, executeAt, JSON.stringify(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.zadd(`${redisKey}::${queue}`, score, JSON.stringify(jobData))\n }\n\n size(): Promise<number> {\n return this.sizeOf('default')\n }\n\n sizeOf(queue: string): Promise<number> {\n return this.#connection.zcard(`${redisKey}::${queue}`)\n }\n\n async recoverStalledJobs(\n queue: string,\n stalledThreshold: number,\n maxStalledCount: number\n ): Promise<number> {\n const now = Date.now()\n const activeKey = `${redisKey}::${queue}::active`\n const pendingKey = `${redisKey}::${queue}`\n\n const recovered = await this.#connection.eval(\n RECOVER_STALLED_JOBS_SCRIPT,\n 2,\n activeKey,\n pendingKey,\n now.toString(),\n stalledThreshold.toString(),\n maxStalledCount.toString()\n )\n\n return recovered as number\n }\n\n async createSchedule(config: ScheduleConfig): Promise<string> {\n const id = config.id ?? randomUUID()\n const now = Date.now()\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: '0',\n created_at: now.toString(),\n }\n\n if (config.cronExpression) scheduleData.cron_expression = config.cronExpression\n if (config.everyMs) scheduleData.every_ms = config.everyMs.toString()\n if (config.from) scheduleData.from_date = config.from.getTime().toString()\n if (config.to) scheduleData.to_date = config.to.getTime().toString()\n if (config.limit) scheduleData.run_limit = config.limit.toString()\n\n // Store schedule as hash\n const scheduleKey = `${schedulesKey}::${id}`\n await this.#connection.hset(scheduleKey, scheduleData)\n\n // Add to index set for listing\n await this.#connection.sadd(schedulesIndexKey, id)\n\n return id\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 const schedules: ScheduleData[] = []\n\n for (const id of ids) {\n const schedule = await this.getSchedule(id)\n if (schedule) {\n // Filter by status if provided\n if (options?.status && schedule.status !== options.status) {\n continue\n }\n schedules.push(schedule)\n }\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.del(scheduleKey)\n await this.#connection.srem(schedulesIndexKey, id)\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, a as AcquiredJob } from '../../index-C3_tlebh.js';
1
+ import { A as Adapter, J as JobData, a as AcquiredJob, S as ScheduleConfig, b as ScheduleData, c as ScheduleListOptions } from '../../index-C0Xg6F4E.js';
2
2
 
3
3
  /**
4
4
  * Create a sync adapter factory.
@@ -24,6 +24,12 @@ declare class SyncAdapter implements Adapter {
24
24
  retryJob(_jobId: string, _queue: string, _retryAt?: Date): Promise<void>;
25
25
  recoverStalledJobs(_queue: string, _stalledThreshold: number, _maxStalledCount: number): Promise<number>;
26
26
  destroy(): Promise<void>;
27
+ createSchedule(_config: ScheduleConfig): Promise<string>;
28
+ getSchedule(_id: string): Promise<ScheduleData | null>;
29
+ listSchedules(_options?: ScheduleListOptions): Promise<ScheduleData[]>;
30
+ updateSchedule(_id: string, _updates: Partial<Pick<ScheduleData, 'status' | 'nextRunAt' | 'lastRunAt' | 'runCount'>>): Promise<void>;
31
+ deleteSchedule(_id: string): Promise<void>;
32
+ claimDueSchedule(): Promise<ScheduleData | null>;
27
33
  }
28
34
 
29
35
  export { SyncAdapter, sync };
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  Locator,
3
3
  QueueManager
4
- } from "../../chunk-CD45GT6E.js";
4
+ } from "../../chunk-RIXMQXYJ.js";
5
5
  import {
6
6
  DEFAULT_PRIORITY
7
- } from "../../chunk-HMGNQSSG.js";
7
+ } from "../../chunk-SMOKFZ46.js";
8
8
 
9
9
  // src/drivers/sync_adapter.ts
10
10
  function sync() {
@@ -55,12 +55,30 @@ var SyncAdapter = class {
55
55
  destroy() {
56
56
  return Promise.resolve();
57
57
  }
58
+ createSchedule(_config) {
59
+ return Promise.resolve(`sync-schedule-${Date.now()}`);
60
+ }
61
+ getSchedule(_id) {
62
+ return Promise.resolve(null);
63
+ }
64
+ listSchedules(_options) {
65
+ return Promise.resolve([]);
66
+ }
67
+ updateSchedule(_id, _updates) {
68
+ return Promise.resolve();
69
+ }
70
+ deleteSchedule(_id) {
71
+ return Promise.resolve();
72
+ }
73
+ claimDueSchedule() {
74
+ return Promise.resolve(null);
75
+ }
58
76
  async #execute(jobName, payload, queue = "default") {
59
77
  const JobClass = Locator.get(jobName);
60
78
  if (!JobClass) {
61
79
  throw new Error(`Job class ${jobName} not found.`);
62
80
  }
63
- const context = Object.freeze({
81
+ const context = {
64
82
  jobId: `sync-${Date.now()}`,
65
83
  name: jobName,
66
84
  attempt: 1,
@@ -68,9 +86,10 @@ var SyncAdapter = class {
68
86
  priority: DEFAULT_PRIORITY,
69
87
  acquiredAt: /* @__PURE__ */ new Date(),
70
88
  stalledCount: 0
71
- });
89
+ };
72
90
  const jobFactory = QueueManager.getJobFactory();
73
- const jobInstance = jobFactory ? await jobFactory(JobClass, payload, context) : new JobClass(payload, context);
91
+ const jobInstance = jobFactory ? await jobFactory(JobClass) : new JobClass();
92
+ jobInstance.$hydrate(payload, context);
74
93
  await jobInstance.execute();
75
94
  }
76
95
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/drivers/sync_adapter.ts"],"sourcesContent":["import { Locator } from '../locator.js'\nimport { QueueManager } from '../queue_manager.js'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type { JobContext, JobData } 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.name, jobData.payload, 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.name, jobData.payload, queue)\n }, delay)\n\n return Promise.resolve()\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): Promise<void> {\n return Promise.resolve()\n }\n\n failJob(_jobId: string, _queue: string, _error?: Error): 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 destroy(): Promise<void> {\n return Promise.resolve()\n }\n\n async #execute(jobName: string, payload: any, queue: string = 'default'): Promise<any> {\n const JobClass = Locator.get(jobName)\n\n if (!JobClass) {\n throw new Error(`Job class ${jobName} not found.`)\n }\n\n const context: JobContext = Object.freeze({\n jobId: `sync-${Date.now()}`,\n name: jobName,\n attempt: 1,\n queue,\n priority: DEFAULT_PRIORITY,\n acquiredAt: new Date(),\n stalledCount: 0,\n })\n\n const jobFactory = QueueManager.getJobFactory()\n const jobInstance = jobFactory\n ? await jobFactory(JobClass, payload, context)\n : new JobClass(payload, context)\n\n await jobInstance.execute()\n }\n}\n"],"mappings":";;;;;;;;;AASO,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,QAAQ,MAAM,QAAQ,SAAS,KAAK;AAAA,EAC3D;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,QAAQ,MAAM,QAAQ,SAAS,KAAK;AAAA,IACzD,GAAG,KAAK;AAER,WAAO,QAAQ,QAAQ;AAAA,EACzB;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,QAA+B;AACzD,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,QAAQ,QAAgB,QAAgB,QAA+B;AACrE,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,UAAyB;AACvB,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,SAAS,SAAiB,SAAc,QAAgB,WAAyB;AACrF,UAAM,WAAW,QAAQ,IAAI,OAAO;AAEpC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,aAAa,OAAO,aAAa;AAAA,IACnD;AAEA,UAAM,UAAsB,OAAO,OAAO;AAAA,MACxC,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,MACzB,MAAM;AAAA,MACN,SAAS;AAAA,MACT;AAAA,MACA,UAAU;AAAA,MACV,YAAY,oBAAI,KAAK;AAAA,MACrB,cAAc;AAAA,IAChB,CAAC;AAED,UAAM,aAAa,aAAa,cAAc;AAC9C,UAAM,cAAc,aAChB,MAAM,WAAW,UAAU,SAAS,OAAO,IAC3C,IAAI,SAAS,SAAS,OAAO;AAEjC,UAAM,YAAY,QAAQ;AAAA,EAC5B;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/drivers/sync_adapter.ts"],"sourcesContent":["import { Locator } from '../locator.js'\nimport { QueueManager } from '../queue_manager.js'\nimport type { Adapter, AcquiredJob } from '../contracts/adapter.js'\nimport type {\n JobContext,\n JobData,\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.name, jobData.payload, 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.name, jobData.payload, queue)\n }, delay)\n\n return Promise.resolve()\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): Promise<void> {\n return Promise.resolve()\n }\n\n failJob(_jobId: string, _queue: string, _error?: Error): 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 destroy(): Promise<void> {\n return Promise.resolve()\n }\n\n createSchedule(_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 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(jobName: string, payload: any, queue: string = 'default'): Promise<any> {\n const JobClass = Locator.get(jobName)\n\n if (!JobClass) {\n throw new Error(`Job class ${jobName} not found.`)\n }\n\n const context: JobContext = {\n jobId: `sync-${Date.now()}`,\n name: jobName,\n attempt: 1,\n queue,\n priority: DEFAULT_PRIORITY,\n acquiredAt: new Date(),\n stalledCount: 0,\n }\n\n const jobFactory = QueueManager.getJobFactory()\n const jobInstance = jobFactory ? await jobFactory(JobClass) : new JobClass()\n\n jobInstance.$hydrate(payload, context)\n await jobInstance.execute()\n }\n}\n"],"mappings":";;;;;;;;;AAeO,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,QAAQ,MAAM,QAAQ,SAAS,KAAK;AAAA,EAC3D;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,QAAQ,MAAM,QAAQ,SAAS,KAAK;AAAA,IACzD,GAAG,KAAK;AAER,WAAO,QAAQ,QAAQ;AAAA,EACzB;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,QAA+B;AACzD,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,QAAQ,QAAgB,QAAgB,QAA+B;AACrE,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,UAAyB;AACvB,WAAO,QAAQ,QAAQ;AAAA,EACzB;AAAA,EAEA,eAAe,SAA0C;AAGvD,WAAO,QAAQ,QAAQ,iBAAiB,KAAK,IAAI,CAAC,EAAE;AAAA,EACtD;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,SAAiB,SAAc,QAAgB,WAAyB;AACrF,UAAM,WAAW,QAAQ,IAAI,OAAO;AAEpC,QAAI,CAAC,UAAU;AACb,YAAM,IAAI,MAAM,aAAa,OAAO,aAAa;AAAA,IACnD;AAEA,UAAM,UAAsB;AAAA,MAC1B,OAAO,QAAQ,KAAK,IAAI,CAAC;AAAA,MACzB,MAAM;AAAA,MACN,SAAS;AAAA,MACT;AAAA,MACA,UAAU;AAAA,MACV,YAAY,oBAAI,KAAK;AAAA,MACrB,cAAc;AAAA,IAChB;AAEA,UAAM,aAAa,aAAa,cAAc;AAC9C,UAAM,cAAc,aAAa,MAAM,WAAW,QAAQ,IAAI,IAAI,SAAS;AAE3E,gBAAY,SAAS,SAAS,OAAO;AACrC,UAAM,YAAY,QAAQ;AAAA,EAC5B;AACF;","names":[]}
@@ -1 +1 @@
1
- export { a as AcquiredJob, A as Adapter, n as AdapterFactory, j as BackoffConfig, B as BackoffStrategy, D as Duration, d as JobClass, i as JobContext, J as JobData, b as JobFactory, h as JobOptions, L as Logger, k as QueueConfig, Q as QueueManagerConfig, R as RetryConfig, m as WorkerConfig, W as WorkerCycle } from '../../index-C3_tlebh.js';
1
+ export { a as AcquiredJob, A as Adapter, p as BackoffConfig, B as BackoffStrategy, m as DispatchResult, D as Duration, f as JobClass, o as JobContext, J as JobData, d as JobFactory, n as JobOptions, L as Logger, q as QueueConfig, Q as QueueManagerConfig, R as RetryConfig, S as ScheduleConfig, b as ScheduleData, c as ScheduleListOptions, t as ScheduleResult, g as ScheduleStatus, r as WorkerConfig, W as WorkerCycle } from '../../index-C0Xg6F4E.js';
@@ -1 +1 @@
1
- export { n as AdapterFactory, j as BackoffConfig, B as BackoffStrategy, D as Duration, d as JobClass, i as JobContext, J as JobData, b as JobFactory, h as JobOptions, L as Logger, k as QueueConfig, Q as QueueManagerConfig, R as RetryConfig, m as WorkerConfig, W as WorkerCycle } from '../../index-C3_tlebh.js';
1
+ export { s as AdapterFactory, p as BackoffConfig, B as BackoffStrategy, m as DispatchResult, D as Duration, f as JobClass, o as JobContext, J as JobData, d as JobFactory, n as JobOptions, L as Logger, q as QueueConfig, Q as QueueManagerConfig, R as RetryConfig, S as ScheduleConfig, b as ScheduleData, c as ScheduleListOptions, t as ScheduleResult, g as ScheduleStatus, r as WorkerConfig, W as WorkerCycle } from '../../index-C0Xg6F4E.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@boringnode/queue",
3
3
  "description": "A simple and efficient queue system for Node.js applications",
4
- "version": "0.0.1-alpha.4",
4
+ "version": "0.2.0",
5
5
  "main": "build/index.js",
6
6
  "type": "module",
7
7
  "files": [
@@ -28,7 +28,8 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@lukeed/ms": "^2.0.2",
31
- "@poppinss/utils": "^6.10.1"
31
+ "@poppinss/utils": "^6.10.1",
32
+ "cron-parser": "^5.4.0"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@adonisjs/eslint-config": "^2.1.2",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/debug.ts","../src/locator.ts","../src/logger.ts","../src/queue_manager.ts"],"sourcesContent":["import { debuglog } from 'node:util'\n\nexport default debuglog('boringnode:queue')\n","import { Job } from './job.js'\nimport * as errors from './exceptions.js'\nimport type { JobClass } from './types/main.js'\nimport debug from './debug.js'\nimport { glob } from 'node:fs/promises'\nimport { resolve } from 'node:path'\n\n/**\n * Job class registry.\n *\n * The Locator maintains a mapping of job names to their classes,\n * allowing the Worker to instantiate jobs by name when processing.\n *\n * Jobs are typically registered automatically via `QueueManager.init()`\n * using the `locations` config option, but can also be registered manually.\n *\n * @example\n * ```typescript\n * import { Locator } from '@boringnode/queue'\n * import SendEmailJob from './jobs/send_email_job.js'\n *\n * // Manual registration\n * Locator.register('SendEmailJob', SendEmailJob)\n *\n * // Auto-registration via glob (used by QueueManager.init)\n * await Locator.registerFromGlob(['./jobs/**\\/*.js'])\n *\n * // Retrieve a job class\n * const JobClass = Locator.getOrThrow('SendEmailJob')\n * ```\n */\nclass LocatorSingleton {\n #registry = new Map<string, JobClass>()\n\n /**\n * Register a job class with a given name.\n *\n * @param name - The job name (usually the class name)\n * @param JobClass - The job class constructor\n *\n * @example\n * ```typescript\n * Locator.register('SendEmailJob', SendEmailJob)\n * ```\n */\n register<T extends Job>(name: string, JobClass: JobClass<T>) {\n debug('registering job: %s', name)\n\n this.#registry.set(name, JobClass)\n }\n\n /**\n * Auto-register job classes from files matching glob patterns.\n *\n * Each file should have a default export that is a Job class.\n * The class name is used as the registration name.\n *\n * @param patterns - Glob patterns to match job files\n * @returns Number of jobs successfully registered\n *\n * @example\n * ```typescript\n * const count = await Locator.registerFromGlob([\n * './jobs/**\\/*.js',\n * './app/jobs/**\\/*.ts'\n * ])\n * console.log(`Registered ${count} jobs`)\n * ```\n */\n async registerFromGlob(patterns: string[]): Promise<number> {\n let registered = 0\n\n for (const pattern of patterns) {\n debug('registering jobs from glob pattern: %s', pattern)\n for await (const file of glob(pattern)) {\n debug('found job file: %s', file)\n\n try {\n const absolutePath = resolve(file)\n const module = await import(`file://${absolutePath}`)\n const JobClass = module.default as JobClass\n\n if (JobClass && typeof JobClass === 'function' && JobClass.name) {\n this.register(JobClass.name, JobClass)\n registered++\n }\n } catch (error) {\n console.warn(`Failed to load job from ${file}:`, error)\n }\n }\n }\n\n return registered\n }\n\n /**\n * Get a job class by name.\n *\n * @param name - The job name to look up\n * @returns The job class, or undefined if not found\n *\n * @example\n * ```typescript\n * const JobClass = Locator.get('SendEmailJob')\n * if (JobClass) {\n * const instance = new JobClass(payload)\n * }\n * ```\n */\n get<T extends Job = Job>(name: string): JobClass<T> | undefined {\n return this.#registry.get(name) as JobClass<T> | undefined\n }\n\n /**\n * Get a job class by name, throwing if not found.\n *\n * @param name - The job name to look up\n * @returns The job class\n * @throws {E_JOB_NOT_FOUND} If the job is not registered\n *\n * @example\n * ```typescript\n * const JobClass = Locator.getOrThrow('SendEmailJob')\n * const instance = new JobClass(payload)\n * ```\n */\n getOrThrow<T extends Job = Job>(name: string): JobClass<T> {\n const JobClass = this.get<T>(name)\n\n if (!JobClass) {\n throw new errors.E_JOB_NOT_FOUND([name])\n }\n\n return JobClass\n }\n\n /**\n * Remove all registered jobs.\n *\n * Primarily useful for testing.\n */\n clear(): void {\n this.#registry.clear()\n }\n}\n\n/** Global job class registry singleton */\nexport const Locator = new LocatorSingleton()\n","export interface LogObject {\n [key: string]: unknown\n}\n\nexport interface ErrorObject extends LogObject {\n err?: Error\n}\n\nexport interface Logger {\n trace(msg: string): void\n trace(obj: LogObject, msg: string): void\n\n debug(msg: string): void\n debug(obj: LogObject, msg: string): void\n\n info(msg: string): void\n info(obj: LogObject, msg: string): void\n\n warn(msg: string): void\n warn(obj: LogObject, msg: string): void\n\n error(msg: string): void\n error(obj: ErrorObject, msg: string): void\n\n child(obj: LogObject): Logger\n}\n\n/**\n * A simple logger that writes to console.\n */\nclass ConsoleLogger implements Logger {\n #prefix: string\n\n constructor(prefix: string = 'queue') {\n this.#prefix = prefix\n }\n\n #format(level: string, msgOrObj: string | LogObject, msg?: string): [string, LogObject?] {\n const prefix = `[${this.#prefix}] ${level}:`\n\n if (typeof msgOrObj === 'object') {\n return [`${prefix} ${msg}`, msgOrObj]\n }\n\n return [`${prefix} ${msgOrObj}`]\n }\n\n trace(msg: string): void\n trace(obj: LogObject, msg: string): void\n trace(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('TRACE', msgOrObj, msg)\n\n if (obj) {\n return console.log(message, obj)\n }\n\n console.log(message)\n }\n\n debug(msg: string): void\n debug(obj: LogObject, msg: string): void\n debug(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('DEBUG', msgOrObj, msg)\n\n if (obj) {\n return console.log(message, obj)\n }\n\n console.log(message)\n }\n\n info(msg: string): void\n info(obj: LogObject, msg: string): void\n info(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('INFO', msgOrObj, msg)\n\n if (obj) {\n return console.log(message, obj)\n }\n\n console.log(message)\n }\n\n warn(msg: string): void\n warn(obj: LogObject, msg: string): void\n warn(msgOrObj: string | LogObject, msg?: string): void {\n const [message, obj] = this.#format('WARN', msgOrObj, msg)\n\n if (obj) {\n return console.warn(message, obj)\n }\n\n console.warn(message)\n }\n\n error(msg: string): void\n error(obj: ErrorObject, msg: string): void\n error(msgOrObj: string | ErrorObject, msg?: string): void {\n const [message, obj] = this.#format('ERROR', msgOrObj, msg)\n\n if (obj) {\n return console.error(message, obj)\n }\n\n console.error(message)\n }\n\n child(obj: LogObject): Logger {\n const childPrefix = obj.pkg ? String(obj.pkg) : this.#prefix\n return new ConsoleLogger(childPrefix)\n }\n}\n\nexport const consoleLogger = new ConsoleLogger()\n","import * as errors from './exceptions.js'\nimport debug from './debug.js'\nimport { Locator } from './locator.js'\nimport { consoleLogger, type Logger } from './logger.js'\nimport type { Adapter } from './contracts/adapter.js'\nimport type {\n AdapterFactory,\n JobFactory,\n QueueConfig,\n QueueManagerConfig,\n RetryConfig,\n} from './types/main.js'\n\n/**\n * Central configuration and adapter management for the queue system.\n *\n * The QueueManager is responsible for:\n * - Initializing adapters and job registration\n * - Providing adapter instances to workers and dispatchers\n * - Managing retry configuration across global, queue, and job levels\n *\n * @example\n * ```typescript\n * import { QueueManager, redis } from '@boringnode/queue'\n *\n * await QueueManager.init({\n * default: 'redis',\n * adapters: {\n * redis: redis({ host: 'localhost' }),\n * },\n * locations: ['./jobs/**\\/*.js'],\n * retry: {\n * maxRetries: 3,\n * backoff: exponentialBackoff(),\n * },\n * })\n *\n * // Get the default adapter\n * const adapter = QueueManager.use()\n *\n * // Clean up when done\n * await QueueManager.destroy()\n * ```\n */\nclass QueueManagerSingleton {\n #initialized = false\n #defaultAdapter!: string\n #adapters: Record<string, AdapterFactory> = {}\n #adapterInstances: Map<string, Adapter> = new Map()\n #globalRetryConfig?: RetryConfig\n #queueConfigs: Map<string, QueueConfig> = new Map()\n #logger: Logger = consoleLogger\n #jobFactory?: JobFactory\n\n /**\n * Initialize the queue system with the given configuration.\n *\n * This must be called before using the queue system. It:\n * - Validates the configuration\n * - Registers adapters\n * - Auto-discovers and registers job classes from `locations`\n *\n * @param config - The queue configuration\n * @returns This instance for chaining\n * @throws {E_CONFIGURATION_ERROR} If the configuration is invalid\n *\n * @example\n * ```typescript\n * await QueueManager.init({\n * default: 'redis',\n * adapters: {\n * redis: redis(),\n * postgres: knex(pgConfig),\n * },\n * locations: ['./jobs/**\\/*.js'],\n * })\n * ```\n */\n async init(config: QueueManagerConfig) {\n debug('initializing queue manager with config: %O', config)\n\n this.#validateConfig(config)\n\n this.#adapterInstances.clear()\n\n this.#defaultAdapter = config.default\n this.#adapters = config.adapters\n this.#globalRetryConfig = config.retry\n this.#logger = config.logger ?? consoleLogger\n this.#jobFactory = config.jobFactory\n\n if (config.queues) {\n for (const [queue, queueConfig] of Object.entries(config.queues)) {\n this.#queueConfigs.set(queue, queueConfig as QueueConfig)\n }\n }\n\n if (config.locations && config.locations.length > 0) {\n const registered = await Locator.registerFromGlob(config.locations)\n\n if (registered === 0) {\n this.#logger.warn(\n `No jobs found for locations: ${config.locations.join(', ')}. ` +\n 'Verify your glob patterns match your job files.'\n )\n }\n }\n\n this.#initialized = true\n\n return this\n }\n\n /**\n * Get an adapter instance by name.\n *\n * Adapter instances are cached and reused. If no name is provided,\n * the default adapter is returned.\n *\n * @param adapter - Adapter name (optional, defaults to the default adapter)\n * @returns The adapter instance\n * @throws {E_QUEUE_NOT_INITIALIZED} If `init()` hasn't been called\n * @throws {E_CONFIGURATION_ERROR} If the adapter is not registered\n * @throws {E_ADAPTER_INIT_ERROR} If the adapter factory throws\n *\n * @example\n * ```typescript\n * // Get default adapter\n * const adapter = QueueManager.use()\n *\n * // Get specific adapter\n * const redisAdapter = QueueManager.use('redis')\n * ```\n */\n use(adapter?: string): Adapter {\n if (!this.#initialized) {\n throw new errors.E_QUEUE_NOT_INITIALIZED()\n }\n\n if (!adapter) {\n adapter = this.#defaultAdapter\n }\n\n // Return cached instance if exists\n const cached = this.#adapterInstances.get(adapter)\n if (cached) {\n return cached\n }\n\n const adapterFactory = this.#adapters[adapter]\n\n if (!adapterFactory) {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${adapter}\" is not registered`])\n }\n\n debug('using adapter \"%s\"', adapter)\n\n try {\n const instance = adapterFactory()\n this.#adapterInstances.set(adapter, instance)\n return instance\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n throw new errors.E_ADAPTER_INIT_ERROR([adapter, message])\n }\n }\n\n /**\n * Get the merged retry configuration for a job.\n *\n * Configuration is merged with priority: job > queue > global.\n * This allows specific jobs or queues to override global defaults.\n *\n * @param queue - The queue name\n * @param jobRetryConfig - Optional job-level retry config\n * @returns The merged retry configuration\n *\n * @example\n * ```typescript\n * // Global: maxRetries=3, Queue: maxRetries=5, Job: maxRetries=1\n * // Result: maxRetries=1 (job wins)\n * const config = QueueManager.getMergedRetryConfig('emails', { maxRetries: 1 })\n * ```\n */\n getMergedRetryConfig(queue: string, jobRetryConfig?: RetryConfig): RetryConfig {\n const queueConfig = this.#queueConfigs.get(queue)\n const queueRetryConfig = queueConfig?.retry || {}\n\n let maxRetries =\n jobRetryConfig?.maxRetries ||\n queueRetryConfig.maxRetries ||\n this.#globalRetryConfig?.maxRetries ||\n 0\n\n let backoff =\n jobRetryConfig?.backoff || queueRetryConfig.backoff || this.#globalRetryConfig?.backoff\n\n return { maxRetries, backoff }\n }\n\n /**\n * Get the configured job factory for custom instantiation.\n *\n * @returns The job factory function, or undefined if not configured\n */\n getJobFactory(): JobFactory | undefined {\n return this.#jobFactory\n }\n\n #validateConfig(config: QueueManagerConfig): void {\n if (!config.adapters || Object.keys(config.adapters).length === 0) {\n throw new errors.E_CONFIGURATION_ERROR(['At least one adapter must be configured'])\n }\n\n if (!config.default) {\n throw new errors.E_CONFIGURATION_ERROR(['Default adapter must be specified'])\n }\n\n if (!config.adapters[config.default]) {\n throw new errors.E_CONFIGURATION_ERROR([\n `Default adapter \"${config.default}\" not found in adapters configuration`,\n ])\n }\n\n for (const [name, factory] of Object.entries(config.adapters)) {\n if (typeof factory !== 'function') {\n throw new errors.E_CONFIGURATION_ERROR([`Adapter \"${name}\" must be a factory function`])\n }\n }\n }\n\n /**\n * Clean up all adapter instances and reset state.\n *\n * Call this when shutting down the application or when\n * you need to reinitialize with a new configuration.\n *\n * @example\n * ```typescript\n * // On application shutdown\n * await QueueManager.destroy()\n * ```\n */\n async destroy() {\n for (const [name, adapter] of this.#adapterInstances) {\n debug('destroying adapter \"%s\"', name)\n await adapter.destroy()\n }\n this.#adapterInstances.clear()\n this.#initialized = false\n }\n}\n\n/** Global queue manager singleton */\nexport const QueueManager = new QueueManagerSingleton()\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/exceptions.ts","../src/constants.ts"],"sourcesContent":["import { createError } from '@poppinss/utils'\n\nexport const E_INVALID_DURATION_EXPRESSION = createError(\n 'Invalid duration expression: \"%s\"',\n 'E_INVALID_DURATION_EXPRESSION',\n 500\n)\n\nexport const E_INVALID_BASE_DELAY = createError<[reason: string]>(\n 'Invalid base delay. Reason: %s',\n 'E_INVALID_BASE_DELAY',\n 500\n)\n\nexport const E_INVALID_MAX_DELAY = createError<[reason: string]>(\n 'Invalid max delay. Reason: %s',\n 'E_INVALID_MAX_DELAY',\n 500\n)\n\nexport const E_INVALID_MULTIPLIER = createError<[reason: string]>(\n 'Invalid multiplier. Reason: %s',\n 'E_INVALID_MULTIPLIER',\n 500\n)\n\nexport const E_CONFIGURATION_ERROR = createError<[reason: string]>(\n 'Configuration error. Reason: %s',\n 'E_CONFIGURATION_ERROR',\n 500\n)\n\nexport const E_JOB_NOT_FOUND = createError<[jobName: string]>(\n 'Requested job \"%s\" is not registered',\n 'E_JOB_NOT_FOUND'\n)\n\nexport const E_JOB_MAX_ATTEMPTS_REACHED = createError<[jobName: string]>(\n 'The job \"%s\" has reached the maximum number of retry attempts',\n 'E_JOB_MAX_ATTEMPTS_REACHED'\n)\n\nexport const E_JOB_TIMEOUT = createError<[jobName: string, timeout: number]>(\n 'The job \"%s\" has exceeded the timeout of %dms',\n 'E_JOB_TIMEOUT'\n)\n\nexport const E_QUEUE_NOT_INITIALIZED = createError(\n 'QueueManager is not initialized. Call QueueManager.init() before using it.',\n 'E_QUEUE_NOT_INITIALIZED',\n 500\n)\n\nexport const E_ADAPTER_INIT_ERROR = createError<[adapterName: string, originalMessage: string]>(\n 'Failed to initialize adapter \"%s\". Reason: %s',\n 'E_ADAPTER_INIT_ERROR',\n 500\n)\n\nexport const E_NO_JOBS_FOUND = createError<[patterns: string]>(\n 'No jobs found for the specified locations: %s. Verify your glob patterns match your job files.',\n 'E_NO_JOBS_FOUND',\n 500\n)\n","/**\n * Default job priority (1-10 scale, lower = higher priority)\n */\nexport const DEFAULT_PRIORITY = 5\n\n/**\n * Multiplier used in score calculation: priority * multiplier + timestamp\n *\n * This ensures higher priority jobs are processed first,\n * while preserving FIFO order within the same priority.\n * The value (1e13) leaves room for ~300 years of millisecond timestamps.\n */\nexport const PRIORITY_SCORE_MULTIPLIER = 1e13\n\n/**\n * Default delay when the worker is idle (no jobs in queue)\n */\nexport const DEFAULT_IDLE_DELAY = '2s'\n\n/**\n * Default interval between stalled job checks\n */\nexport const DEFAULT_STALLED_INTERVAL = '30s'\n\n/**\n * Default threshold after which a job is considered stalled\n */\nexport const DEFAULT_STALLED_THRESHOLD = '30s'\n\n/**\n * Default delay before retrying after an error\n */\nexport const DEFAULT_ERROR_RETRY_DELAY = '5s'\n"],"mappings":";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,SAAS,mBAAmB;AAErB,IAAM,gCAAgC;AAAA,EAC3C;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,sBAAsB;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,wBAAwB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AACF;AAEO,IAAM,6BAA6B;AAAA,EACxC;AAAA,EACA;AACF;AAEO,IAAM,gBAAgB;AAAA,EAC3B;AAAA,EACA;AACF;AAEO,IAAM,0BAA0B;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,kBAAkB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AACF;;;AC5DO,IAAM,mBAAmB;AASzB,IAAM,4BAA4B;AAKlC,IAAM,qBAAqB;AAK3B,IAAM,2BAA2B;AAKjC,IAAM,4BAA4B;AAKlC,IAAM,4BAA4B;","names":[]}