tasklane 0.1.0 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -11,12 +11,22 @@ var _flowProducer = null;
11
11
  var _handlers = /* @__PURE__ */ new Map();
12
12
  var _failedListeners = [];
13
13
  var _completedListeners = [];
14
- function parseRedis(url) {
15
- const parsed = new URL(url);
14
+ function parseRedis(input) {
15
+ if (typeof input !== "string") {
16
+ return {
17
+ host: input.host,
18
+ port: input.port ?? 6379,
19
+ password: input.password,
20
+ username: input.username,
21
+ db: input.db ?? 0
22
+ };
23
+ }
24
+ const parsed = new URL(input);
16
25
  return {
17
26
  host: parsed.hostname || "127.0.0.1",
18
27
  port: parsed.port ? parseInt(parsed.port, 10) : 6379,
19
- password: parsed.password || void 0,
28
+ password: parsed.password ? decodeURIComponent(parsed.password) : void 0,
29
+ username: parsed.username ? decodeURIComponent(parsed.username) : void 0,
20
30
  db: parsed.pathname && parsed.pathname !== "/" ? parseInt(parsed.pathname.slice(1), 10) : 0
21
31
  };
22
32
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts","../src/job.ts","../src/index.ts"],"names":["Queue","FlowProducer","Worker","job","startWorker","getFailed","retryJob"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Queue, Worker, FlowProducer } from \"bullmq\";\nimport type { ConnectionOptions, FlowJob } from \"bullmq\";\nimport type { JobsConfig, FailedEvent, CompletedEvent, FailedJob, BackoffStrategy, FlowNode } from \"./types.js\";\n\n// --- Global singleton state ---\n\nlet _config: JobsConfig | null = null;\nlet _connection: ConnectionOptions | null = null;\nlet _queue: Queue | null = null;\nlet _worker: Worker | null = null;\nlet _flowProducer: FlowProducer | null = null;\n\nconst _handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();\nconst _failedListeners: ((e: FailedEvent) => void)[] = [];\nconst _completedListeners: ((e: CompletedEvent) => void)[] = [];\n\n// --- Internal helpers ---\n\nfunction parseRedis(url: string): ConnectionOptions {\n const parsed = new URL(url);\n return {\n host: parsed.hostname || \"127.0.0.1\",\n port: parsed.port ? parseInt(parsed.port, 10) : 6379,\n password: parsed.password || undefined,\n db: parsed.pathname && parsed.pathname !== \"/\" ? parseInt(parsed.pathname.slice(1), 10) : 0,\n };\n}\n\nfunction requireQueue(): Queue {\n if (!_queue) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching jobs.`\n );\n }\n return _queue;\n}\n\nfunction requireFlowProducer(): FlowProducer {\n if (!_flowProducer) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching flows.`\n );\n }\n return _flowProducer;\n}\n\nfunction defaultBackoff(override?: BackoffStrategy) {\n return override ?? _config?.backoff ?? { type: \"exponential\" as const, delay: 1000 };\n}\n\nfunction defaultJobOpts(attempts?: number, backoff?: BackoffStrategy) {\n return {\n attempts: attempts ?? _config?.attempts ?? 3,\n backoff: defaultBackoff(backoff),\n removeOnComplete: { count: 1000 },\n removeOnFail: { count: 5000 },\n };\n}\n\nfunction toFlowJob(node: FlowNode): FlowJob {\n return {\n name: (node.job as { jobName: string }).jobName,\n queueName: _config?.queue ?? \"default\",\n data: { args: node.args ?? [] },\n opts: defaultJobOpts(node.attempts, node.backoff),\n children: node.children?.map(toFlowJob),\n };\n}\n\n// --- Public registry API ---\n\nexport function configure(cfg: JobsConfig): void {\n _config = cfg;\n _connection = parseRedis(cfg.redis);\n _queue = new Queue(cfg.queue ?? \"default\", { connection: _connection });\n _flowProducer = new FlowProducer({ connection: _connection });\n}\n\nexport function resetListeners(): void {\n _failedListeners.length = 0;\n _completedListeners.length = 0;\n}\n\nexport function registerHandler(\n name: string,\n fn: (...args: unknown[]) => Promise<unknown>\n): void {\n _handlers.set(name, fn);\n}\n\nexport async function enqueue(\n name: string,\n args: unknown[],\n opts?: { delay?: number; attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().add(name, { args }, {\n ...defaultJobOpts(opts?.attempts, opts?.backoff),\n delay: opts?.delay,\n });\n}\n\nexport async function enqueueScheduled(\n name: string,\n cronPattern: string,\n args: unknown[],\n opts?: { attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().upsertJobScheduler(\n `${name}::cron`,\n { pattern: cronPattern },\n {\n name,\n data: { args },\n opts: defaultJobOpts(opts?.attempts, opts?.backoff),\n }\n );\n}\n\nexport async function dispatchFlow(node: FlowNode): Promise<void> {\n await requireFlowProducer().add(toFlowJob(node));\n}\n\nexport async function startWorker(): Promise<void> {\n if (!_config || !_connection) {\n throw new Error(`[tasklane] initJobs() has not been called before startWorker().`);\n }\n if (_worker) return;\n\n _worker = new Worker(\n _config.queue ?? \"default\",\n async (bullJob) => {\n const handler = _handlers.get(bullJob.name);\n if (!handler) {\n throw new Error(\n `[tasklane] No handler registered for job \"${bullJob.name}\". ` +\n `Make sure the file defining this job is imported before startWorker() is called.`\n );\n }\n const args = (bullJob.data as { args: unknown[] }).args ?? [];\n return handler(...args);\n },\n { connection: _connection }\n );\n\n _worker.on(\"error\", (err) => {\n _failedListeners.forEach((fn) =>\n fn({\n jobId: \"worker\",\n name: \"worker-error\",\n args: [],\n error: err,\n attemptsMade: 0,\n isFinalFailure: false,\n })\n );\n });\n\n _worker.on(\"failed\", (bullJob, err) => {\n if (!bullJob) return;\n const isFinalFailure = bullJob.attemptsMade >= (bullJob.opts.attempts ?? 1);\n const event: FailedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n error: err,\n attemptsMade: bullJob.attemptsMade,\n isFinalFailure,\n };\n _failedListeners.forEach((fn) => fn(event));\n });\n\n _worker.on(\"completed\", (bullJob, result) => {\n const event: CompletedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n result,\n };\n _completedListeners.forEach((fn) => fn(event));\n });\n\n await _worker.waitUntilReady();\n}\n\nexport function onFailed(listener: (e: FailedEvent) => void): () => void {\n _failedListeners.push(listener);\n return () => {\n const i = _failedListeners.indexOf(listener);\n if (i !== -1) _failedListeners.splice(i, 1);\n };\n}\n\nexport function onCompleted(listener: (e: CompletedEvent) => void): () => void {\n _completedListeners.push(listener);\n return () => {\n const i = _completedListeners.indexOf(listener);\n if (i !== -1) _completedListeners.splice(i, 1);\n };\n}\n\nexport async function getFailed(start = 0, limit = 50): Promise<FailedJob[]> {\n const queue = requireQueue();\n const jobs = await queue.getFailed(start, start + limit - 1);\n return jobs.map((j) => ({\n jobId: j.id!,\n name: j.name,\n args: (j.data as { args: unknown[] }).args ?? [],\n failedReason: j.failedReason ?? \"Unknown error\",\n attemptsMade: j.attemptsMade,\n timestamp: j.timestamp,\n finishedOn: j.finishedOn,\n }));\n}\n\nexport async function retryJob(jobId: string): Promise<void> {\n const queue = requireQueue();\n const job = await queue.getJob(jobId);\n if (!job) {\n throw new Error(`[tasklane] retryJob: no job found with id \"${jobId}\"`);\n }\n await job.retry();\n}\n\nexport async function stopAll(): Promise<void> {\n await Promise.all([\n _worker?.close(),\n _queue?.close(),\n _flowProducer?.close(),\n ]);\n _worker = null;\n _queue = null;\n _flowProducer = null;\n _connection = null;\n _config = null;\n}\n","import { registerHandler, enqueue, enqueueScheduled } from \"./registry.js\";\nimport type { JobFnOptions, BackoffStrategy } from \"./types.js\";\n\n/**\n * Wraps an async function as a background job.\n *\n * The returned function has the same signature as the original — calling it\n * enqueues the job instead of running it inline.\n *\n * The function MUST be named (not an arrow function) so the package can\n * use its name as the job identifier.\n *\n * @example\n * export const sendSms = job(async function sendSms(to: string, message: string) {\n * await smsProvider.send({ to, message });\n * }, { attempts: 5, backoff: { type: \"fixed\", delay: 2000 } });\n *\n * // Enqueues immediately\n * await sendSms(\"Mushud\", \"Your OTP is 1234\");\n *\n * // Enqueue after a delay (ms)\n * await sendSms.delay(60_000)(\"Mushud\", \"Reminder\");\n *\n * // Enqueue once at a specific date\n * await sendSms.at(new Date(\"2026-04-13T09:00:00Z\"))(\"Mushud\", \"Good morning\");\n *\n * // Recurring cron schedule\n * await sendSms.cron(\"0 9 * * *\")(\"Mushud\", \"Daily reminder\");\n *\n * // Run directly without the queue\n * await sendSms.run(\"Mushud\", \"Inline\");\n */\nexport function job<T extends (...args: any[]) => Promise<any>>(\n fn: T,\n opts?: JobFnOptions\n): JobFn<T> {\n const name = fn.name;\n\n if (!name) {\n throw new Error(\n `[tasklane] job() requires a named function. ` +\n `Use: job(async function myJobName(...) { ... }) — not an arrow function.`\n );\n }\n\n registerHandler(name, fn);\n\n // Extracted once — shared by all dispatch paths\n const attempts = opts?.attempts;\n const backoff: BackoffStrategy | undefined = opts?.backoff;\n\n const proxy = async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { attempts, backoff });\n };\n\n return Object.assign(proxy, {\n /** The registered job name — used by flow() to build the job tree */\n jobName: name,\n\n /** Run the handler directly, bypassing the queue entirely */\n run: (...args: Parameters<T>): ReturnType<T> => fn(...args) as ReturnType<T>,\n\n delay: (ms: number) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { delay: ms, attempts, backoff });\n },\n\n at: (date: Date) =>\n async (...args: Parameters<T>): Promise<void> => {\n const delay = date.getTime() - Date.now();\n if (delay < 0) throw new Error(`[tasklane] .at() date is in the past`);\n await enqueue(name, args, { delay, attempts, backoff });\n },\n\n cron: (pattern: string) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueueScheduled(name, pattern, args, { attempts, backoff });\n },\n }) as unknown as JobFn<T>;\n}\n\n/** The type returned by job() — same call signature plus scheduling helpers */\nexport type JobFn<T extends (...args: any[]) => Promise<any>> = {\n (...args: Parameters<T>): Promise<void>;\n /** The registered job name */\n readonly jobName: string;\n /** Run the handler directly, bypassing the queue entirely */\n run(...args: Parameters<T>): ReturnType<T>;\n /** Enqueue after a delay in milliseconds */\n delay(ms: number): (...args: Parameters<T>) => Promise<void>;\n /** Enqueue once at a specific Date */\n at(date: Date): (...args: Parameters<T>) => Promise<void>;\n /** Register a recurring cron schedule */\n cron(pattern: string): (...args: Parameters<T>) => Promise<void>;\n};\n","import {\n configure,\n startWorker as _startWorker,\n stopAll,\n onFailed,\n onCompleted,\n getFailed as _getFailed,\n retryJob as _retryJob,\n dispatchFlow,\n resetListeners,\n} from \"./registry.js\";\nimport type { JobsConfig, FailedEvent, CompletedEvent, FlowNode } from \"./types.js\";\n\nexport { job } from \"./job.js\";\nexport type { JobFn } from \"./job.js\";\nexport type {\n JobsConfig,\n JobFnOptions,\n FailedEvent,\n CompletedEvent,\n FailedJob,\n BackoffStrategy,\n FlowNode,\n} from \"./types.js\";\n\n/**\n * Initialize the jobs runtime. Call this once at app startup before\n * importing or calling any job functions.\n *\n * @example\n * initJobs({\n * redis: process.env.REDIS_URL,\n * attempts: 3,\n * backoff: { type: \"exponential\", delay: 1000 },\n * });\n */\nexport function initJobs(config: JobsConfig): void {\n configure(config);\n}\n\n/**\n * Start the worker process. Call after initJobs() and after importing\n * all files that define jobs.\n */\nexport async function startWorker(): Promise<void> {\n return _startWorker();\n}\n\n/**\n * Gracefully stop the worker, close all queue connections,\n * and clear all event listeners.\n */\nexport async function stopWorker(): Promise<void> {\n resetListeners();\n return stopAll();\n}\n\n/**\n * Listen for failed jobs. Fires on every failed attempt.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = onJobFailed((event) => {\n * if (event.isFinalFailure) alerting.send(event.name);\n * });\n * // later: unsub();\n */\nexport function onJobFailed(listener: (event: FailedEvent) => void): () => void {\n return onFailed(listener);\n}\n\n/**\n * Listen for completed jobs. Returns an unsubscribe function.\n */\nexport function onJobCompleted(listener: (event: CompletedEvent) => void): () => void {\n return onCompleted(listener);\n}\n\n/**\n * Get a list of permanently failed jobs (all retries exhausted).\n *\n * @param start - Pagination offset. Default: 0\n * @param limit - Max results. Default: 50\n */\nexport async function getFailed(start = 0, limit = 50) {\n return _getFailed(start, limit);\n}\n\n/**\n * Re-queue a permanently failed job by its ID.\n * The attempt count resets to zero.\n */\nexport async function retryJob(jobId: string): Promise<void> {\n return _retryJob(jobId);\n}\n\n/**\n * Dispatch a job flow — a parent job that runs only after all its\n * children complete. Supports unlimited nesting depth.\n *\n * Children run in parallel. The parent runs last.\n * If any child fails permanently, the parent is not executed.\n *\n * @example\n * await flow({\n * job: processOrder,\n * args: [\"ord_123\"],\n * children: [\n * { job: chargePayment, args: [\"ord_123\"] },\n * { job: reserveInventory, args: [\"ord_123\"] },\n * {\n * job: prepareShipment,\n * args: [\"ord_123\"],\n * children: [\n * { job: validateAddress, args: [\"ord_123\"] },\n * ],\n * },\n * ],\n * });\n */\nexport async function flow(node: FlowNode): Promise<void> {\n return dispatchFlow(node);\n}\n"]}
1
+ {"version":3,"sources":["../src/registry.ts","../src/job.ts","../src/index.ts"],"names":["Queue","FlowProducer","Worker","job","startWorker","getFailed","retryJob"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Queue, Worker, FlowProducer } from \"bullmq\";\nimport type { ConnectionOptions, FlowJob } from \"bullmq\";\nimport type { JobsConfig, RedisOptions, FailedEvent, CompletedEvent, FailedJob, BackoffStrategy, FlowNode } from \"./types.js\";\n\n// --- Global singleton state ---\n\nlet _config: JobsConfig | null = null;\nlet _connection: ConnectionOptions | null = null;\nlet _queue: Queue | null = null;\nlet _worker: Worker | null = null;\nlet _flowProducer: FlowProducer | null = null;\n\nconst _handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();\nconst _failedListeners: ((e: FailedEvent) => void)[] = [];\nconst _completedListeners: ((e: CompletedEvent) => void)[] = [];\n\n// --- Internal helpers ---\n\nfunction parseRedis(input: string | RedisOptions): ConnectionOptions {\n if (typeof input !== \"string\") {\n return {\n host: input.host,\n port: input.port ?? 6379,\n password: input.password,\n username: input.username,\n db: input.db ?? 0,\n };\n }\n const parsed = new URL(input);\n return {\n host: parsed.hostname || \"127.0.0.1\",\n port: parsed.port ? parseInt(parsed.port, 10) : 6379,\n password: parsed.password ? decodeURIComponent(parsed.password) : undefined,\n username: parsed.username ? decodeURIComponent(parsed.username) : undefined,\n db: parsed.pathname && parsed.pathname !== \"/\" ? parseInt(parsed.pathname.slice(1), 10) : 0,\n };\n}\n\nfunction requireQueue(): Queue {\n if (!_queue) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching jobs.`\n );\n }\n return _queue;\n}\n\nfunction requireFlowProducer(): FlowProducer {\n if (!_flowProducer) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching flows.`\n );\n }\n return _flowProducer;\n}\n\nfunction defaultBackoff(override?: BackoffStrategy) {\n return override ?? _config?.backoff ?? { type: \"exponential\" as const, delay: 1000 };\n}\n\nfunction defaultJobOpts(attempts?: number, backoff?: BackoffStrategy) {\n return {\n attempts: attempts ?? _config?.attempts ?? 3,\n backoff: defaultBackoff(backoff),\n removeOnComplete: { count: 1000 },\n removeOnFail: { count: 5000 },\n };\n}\n\nfunction toFlowJob(node: FlowNode): FlowJob {\n return {\n name: (node.job as { jobName: string }).jobName,\n queueName: _config?.queue ?? \"default\",\n data: { args: node.args ?? [] },\n opts: defaultJobOpts(node.attempts, node.backoff),\n children: node.children?.map(toFlowJob),\n };\n}\n\n// --- Public registry API ---\n\nexport function configure(cfg: JobsConfig): void {\n _config = cfg;\n _connection = parseRedis(cfg.redis);\n _queue = new Queue(cfg.queue ?? \"default\", { connection: _connection });\n _flowProducer = new FlowProducer({ connection: _connection });\n}\n\nexport function resetListeners(): void {\n _failedListeners.length = 0;\n _completedListeners.length = 0;\n}\n\nexport function registerHandler(\n name: string,\n fn: (...args: unknown[]) => Promise<unknown>\n): void {\n _handlers.set(name, fn);\n}\n\nexport async function enqueue(\n name: string,\n args: unknown[],\n opts?: { delay?: number; attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().add(name, { args }, {\n ...defaultJobOpts(opts?.attempts, opts?.backoff),\n delay: opts?.delay,\n });\n}\n\nexport async function enqueueScheduled(\n name: string,\n cronPattern: string,\n args: unknown[],\n opts?: { attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().upsertJobScheduler(\n `${name}::cron`,\n { pattern: cronPattern },\n {\n name,\n data: { args },\n opts: defaultJobOpts(opts?.attempts, opts?.backoff),\n }\n );\n}\n\nexport async function dispatchFlow(node: FlowNode): Promise<void> {\n await requireFlowProducer().add(toFlowJob(node));\n}\n\nexport async function startWorker(): Promise<void> {\n if (!_config || !_connection) {\n throw new Error(`[tasklane] initJobs() has not been called before startWorker().`);\n }\n if (_worker) return;\n\n _worker = new Worker(\n _config.queue ?? \"default\",\n async (bullJob) => {\n const handler = _handlers.get(bullJob.name);\n if (!handler) {\n throw new Error(\n `[tasklane] No handler registered for job \"${bullJob.name}\". ` +\n `Make sure the file defining this job is imported before startWorker() is called.`\n );\n }\n const args = (bullJob.data as { args: unknown[] }).args ?? [];\n return handler(...args);\n },\n { connection: _connection }\n );\n\n _worker.on(\"error\", (err) => {\n _failedListeners.forEach((fn) =>\n fn({\n jobId: \"worker\",\n name: \"worker-error\",\n args: [],\n error: err,\n attemptsMade: 0,\n isFinalFailure: false,\n })\n );\n });\n\n _worker.on(\"failed\", (bullJob, err) => {\n if (!bullJob) return;\n const isFinalFailure = bullJob.attemptsMade >= (bullJob.opts.attempts ?? 1);\n const event: FailedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n error: err,\n attemptsMade: bullJob.attemptsMade,\n isFinalFailure,\n };\n _failedListeners.forEach((fn) => fn(event));\n });\n\n _worker.on(\"completed\", (bullJob, result) => {\n const event: CompletedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n result,\n };\n _completedListeners.forEach((fn) => fn(event));\n });\n\n await _worker.waitUntilReady();\n}\n\nexport function onFailed(listener: (e: FailedEvent) => void): () => void {\n _failedListeners.push(listener);\n return () => {\n const i = _failedListeners.indexOf(listener);\n if (i !== -1) _failedListeners.splice(i, 1);\n };\n}\n\nexport function onCompleted(listener: (e: CompletedEvent) => void): () => void {\n _completedListeners.push(listener);\n return () => {\n const i = _completedListeners.indexOf(listener);\n if (i !== -1) _completedListeners.splice(i, 1);\n };\n}\n\nexport async function getFailed(start = 0, limit = 50): Promise<FailedJob[]> {\n const queue = requireQueue();\n const jobs = await queue.getFailed(start, start + limit - 1);\n return jobs.map((j) => ({\n jobId: j.id!,\n name: j.name,\n args: (j.data as { args: unknown[] }).args ?? [],\n failedReason: j.failedReason ?? \"Unknown error\",\n attemptsMade: j.attemptsMade,\n timestamp: j.timestamp,\n finishedOn: j.finishedOn,\n }));\n}\n\nexport async function retryJob(jobId: string): Promise<void> {\n const queue = requireQueue();\n const job = await queue.getJob(jobId);\n if (!job) {\n throw new Error(`[tasklane] retryJob: no job found with id \"${jobId}\"`);\n }\n await job.retry();\n}\n\nexport async function stopAll(): Promise<void> {\n await Promise.all([\n _worker?.close(),\n _queue?.close(),\n _flowProducer?.close(),\n ]);\n _worker = null;\n _queue = null;\n _flowProducer = null;\n _connection = null;\n _config = null;\n}\n","import { registerHandler, enqueue, enqueueScheduled } from \"./registry.js\";\nimport type { JobFnOptions, BackoffStrategy } from \"./types.js\";\n\n/**\n * Wraps an async function as a background job.\n *\n * The returned function has the same signature as the original — calling it\n * enqueues the job instead of running it inline.\n *\n * The function MUST be named (not an arrow function) so the package can\n * use its name as the job identifier.\n *\n * @example\n * export const sendSms = job(async function sendSms(to: string, message: string) {\n * await smsProvider.send({ to, message });\n * }, { attempts: 5, backoff: { type: \"fixed\", delay: 2000 } });\n *\n * // Enqueues immediately\n * await sendSms(\"Mushud\", \"Your OTP is 1234\");\n *\n * // Enqueue after a delay (ms)\n * await sendSms.delay(60_000)(\"Mushud\", \"Reminder\");\n *\n * // Enqueue once at a specific date\n * await sendSms.at(new Date(\"2026-04-13T09:00:00Z\"))(\"Mushud\", \"Good morning\");\n *\n * // Recurring cron schedule\n * await sendSms.cron(\"0 9 * * *\")(\"Mushud\", \"Daily reminder\");\n *\n * // Run directly without the queue\n * await sendSms.run(\"Mushud\", \"Inline\");\n */\nexport function job<T extends (...args: any[]) => Promise<any>>(\n fn: T,\n opts?: JobFnOptions\n): JobFn<T> {\n const name = fn.name;\n\n if (!name) {\n throw new Error(\n `[tasklane] job() requires a named function. ` +\n `Use: job(async function myJobName(...) { ... }) — not an arrow function.`\n );\n }\n\n registerHandler(name, fn);\n\n // Extracted once — shared by all dispatch paths\n const attempts = opts?.attempts;\n const backoff: BackoffStrategy | undefined = opts?.backoff;\n\n const proxy = async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { attempts, backoff });\n };\n\n return Object.assign(proxy, {\n /** The registered job name — used by flow() to build the job tree */\n jobName: name,\n\n /** Run the handler directly, bypassing the queue entirely */\n run: (...args: Parameters<T>): ReturnType<T> => fn(...args) as ReturnType<T>,\n\n delay: (ms: number) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { delay: ms, attempts, backoff });\n },\n\n at: (date: Date) =>\n async (...args: Parameters<T>): Promise<void> => {\n const delay = date.getTime() - Date.now();\n if (delay < 0) throw new Error(`[tasklane] .at() date is in the past`);\n await enqueue(name, args, { delay, attempts, backoff });\n },\n\n cron: (pattern: string) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueueScheduled(name, pattern, args, { attempts, backoff });\n },\n }) as unknown as JobFn<T>;\n}\n\n/** The type returned by job() — same call signature plus scheduling helpers */\nexport type JobFn<T extends (...args: any[]) => Promise<any>> = {\n (...args: Parameters<T>): Promise<void>;\n /** The registered job name */\n readonly jobName: string;\n /** Run the handler directly, bypassing the queue entirely */\n run(...args: Parameters<T>): ReturnType<T>;\n /** Enqueue after a delay in milliseconds */\n delay(ms: number): (...args: Parameters<T>) => Promise<void>;\n /** Enqueue once at a specific Date */\n at(date: Date): (...args: Parameters<T>) => Promise<void>;\n /** Register a recurring cron schedule */\n cron(pattern: string): (...args: Parameters<T>) => Promise<void>;\n};\n","import {\n configure,\n startWorker as _startWorker,\n stopAll,\n onFailed,\n onCompleted,\n getFailed as _getFailed,\n retryJob as _retryJob,\n dispatchFlow,\n resetListeners,\n} from \"./registry.js\";\nimport type { JobsConfig, FailedEvent, CompletedEvent, FlowNode } from \"./types.js\";\n\nexport { job } from \"./job.js\";\nexport type { JobFn } from \"./job.js\";\nexport type {\n JobsConfig,\n JobFnOptions,\n FailedEvent,\n CompletedEvent,\n FailedJob,\n BackoffStrategy,\n FlowNode,\n} from \"./types.js\";\n\n/**\n * Initialize the jobs runtime. Call this once at app startup before\n * importing or calling any job functions.\n *\n * @example\n * initJobs({\n * redis: process.env.REDIS_URL,\n * attempts: 3,\n * backoff: { type: \"exponential\", delay: 1000 },\n * });\n */\nexport function initJobs(config: JobsConfig): void {\n configure(config);\n}\n\n/**\n * Start the worker process. Call after initJobs() and after importing\n * all files that define jobs.\n */\nexport async function startWorker(): Promise<void> {\n return _startWorker();\n}\n\n/**\n * Gracefully stop the worker, close all queue connections,\n * and clear all event listeners.\n */\nexport async function stopWorker(): Promise<void> {\n resetListeners();\n return stopAll();\n}\n\n/**\n * Listen for failed jobs. Fires on every failed attempt.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = onJobFailed((event) => {\n * if (event.isFinalFailure) alerting.send(event.name);\n * });\n * // later: unsub();\n */\nexport function onJobFailed(listener: (event: FailedEvent) => void): () => void {\n return onFailed(listener);\n}\n\n/**\n * Listen for completed jobs. Returns an unsubscribe function.\n */\nexport function onJobCompleted(listener: (event: CompletedEvent) => void): () => void {\n return onCompleted(listener);\n}\n\n/**\n * Get a list of permanently failed jobs (all retries exhausted).\n *\n * @param start - Pagination offset. Default: 0\n * @param limit - Max results. Default: 50\n */\nexport async function getFailed(start = 0, limit = 50) {\n return _getFailed(start, limit);\n}\n\n/**\n * Re-queue a permanently failed job by its ID.\n * The attempt count resets to zero.\n */\nexport async function retryJob(jobId: string): Promise<void> {\n return _retryJob(jobId);\n}\n\n/**\n * Dispatch a job flow — a parent job that runs only after all its\n * children complete. Supports unlimited nesting depth.\n *\n * Children run in parallel. The parent runs last.\n * If any child fails permanently, the parent is not executed.\n *\n * @example\n * await flow({\n * job: processOrder,\n * args: [\"ord_123\"],\n * children: [\n * { job: chargePayment, args: [\"ord_123\"] },\n * { job: reserveInventory, args: [\"ord_123\"] },\n * {\n * job: prepareShipment,\n * args: [\"ord_123\"],\n * children: [\n * { job: validateAddress, args: [\"ord_123\"] },\n * ],\n * },\n * ],\n * });\n */\nexport async function flow(node: FlowNode): Promise<void> {\n return dispatchFlow(node);\n}\n"]}
package/dist/index.d.cts CHANGED
@@ -4,9 +4,20 @@ interface BackoffStrategy {
4
4
  /** Base delay in milliseconds */
5
5
  delay: number;
6
6
  }
7
+ interface RedisOptions {
8
+ host: string;
9
+ port?: number;
10
+ password?: string;
11
+ username?: string;
12
+ db?: number;
13
+ }
7
14
  interface JobsConfig {
8
- /** Redis connection URL, e.g. "redis://127.0.0.1:6379" */
9
- redis: string;
15
+ /**
16
+ * Redis connection. Pass a URL string ("redis://host:6379") or an options
17
+ * object. Use the object form when your password contains special characters
18
+ * like `@` that would break URL parsing.
19
+ */
20
+ redis: string | RedisOptions;
10
21
  /** Queue name. Default: "default" */
11
22
  queue?: string;
12
23
  /** Default retry attempts for all jobs. Default: 3 */
package/dist/index.d.ts CHANGED
@@ -4,9 +4,20 @@ interface BackoffStrategy {
4
4
  /** Base delay in milliseconds */
5
5
  delay: number;
6
6
  }
7
+ interface RedisOptions {
8
+ host: string;
9
+ port?: number;
10
+ password?: string;
11
+ username?: string;
12
+ db?: number;
13
+ }
7
14
  interface JobsConfig {
8
- /** Redis connection URL, e.g. "redis://127.0.0.1:6379" */
9
- redis: string;
15
+ /**
16
+ * Redis connection. Pass a URL string ("redis://host:6379") or an options
17
+ * object. Use the object form when your password contains special characters
18
+ * like `@` that would break URL parsing.
19
+ */
20
+ redis: string | RedisOptions;
10
21
  /** Queue name. Default: "default" */
11
22
  queue?: string;
12
23
  /** Default retry attempts for all jobs. Default: 3 */
package/dist/index.js CHANGED
@@ -9,12 +9,22 @@ var _flowProducer = null;
9
9
  var _handlers = /* @__PURE__ */ new Map();
10
10
  var _failedListeners = [];
11
11
  var _completedListeners = [];
12
- function parseRedis(url) {
13
- const parsed = new URL(url);
12
+ function parseRedis(input) {
13
+ if (typeof input !== "string") {
14
+ return {
15
+ host: input.host,
16
+ port: input.port ?? 6379,
17
+ password: input.password,
18
+ username: input.username,
19
+ db: input.db ?? 0
20
+ };
21
+ }
22
+ const parsed = new URL(input);
14
23
  return {
15
24
  host: parsed.hostname || "127.0.0.1",
16
25
  port: parsed.port ? parseInt(parsed.port, 10) : 6379,
17
- password: parsed.password || void 0,
26
+ password: parsed.password ? decodeURIComponent(parsed.password) : void 0,
27
+ username: parsed.username ? decodeURIComponent(parsed.username) : void 0,
18
28
  db: parsed.pathname && parsed.pathname !== "/" ? parseInt(parsed.pathname.slice(1), 10) : 0
19
29
  };
20
30
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/registry.ts","../src/job.ts","../src/index.ts"],"names":["job","startWorker","getFailed","retryJob"],"mappings":"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","file":"index.js","sourcesContent":["import { Queue, Worker, FlowProducer } from \"bullmq\";\nimport type { ConnectionOptions, FlowJob } from \"bullmq\";\nimport type { JobsConfig, FailedEvent, CompletedEvent, FailedJob, BackoffStrategy, FlowNode } from \"./types.js\";\n\n// --- Global singleton state ---\n\nlet _config: JobsConfig | null = null;\nlet _connection: ConnectionOptions | null = null;\nlet _queue: Queue | null = null;\nlet _worker: Worker | null = null;\nlet _flowProducer: FlowProducer | null = null;\n\nconst _handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();\nconst _failedListeners: ((e: FailedEvent) => void)[] = [];\nconst _completedListeners: ((e: CompletedEvent) => void)[] = [];\n\n// --- Internal helpers ---\n\nfunction parseRedis(url: string): ConnectionOptions {\n const parsed = new URL(url);\n return {\n host: parsed.hostname || \"127.0.0.1\",\n port: parsed.port ? parseInt(parsed.port, 10) : 6379,\n password: parsed.password || undefined,\n db: parsed.pathname && parsed.pathname !== \"/\" ? parseInt(parsed.pathname.slice(1), 10) : 0,\n };\n}\n\nfunction requireQueue(): Queue {\n if (!_queue) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching jobs.`\n );\n }\n return _queue;\n}\n\nfunction requireFlowProducer(): FlowProducer {\n if (!_flowProducer) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching flows.`\n );\n }\n return _flowProducer;\n}\n\nfunction defaultBackoff(override?: BackoffStrategy) {\n return override ?? _config?.backoff ?? { type: \"exponential\" as const, delay: 1000 };\n}\n\nfunction defaultJobOpts(attempts?: number, backoff?: BackoffStrategy) {\n return {\n attempts: attempts ?? _config?.attempts ?? 3,\n backoff: defaultBackoff(backoff),\n removeOnComplete: { count: 1000 },\n removeOnFail: { count: 5000 },\n };\n}\n\nfunction toFlowJob(node: FlowNode): FlowJob {\n return {\n name: (node.job as { jobName: string }).jobName,\n queueName: _config?.queue ?? \"default\",\n data: { args: node.args ?? [] },\n opts: defaultJobOpts(node.attempts, node.backoff),\n children: node.children?.map(toFlowJob),\n };\n}\n\n// --- Public registry API ---\n\nexport function configure(cfg: JobsConfig): void {\n _config = cfg;\n _connection = parseRedis(cfg.redis);\n _queue = new Queue(cfg.queue ?? \"default\", { connection: _connection });\n _flowProducer = new FlowProducer({ connection: _connection });\n}\n\nexport function resetListeners(): void {\n _failedListeners.length = 0;\n _completedListeners.length = 0;\n}\n\nexport function registerHandler(\n name: string,\n fn: (...args: unknown[]) => Promise<unknown>\n): void {\n _handlers.set(name, fn);\n}\n\nexport async function enqueue(\n name: string,\n args: unknown[],\n opts?: { delay?: number; attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().add(name, { args }, {\n ...defaultJobOpts(opts?.attempts, opts?.backoff),\n delay: opts?.delay,\n });\n}\n\nexport async function enqueueScheduled(\n name: string,\n cronPattern: string,\n args: unknown[],\n opts?: { attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().upsertJobScheduler(\n `${name}::cron`,\n { pattern: cronPattern },\n {\n name,\n data: { args },\n opts: defaultJobOpts(opts?.attempts, opts?.backoff),\n }\n );\n}\n\nexport async function dispatchFlow(node: FlowNode): Promise<void> {\n await requireFlowProducer().add(toFlowJob(node));\n}\n\nexport async function startWorker(): Promise<void> {\n if (!_config || !_connection) {\n throw new Error(`[tasklane] initJobs() has not been called before startWorker().`);\n }\n if (_worker) return;\n\n _worker = new Worker(\n _config.queue ?? \"default\",\n async (bullJob) => {\n const handler = _handlers.get(bullJob.name);\n if (!handler) {\n throw new Error(\n `[tasklane] No handler registered for job \"${bullJob.name}\". ` +\n `Make sure the file defining this job is imported before startWorker() is called.`\n );\n }\n const args = (bullJob.data as { args: unknown[] }).args ?? [];\n return handler(...args);\n },\n { connection: _connection }\n );\n\n _worker.on(\"error\", (err) => {\n _failedListeners.forEach((fn) =>\n fn({\n jobId: \"worker\",\n name: \"worker-error\",\n args: [],\n error: err,\n attemptsMade: 0,\n isFinalFailure: false,\n })\n );\n });\n\n _worker.on(\"failed\", (bullJob, err) => {\n if (!bullJob) return;\n const isFinalFailure = bullJob.attemptsMade >= (bullJob.opts.attempts ?? 1);\n const event: FailedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n error: err,\n attemptsMade: bullJob.attemptsMade,\n isFinalFailure,\n };\n _failedListeners.forEach((fn) => fn(event));\n });\n\n _worker.on(\"completed\", (bullJob, result) => {\n const event: CompletedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n result,\n };\n _completedListeners.forEach((fn) => fn(event));\n });\n\n await _worker.waitUntilReady();\n}\n\nexport function onFailed(listener: (e: FailedEvent) => void): () => void {\n _failedListeners.push(listener);\n return () => {\n const i = _failedListeners.indexOf(listener);\n if (i !== -1) _failedListeners.splice(i, 1);\n };\n}\n\nexport function onCompleted(listener: (e: CompletedEvent) => void): () => void {\n _completedListeners.push(listener);\n return () => {\n const i = _completedListeners.indexOf(listener);\n if (i !== -1) _completedListeners.splice(i, 1);\n };\n}\n\nexport async function getFailed(start = 0, limit = 50): Promise<FailedJob[]> {\n const queue = requireQueue();\n const jobs = await queue.getFailed(start, start + limit - 1);\n return jobs.map((j) => ({\n jobId: j.id!,\n name: j.name,\n args: (j.data as { args: unknown[] }).args ?? [],\n failedReason: j.failedReason ?? \"Unknown error\",\n attemptsMade: j.attemptsMade,\n timestamp: j.timestamp,\n finishedOn: j.finishedOn,\n }));\n}\n\nexport async function retryJob(jobId: string): Promise<void> {\n const queue = requireQueue();\n const job = await queue.getJob(jobId);\n if (!job) {\n throw new Error(`[tasklane] retryJob: no job found with id \"${jobId}\"`);\n }\n await job.retry();\n}\n\nexport async function stopAll(): Promise<void> {\n await Promise.all([\n _worker?.close(),\n _queue?.close(),\n _flowProducer?.close(),\n ]);\n _worker = null;\n _queue = null;\n _flowProducer = null;\n _connection = null;\n _config = null;\n}\n","import { registerHandler, enqueue, enqueueScheduled } from \"./registry.js\";\nimport type { JobFnOptions, BackoffStrategy } from \"./types.js\";\n\n/**\n * Wraps an async function as a background job.\n *\n * The returned function has the same signature as the original — calling it\n * enqueues the job instead of running it inline.\n *\n * The function MUST be named (not an arrow function) so the package can\n * use its name as the job identifier.\n *\n * @example\n * export const sendSms = job(async function sendSms(to: string, message: string) {\n * await smsProvider.send({ to, message });\n * }, { attempts: 5, backoff: { type: \"fixed\", delay: 2000 } });\n *\n * // Enqueues immediately\n * await sendSms(\"Mushud\", \"Your OTP is 1234\");\n *\n * // Enqueue after a delay (ms)\n * await sendSms.delay(60_000)(\"Mushud\", \"Reminder\");\n *\n * // Enqueue once at a specific date\n * await sendSms.at(new Date(\"2026-04-13T09:00:00Z\"))(\"Mushud\", \"Good morning\");\n *\n * // Recurring cron schedule\n * await sendSms.cron(\"0 9 * * *\")(\"Mushud\", \"Daily reminder\");\n *\n * // Run directly without the queue\n * await sendSms.run(\"Mushud\", \"Inline\");\n */\nexport function job<T extends (...args: any[]) => Promise<any>>(\n fn: T,\n opts?: JobFnOptions\n): JobFn<T> {\n const name = fn.name;\n\n if (!name) {\n throw new Error(\n `[tasklane] job() requires a named function. ` +\n `Use: job(async function myJobName(...) { ... }) — not an arrow function.`\n );\n }\n\n registerHandler(name, fn);\n\n // Extracted once — shared by all dispatch paths\n const attempts = opts?.attempts;\n const backoff: BackoffStrategy | undefined = opts?.backoff;\n\n const proxy = async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { attempts, backoff });\n };\n\n return Object.assign(proxy, {\n /** The registered job name — used by flow() to build the job tree */\n jobName: name,\n\n /** Run the handler directly, bypassing the queue entirely */\n run: (...args: Parameters<T>): ReturnType<T> => fn(...args) as ReturnType<T>,\n\n delay: (ms: number) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { delay: ms, attempts, backoff });\n },\n\n at: (date: Date) =>\n async (...args: Parameters<T>): Promise<void> => {\n const delay = date.getTime() - Date.now();\n if (delay < 0) throw new Error(`[tasklane] .at() date is in the past`);\n await enqueue(name, args, { delay, attempts, backoff });\n },\n\n cron: (pattern: string) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueueScheduled(name, pattern, args, { attempts, backoff });\n },\n }) as unknown as JobFn<T>;\n}\n\n/** The type returned by job() — same call signature plus scheduling helpers */\nexport type JobFn<T extends (...args: any[]) => Promise<any>> = {\n (...args: Parameters<T>): Promise<void>;\n /** The registered job name */\n readonly jobName: string;\n /** Run the handler directly, bypassing the queue entirely */\n run(...args: Parameters<T>): ReturnType<T>;\n /** Enqueue after a delay in milliseconds */\n delay(ms: number): (...args: Parameters<T>) => Promise<void>;\n /** Enqueue once at a specific Date */\n at(date: Date): (...args: Parameters<T>) => Promise<void>;\n /** Register a recurring cron schedule */\n cron(pattern: string): (...args: Parameters<T>) => Promise<void>;\n};\n","import {\n configure,\n startWorker as _startWorker,\n stopAll,\n onFailed,\n onCompleted,\n getFailed as _getFailed,\n retryJob as _retryJob,\n dispatchFlow,\n resetListeners,\n} from \"./registry.js\";\nimport type { JobsConfig, FailedEvent, CompletedEvent, FlowNode } from \"./types.js\";\n\nexport { job } from \"./job.js\";\nexport type { JobFn } from \"./job.js\";\nexport type {\n JobsConfig,\n JobFnOptions,\n FailedEvent,\n CompletedEvent,\n FailedJob,\n BackoffStrategy,\n FlowNode,\n} from \"./types.js\";\n\n/**\n * Initialize the jobs runtime. Call this once at app startup before\n * importing or calling any job functions.\n *\n * @example\n * initJobs({\n * redis: process.env.REDIS_URL,\n * attempts: 3,\n * backoff: { type: \"exponential\", delay: 1000 },\n * });\n */\nexport function initJobs(config: JobsConfig): void {\n configure(config);\n}\n\n/**\n * Start the worker process. Call after initJobs() and after importing\n * all files that define jobs.\n */\nexport async function startWorker(): Promise<void> {\n return _startWorker();\n}\n\n/**\n * Gracefully stop the worker, close all queue connections,\n * and clear all event listeners.\n */\nexport async function stopWorker(): Promise<void> {\n resetListeners();\n return stopAll();\n}\n\n/**\n * Listen for failed jobs. Fires on every failed attempt.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = onJobFailed((event) => {\n * if (event.isFinalFailure) alerting.send(event.name);\n * });\n * // later: unsub();\n */\nexport function onJobFailed(listener: (event: FailedEvent) => void): () => void {\n return onFailed(listener);\n}\n\n/**\n * Listen for completed jobs. Returns an unsubscribe function.\n */\nexport function onJobCompleted(listener: (event: CompletedEvent) => void): () => void {\n return onCompleted(listener);\n}\n\n/**\n * Get a list of permanently failed jobs (all retries exhausted).\n *\n * @param start - Pagination offset. Default: 0\n * @param limit - Max results. Default: 50\n */\nexport async function getFailed(start = 0, limit = 50) {\n return _getFailed(start, limit);\n}\n\n/**\n * Re-queue a permanently failed job by its ID.\n * The attempt count resets to zero.\n */\nexport async function retryJob(jobId: string): Promise<void> {\n return _retryJob(jobId);\n}\n\n/**\n * Dispatch a job flow — a parent job that runs only after all its\n * children complete. Supports unlimited nesting depth.\n *\n * Children run in parallel. The parent runs last.\n * If any child fails permanently, the parent is not executed.\n *\n * @example\n * await flow({\n * job: processOrder,\n * args: [\"ord_123\"],\n * children: [\n * { job: chargePayment, args: [\"ord_123\"] },\n * { job: reserveInventory, args: [\"ord_123\"] },\n * {\n * job: prepareShipment,\n * args: [\"ord_123\"],\n * children: [\n * { job: validateAddress, args: [\"ord_123\"] },\n * ],\n * },\n * ],\n * });\n */\nexport async function flow(node: FlowNode): Promise<void> {\n return dispatchFlow(node);\n}\n"]}
1
+ {"version":3,"sources":["../src/registry.ts","../src/job.ts","../src/index.ts"],"names":["job","startWorker","getFailed","retryJob"],"mappings":"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","file":"index.js","sourcesContent":["import { Queue, Worker, FlowProducer } from \"bullmq\";\nimport type { ConnectionOptions, FlowJob } from \"bullmq\";\nimport type { JobsConfig, RedisOptions, FailedEvent, CompletedEvent, FailedJob, BackoffStrategy, FlowNode } from \"./types.js\";\n\n// --- Global singleton state ---\n\nlet _config: JobsConfig | null = null;\nlet _connection: ConnectionOptions | null = null;\nlet _queue: Queue | null = null;\nlet _worker: Worker | null = null;\nlet _flowProducer: FlowProducer | null = null;\n\nconst _handlers = new Map<string, (...args: unknown[]) => Promise<unknown>>();\nconst _failedListeners: ((e: FailedEvent) => void)[] = [];\nconst _completedListeners: ((e: CompletedEvent) => void)[] = [];\n\n// --- Internal helpers ---\n\nfunction parseRedis(input: string | RedisOptions): ConnectionOptions {\n if (typeof input !== \"string\") {\n return {\n host: input.host,\n port: input.port ?? 6379,\n password: input.password,\n username: input.username,\n db: input.db ?? 0,\n };\n }\n const parsed = new URL(input);\n return {\n host: parsed.hostname || \"127.0.0.1\",\n port: parsed.port ? parseInt(parsed.port, 10) : 6379,\n password: parsed.password ? decodeURIComponent(parsed.password) : undefined,\n username: parsed.username ? decodeURIComponent(parsed.username) : undefined,\n db: parsed.pathname && parsed.pathname !== \"/\" ? parseInt(parsed.pathname.slice(1), 10) : 0,\n };\n}\n\nfunction requireQueue(): Queue {\n if (!_queue) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching jobs.`\n );\n }\n return _queue;\n}\n\nfunction requireFlowProducer(): FlowProducer {\n if (!_flowProducer) {\n throw new Error(\n `[tasklane] initJobs() has not been called. Call initJobs({ redis }) before dispatching flows.`\n );\n }\n return _flowProducer;\n}\n\nfunction defaultBackoff(override?: BackoffStrategy) {\n return override ?? _config?.backoff ?? { type: \"exponential\" as const, delay: 1000 };\n}\n\nfunction defaultJobOpts(attempts?: number, backoff?: BackoffStrategy) {\n return {\n attempts: attempts ?? _config?.attempts ?? 3,\n backoff: defaultBackoff(backoff),\n removeOnComplete: { count: 1000 },\n removeOnFail: { count: 5000 },\n };\n}\n\nfunction toFlowJob(node: FlowNode): FlowJob {\n return {\n name: (node.job as { jobName: string }).jobName,\n queueName: _config?.queue ?? \"default\",\n data: { args: node.args ?? [] },\n opts: defaultJobOpts(node.attempts, node.backoff),\n children: node.children?.map(toFlowJob),\n };\n}\n\n// --- Public registry API ---\n\nexport function configure(cfg: JobsConfig): void {\n _config = cfg;\n _connection = parseRedis(cfg.redis);\n _queue = new Queue(cfg.queue ?? \"default\", { connection: _connection });\n _flowProducer = new FlowProducer({ connection: _connection });\n}\n\nexport function resetListeners(): void {\n _failedListeners.length = 0;\n _completedListeners.length = 0;\n}\n\nexport function registerHandler(\n name: string,\n fn: (...args: unknown[]) => Promise<unknown>\n): void {\n _handlers.set(name, fn);\n}\n\nexport async function enqueue(\n name: string,\n args: unknown[],\n opts?: { delay?: number; attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().add(name, { args }, {\n ...defaultJobOpts(opts?.attempts, opts?.backoff),\n delay: opts?.delay,\n });\n}\n\nexport async function enqueueScheduled(\n name: string,\n cronPattern: string,\n args: unknown[],\n opts?: { attempts?: number; backoff?: BackoffStrategy }\n): Promise<void> {\n await requireQueue().upsertJobScheduler(\n `${name}::cron`,\n { pattern: cronPattern },\n {\n name,\n data: { args },\n opts: defaultJobOpts(opts?.attempts, opts?.backoff),\n }\n );\n}\n\nexport async function dispatchFlow(node: FlowNode): Promise<void> {\n await requireFlowProducer().add(toFlowJob(node));\n}\n\nexport async function startWorker(): Promise<void> {\n if (!_config || !_connection) {\n throw new Error(`[tasklane] initJobs() has not been called before startWorker().`);\n }\n if (_worker) return;\n\n _worker = new Worker(\n _config.queue ?? \"default\",\n async (bullJob) => {\n const handler = _handlers.get(bullJob.name);\n if (!handler) {\n throw new Error(\n `[tasklane] No handler registered for job \"${bullJob.name}\". ` +\n `Make sure the file defining this job is imported before startWorker() is called.`\n );\n }\n const args = (bullJob.data as { args: unknown[] }).args ?? [];\n return handler(...args);\n },\n { connection: _connection }\n );\n\n _worker.on(\"error\", (err) => {\n _failedListeners.forEach((fn) =>\n fn({\n jobId: \"worker\",\n name: \"worker-error\",\n args: [],\n error: err,\n attemptsMade: 0,\n isFinalFailure: false,\n })\n );\n });\n\n _worker.on(\"failed\", (bullJob, err) => {\n if (!bullJob) return;\n const isFinalFailure = bullJob.attemptsMade >= (bullJob.opts.attempts ?? 1);\n const event: FailedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n error: err,\n attemptsMade: bullJob.attemptsMade,\n isFinalFailure,\n };\n _failedListeners.forEach((fn) => fn(event));\n });\n\n _worker.on(\"completed\", (bullJob, result) => {\n const event: CompletedEvent = {\n jobId: bullJob.id!,\n name: bullJob.name,\n args: (bullJob.data as { args: unknown[] }).args ?? [],\n result,\n };\n _completedListeners.forEach((fn) => fn(event));\n });\n\n await _worker.waitUntilReady();\n}\n\nexport function onFailed(listener: (e: FailedEvent) => void): () => void {\n _failedListeners.push(listener);\n return () => {\n const i = _failedListeners.indexOf(listener);\n if (i !== -1) _failedListeners.splice(i, 1);\n };\n}\n\nexport function onCompleted(listener: (e: CompletedEvent) => void): () => void {\n _completedListeners.push(listener);\n return () => {\n const i = _completedListeners.indexOf(listener);\n if (i !== -1) _completedListeners.splice(i, 1);\n };\n}\n\nexport async function getFailed(start = 0, limit = 50): Promise<FailedJob[]> {\n const queue = requireQueue();\n const jobs = await queue.getFailed(start, start + limit - 1);\n return jobs.map((j) => ({\n jobId: j.id!,\n name: j.name,\n args: (j.data as { args: unknown[] }).args ?? [],\n failedReason: j.failedReason ?? \"Unknown error\",\n attemptsMade: j.attemptsMade,\n timestamp: j.timestamp,\n finishedOn: j.finishedOn,\n }));\n}\n\nexport async function retryJob(jobId: string): Promise<void> {\n const queue = requireQueue();\n const job = await queue.getJob(jobId);\n if (!job) {\n throw new Error(`[tasklane] retryJob: no job found with id \"${jobId}\"`);\n }\n await job.retry();\n}\n\nexport async function stopAll(): Promise<void> {\n await Promise.all([\n _worker?.close(),\n _queue?.close(),\n _flowProducer?.close(),\n ]);\n _worker = null;\n _queue = null;\n _flowProducer = null;\n _connection = null;\n _config = null;\n}\n","import { registerHandler, enqueue, enqueueScheduled } from \"./registry.js\";\nimport type { JobFnOptions, BackoffStrategy } from \"./types.js\";\n\n/**\n * Wraps an async function as a background job.\n *\n * The returned function has the same signature as the original — calling it\n * enqueues the job instead of running it inline.\n *\n * The function MUST be named (not an arrow function) so the package can\n * use its name as the job identifier.\n *\n * @example\n * export const sendSms = job(async function sendSms(to: string, message: string) {\n * await smsProvider.send({ to, message });\n * }, { attempts: 5, backoff: { type: \"fixed\", delay: 2000 } });\n *\n * // Enqueues immediately\n * await sendSms(\"Mushud\", \"Your OTP is 1234\");\n *\n * // Enqueue after a delay (ms)\n * await sendSms.delay(60_000)(\"Mushud\", \"Reminder\");\n *\n * // Enqueue once at a specific date\n * await sendSms.at(new Date(\"2026-04-13T09:00:00Z\"))(\"Mushud\", \"Good morning\");\n *\n * // Recurring cron schedule\n * await sendSms.cron(\"0 9 * * *\")(\"Mushud\", \"Daily reminder\");\n *\n * // Run directly without the queue\n * await sendSms.run(\"Mushud\", \"Inline\");\n */\nexport function job<T extends (...args: any[]) => Promise<any>>(\n fn: T,\n opts?: JobFnOptions\n): JobFn<T> {\n const name = fn.name;\n\n if (!name) {\n throw new Error(\n `[tasklane] job() requires a named function. ` +\n `Use: job(async function myJobName(...) { ... }) — not an arrow function.`\n );\n }\n\n registerHandler(name, fn);\n\n // Extracted once — shared by all dispatch paths\n const attempts = opts?.attempts;\n const backoff: BackoffStrategy | undefined = opts?.backoff;\n\n const proxy = async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { attempts, backoff });\n };\n\n return Object.assign(proxy, {\n /** The registered job name — used by flow() to build the job tree */\n jobName: name,\n\n /** Run the handler directly, bypassing the queue entirely */\n run: (...args: Parameters<T>): ReturnType<T> => fn(...args) as ReturnType<T>,\n\n delay: (ms: number) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueue(name, args, { delay: ms, attempts, backoff });\n },\n\n at: (date: Date) =>\n async (...args: Parameters<T>): Promise<void> => {\n const delay = date.getTime() - Date.now();\n if (delay < 0) throw new Error(`[tasklane] .at() date is in the past`);\n await enqueue(name, args, { delay, attempts, backoff });\n },\n\n cron: (pattern: string) =>\n async (...args: Parameters<T>): Promise<void> => {\n await enqueueScheduled(name, pattern, args, { attempts, backoff });\n },\n }) as unknown as JobFn<T>;\n}\n\n/** The type returned by job() — same call signature plus scheduling helpers */\nexport type JobFn<T extends (...args: any[]) => Promise<any>> = {\n (...args: Parameters<T>): Promise<void>;\n /** The registered job name */\n readonly jobName: string;\n /** Run the handler directly, bypassing the queue entirely */\n run(...args: Parameters<T>): ReturnType<T>;\n /** Enqueue after a delay in milliseconds */\n delay(ms: number): (...args: Parameters<T>) => Promise<void>;\n /** Enqueue once at a specific Date */\n at(date: Date): (...args: Parameters<T>) => Promise<void>;\n /** Register a recurring cron schedule */\n cron(pattern: string): (...args: Parameters<T>) => Promise<void>;\n};\n","import {\n configure,\n startWorker as _startWorker,\n stopAll,\n onFailed,\n onCompleted,\n getFailed as _getFailed,\n retryJob as _retryJob,\n dispatchFlow,\n resetListeners,\n} from \"./registry.js\";\nimport type { JobsConfig, FailedEvent, CompletedEvent, FlowNode } from \"./types.js\";\n\nexport { job } from \"./job.js\";\nexport type { JobFn } from \"./job.js\";\nexport type {\n JobsConfig,\n JobFnOptions,\n FailedEvent,\n CompletedEvent,\n FailedJob,\n BackoffStrategy,\n FlowNode,\n} from \"./types.js\";\n\n/**\n * Initialize the jobs runtime. Call this once at app startup before\n * importing or calling any job functions.\n *\n * @example\n * initJobs({\n * redis: process.env.REDIS_URL,\n * attempts: 3,\n * backoff: { type: \"exponential\", delay: 1000 },\n * });\n */\nexport function initJobs(config: JobsConfig): void {\n configure(config);\n}\n\n/**\n * Start the worker process. Call after initJobs() and after importing\n * all files that define jobs.\n */\nexport async function startWorker(): Promise<void> {\n return _startWorker();\n}\n\n/**\n * Gracefully stop the worker, close all queue connections,\n * and clear all event listeners.\n */\nexport async function stopWorker(): Promise<void> {\n resetListeners();\n return stopAll();\n}\n\n/**\n * Listen for failed jobs. Fires on every failed attempt.\n * Returns an unsubscribe function.\n *\n * @example\n * const unsub = onJobFailed((event) => {\n * if (event.isFinalFailure) alerting.send(event.name);\n * });\n * // later: unsub();\n */\nexport function onJobFailed(listener: (event: FailedEvent) => void): () => void {\n return onFailed(listener);\n}\n\n/**\n * Listen for completed jobs. Returns an unsubscribe function.\n */\nexport function onJobCompleted(listener: (event: CompletedEvent) => void): () => void {\n return onCompleted(listener);\n}\n\n/**\n * Get a list of permanently failed jobs (all retries exhausted).\n *\n * @param start - Pagination offset. Default: 0\n * @param limit - Max results. Default: 50\n */\nexport async function getFailed(start = 0, limit = 50) {\n return _getFailed(start, limit);\n}\n\n/**\n * Re-queue a permanently failed job by its ID.\n * The attempt count resets to zero.\n */\nexport async function retryJob(jobId: string): Promise<void> {\n return _retryJob(jobId);\n}\n\n/**\n * Dispatch a job flow — a parent job that runs only after all its\n * children complete. Supports unlimited nesting depth.\n *\n * Children run in parallel. The parent runs last.\n * If any child fails permanently, the parent is not executed.\n *\n * @example\n * await flow({\n * job: processOrder,\n * args: [\"ord_123\"],\n * children: [\n * { job: chargePayment, args: [\"ord_123\"] },\n * { job: reserveInventory, args: [\"ord_123\"] },\n * {\n * job: prepareShipment,\n * args: [\"ord_123\"],\n * children: [\n * { job: validateAddress, args: [\"ord_123\"] },\n * ],\n * },\n * ],\n * });\n */\nexport async function flow(node: FlowNode): Promise<void> {\n return dispatchFlow(node);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tasklane",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Background jobs that feel like normal async functions. Built on top of BullMQ and Redis.",
5
5
  "author": {
6
6
  "name": "mushud",