@libeilong/mq 0.2.4 → 0.2.6
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.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -24,10 +24,10 @@ interface RetryOptions {
|
|
|
24
24
|
}
|
|
25
25
|
interface HandleOptions {
|
|
26
26
|
token: string;
|
|
27
|
-
retryLater(options?: RetryOptions):
|
|
28
|
-
retryLater(errorOrMessage?: Error | string):
|
|
29
|
-
retryLater(arg?: RetryOptions | Error | string):
|
|
30
|
-
abort(message?: string):
|
|
27
|
+
retryLater(options?: RetryOptions): Error;
|
|
28
|
+
retryLater(errorOrMessage?: Error | string): Error;
|
|
29
|
+
retryLater(arg?: RetryOptions | Error | string): Error;
|
|
30
|
+
abort(message?: string): Error;
|
|
31
31
|
}
|
|
32
32
|
interface BaseTaskOptions {
|
|
33
33
|
name?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -24,10 +24,10 @@ interface RetryOptions {
|
|
|
24
24
|
}
|
|
25
25
|
interface HandleOptions {
|
|
26
26
|
token: string;
|
|
27
|
-
retryLater(options?: RetryOptions):
|
|
28
|
-
retryLater(errorOrMessage?: Error | string):
|
|
29
|
-
retryLater(arg?: RetryOptions | Error | string):
|
|
30
|
-
abort(message?: string):
|
|
27
|
+
retryLater(options?: RetryOptions): Error;
|
|
28
|
+
retryLater(errorOrMessage?: Error | string): Error;
|
|
29
|
+
retryLater(arg?: RetryOptions | Error | string): Error;
|
|
30
|
+
abort(message?: string): Error;
|
|
31
31
|
}
|
|
32
32
|
interface BaseTaskOptions {
|
|
33
33
|
name?: string;
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`groupmq-plus`);c=s(c);var l=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}},u=class{get name(){return this.opts?.name||this.constructor.name}constructor(e,t){this.connection=e,this.opts=Object.assign({debug:!1,autoStart:!0,queueOptions:{},workerOptions:{}},t),this.opts.queueOptions=Object.assign({redis:e,namespace:this.name,keepCompleted:0,maxAttempts:3,jobTimeoutMs:5e3},this.opts.queueOptions),this.opts.workerOptions=Object.assign({concurrency:1,name:this.name},this.opts.workerOptions),this.queue=new c.Queue(this.opts.queueOptions),this.worker=new c.Worker({...this.opts.workerOptions,autoStart:this.opts.autoStart,queue:this.queue,handler:async e=>{try{return await this.handle(e,{token:e.token,retryLater:t=>{let n=e.attemptsMade,r=e.opts.attempts,i={};i=t instanceof Error?{error:t,message:t.message}:typeof t==`string`?{message:t}:t||{};let{error:a,delay:o}=i,s=i.message||(a instanceof Error?a.message:void 0)||`retry later`,c=a instanceof Error?a:void 0;if(n+1>=r)
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`groupmq-plus`);c=s(c);var l=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}},u=class{get name(){return this.opts?.name||this.constructor.name}constructor(e,t){this.connection=e,this.opts=Object.assign({debug:!1,autoStart:!0,queueOptions:{},workerOptions:{}},t),this.opts.queueOptions=Object.assign({redis:e,namespace:this.name,keepCompleted:0,maxAttempts:3,jobTimeoutMs:5e3},this.opts.queueOptions),this.opts.workerOptions=Object.assign({concurrency:1,name:this.name},this.opts.workerOptions),this.queue=new c.Queue(this.opts.queueOptions),this.worker=new c.Worker({...this.opts.workerOptions,autoStart:this.opts.autoStart,queue:this.queue,handler:async e=>{try{return await this.handle(e,{token:e.token,retryLater:t=>{let n=e.attemptsMade,r=e.opts.attempts,i={};i=t instanceof Error?{error:t,message:t.message}:typeof t==`string`?{message:t}:t||{};let{error:a,delay:o}=i,s=i.message||(a instanceof Error?a.message:void 0)||`retry later`,c=a instanceof Error?a:void 0;if(n+1>=r)return c||Error(s);let u=new l(s,o);return c&&(u.stack=c.stack,u.cause=c),u},abort:e=>new c.UnrecoverableError(e||`failed`)})}catch(e){throw e instanceof Error&&Object.defineProperty(e,`__isJobError`,{value:!0,enumerable:!1}),e}},backoff:(e,t)=>{if(t instanceof l&&typeof t.customDelay==`number`)return t.customDelay;let n=this.opts.workerOptions?.backoff;if(typeof n==`function`)return n(e,t);if(n&&typeof n==`object`){let{type:t,delay:r=1e3,jitter:i=0}=n,a;if(a=t===`fixed`?r:t===`exponential`?r*2**(e-1):1e3*2**(e-1),i>0){let e=a*i,t=Math.random()*e*2-e;a+=t}return Math.max(0,Math.round(a))}return 1e3*2**(e-1)}}),this.setupEventListeners()}setupEventListeners(){this.worker.on(`completed`,e=>{this.opts.debug&&console.log(`[COMPLETED] ${this.name}-${e.id} (Group: ${e.groupId})`),this.onCompleted(e)}),this.worker.on(`failed`,async e=>{let t=e.isFailed(),n=Error(e.failedReason||`task failed`);e.stacktrace&&(n.stack=e.stacktrace),this.opts.debug&&(t?console.error(`[FINAL FAILED] ${this.name}-${e.id}: ${n.message}`):console.log(`[RETRY] ${this.name}-${e.id}: ${n.message}`)),await this.onFailed(e,n),t&&await this.onFinalFailed(e,n)}),this.worker.on(`error`,e=>{e instanceof l||e instanceof Error&&e.__isJobError||this.opts.debug&&console.error(`[WORKER ERROR] ${this.name}`,e)})}async run(){return this.worker.run()}async onFailed(e,t){}async onFinalFailed(e,t){}async onCompleted(e){}setGroupConfig(e,t){return this.queue.groups.setConfig(e,t)}async start(e){let t;if(`children`in e&&Array.isArray(e.children)){if(!e.parent)throw Error(`Flow must include a parent job`);if(e.children.length===0)throw Error(`Flow must include at least one child job`);t=await this.queue.addFlow(e)}else t=await this.queue.add(e);return t.finished=async e=>t.waitUntilFinished(e),t}async close(){await this.worker.close()}};exports.BaseTask=u,Object.defineProperty(exports,`Job`,{enumerable:!0,get:function(){return c.Job}}),Object.defineProperty(exports,`PriorityStrategy`,{enumerable:!0,get:function(){return c.PriorityStrategy}}),Object.defineProperty(exports,`Queue`,{enumerable:!0,get:function(){return c.Queue}}),exports.TaskRetryLaterError=l,Object.defineProperty(exports,`UnrecoverableError`,{enumerable:!0,get:function(){return c.UnrecoverableError}}),Object.defineProperty(exports,`Worker`,{enumerable:!0,get:function(){return c.Worker}});
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["connection: IORedis","Queue","Worker","options: RetryOptions","UnrecoverableError","baseDelay: number","job: Job<D>"],"sources":["../src/BaseTask.ts"],"sourcesContent":["import {\n AddOptions,\n FlowJob,\n FlowOptions,\n GroupOptions,\n Job,\n Queue,\n QueueOptions,\n UnrecoverableError,\n Worker,\n WorkerOptions,\n} from 'groupmq-plus'\nimport IORedis from 'ioredis'\n\nexport type { FlowChildResult } from 'groupmq-plus'\n\nexport interface BackoffOptions {\n /** 延迟策略:固定间隔、指数递增 */\n type: 'fixed' | 'exponential'\n /** 延迟时间为毫秒 */\n delay?: number\n /**\n * 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。\n * 抖动用于增加随机性,以防止因多个任务同时重试而导致的惊群效应。\n * 最终的延迟时间将在 `delay ± (delay * jitter)` 的范围内随机波动。\n * @defaultValue 0\n * @example 0.1 // 为延迟添加 ±10% 的随机抖动\n * @example 0.25 // 为延迟添加 ±25% 的随机抖动\n */\n jitter?: number\n}\n\nexport interface RetryOptions {\n error?: Error | unknown\n message?: string\n delay?: number\n}\n\nexport interface HandleOptions {\n token: string\n retryLater(options?: RetryOptions): void\n retryLater(errorOrMessage?: Error | string): void\n retryLater(arg?: RetryOptions | Error | string): void\n abort(message?: string): void\n}\n\nexport interface BaseTaskOptions {\n name?: string\n autoStart?: boolean\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n debug?: boolean\n}\n\n/**\n * 任务启动选项,用于单个任务或 Flow 中的子任务\n */\nexport type TaskStartOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n delay?: number\n groupConfig?: GroupOptions\n} & Omit<AddOptions<D>, 'groupId' | 'data' | 'groupConfig'>\n\n/**\n * Flow 任务选项\n */\nexport type TaskFlowOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n children: Array<\n {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n } & Omit<FlowJob<D>, 'groupId' | 'data'>\n >\n} & Omit<FlowJob<D>, 'groupId' | 'data'>\n\nexport class TaskRetryLaterError extends Error {\n public customDelay?: number\n\n constructor(message?: string, delay?: number) {\n super(message)\n this.name = 'TaskRetryLaterError'\n this.customDelay = delay\n }\n}\n\nexport abstract class BaseTask<D = any> {\n public queue: Queue<D>\n public worker: Worker<D>\n protected opts: BaseTaskOptions\n\n get name() {\n return this.opts?.name || this.constructor.name\n }\n\n constructor(protected connection: IORedis, opts?: Partial<BaseTaskOptions>) {\n this.opts = Object.assign(\n {\n debug: false,\n autoStart: true,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n keepCompleted: 0,\n maxAttempts: 3,\n jobTimeoutMs: 5000,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign(\n {\n concurrency: 1,\n name: this.name,\n },\n this.opts.workerOptions,\n )\n\n this.queue = new Queue<D>(this.opts.queueOptions as QueueOptions)\n\n this.worker = new Worker<D>({\n ...(this.opts.workerOptions as WorkerOptions<D>),\n autoStart: this.opts.autoStart,\n queue: this.queue,\n handler: async (job) => {\n // [优化] 包裹 try-catch 以便给业务错误打标签\n try {\n return await this.handle(job, {\n token: job.token!,\n retryLater: (arg: RetryOptions | Error | string) => {\n const currentAttempts = job.attemptsMade\n const maxAttempts = job.opts.attempts\n\n let options: RetryOptions = {}\n if (arg instanceof Error) {\n options = { error: arg, message: arg.message }\n } else if (typeof arg === 'string') {\n options = { message: arg }\n } else {\n options = arg || {}\n }\n\n const { error, delay } = options\n const message = options.message || (error instanceof Error ? error.message : undefined) || 'retry later'\n const errorObj = error instanceof Error ? error : undefined\n\n if (currentAttempts + 1 >= maxAttempts) {\n if (errorObj) throw errorObj\n throw new Error(message)\n }\n\n const retryErr = new TaskRetryLaterError(message, delay)\n if (errorObj) {\n retryErr.stack = errorObj.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n retryErr.cause = errorObj\n }\n throw retryErr\n },\n abort: (message?: string) => {\n throw new UnrecoverableError(message || 'failed')\n },\n })\n } catch (err) {\n // [优化] 标记这个错误是来自业务逻辑的\n // 这样在 error 事件监听器中可以识别并忽略打印,因为 failed 事件已经处理了日志\n if (err instanceof Error) {\n Object.defineProperty(err, '__isJobError', { value: true, enumerable: false })\n }\n throw err\n }\n },\n backoff: (attempt: number, error: unknown) => {\n if (error instanceof TaskRetryLaterError && typeof error.customDelay === 'number') {\n return error.customDelay\n }\n\n const backoffConfig = this.opts.workerOptions?.backoff\n\n if (typeof backoffConfig === 'function') {\n return backoffConfig(attempt, error)\n }\n\n if (backoffConfig && typeof backoffConfig === 'object') {\n const { type, delay = 1000, jitter = 0 } = backoffConfig\n let baseDelay: number\n\n if (type === 'fixed') {\n baseDelay = delay\n } else if (type === 'exponential') {\n baseDelay = delay * Math.pow(2, attempt - 1)\n } else {\n baseDelay = 1000 * Math.pow(2, attempt - 1)\n }\n\n if (jitter > 0) {\n const jitterAmount = baseDelay * jitter\n const randomJitter = Math.random() * jitterAmount * 2 - jitterAmount\n baseDelay = baseDelay + randomJitter\n }\n\n return Math.max(0, Math.round(baseDelay))\n }\n\n return 1000 * Math.pow(2, attempt - 1)\n },\n })\n\n this.setupEventListeners()\n }\n\n private setupEventListeners() {\n this.worker.on('completed', (job) => {\n if (this.opts.debug) {\n console.log(`[COMPLETED] ${this.name}-${job.id} (Group: ${job.groupId})`)\n }\n this.onCompleted(job)\n })\n\n this.worker.on('failed', async (job) => {\n const isFinalFailure = job.isFailed()\n const error = new Error(job.failedReason || 'task failed')\n if (job.stacktrace) {\n error.stack = job.stacktrace\n }\n\n if (this.opts.debug) {\n // failed 事件负责打印业务重试日志,这通常更简洁、更有语义\n if (!isFinalFailure) {\n console.log(`[RETRY] ${this.name}-${job.id}: ${error.message}`)\n } else {\n console.error(`[FINAL FAILED] ${this.name}-${job.id}: ${error.message}`)\n }\n }\n\n await this.onFailed(job, error)\n\n if (isFinalFailure) {\n await this.onFinalFailed(job, error)\n }\n })\n\n this.worker.on('error', (err) => {\n // 1. 忽略 TaskRetryLaterError (这是流程控制信号,不是真正的错误)\n if (err instanceof TaskRetryLaterError) return\n\n // 2. [优化] 忽略被标记为业务逻辑错误的 Error\n // 因为这些错误已经在 failed 事件中作为 [RETRY] 或 [FINAL FAILED] 打印了\n // 我们只希望在这里看到真正的系统级错误(如 Redis 连接失败、Worker 内部崩溃等)\n if (err instanceof Error && (err as any).__isJobError) return\n\n if (this.opts.debug) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\n }\n\n async run() {\n return this.worker.run()\n }\n\n abstract handle(job: Job<D>, opts: HandleOptions): Promise<any>\n\n async onFailed(job: Job<D>, error: Error) {}\n async onFinalFailed(job: Job<D>, error: Error) {}\n async onCompleted(job: Job<D>) {}\n\n setGroupConfig(groupId: string, config: { priority?: number; concurrency?: number }) {\n return this.queue.groups.setConfig(groupId, config)\n }\n\n async start(opts: AddOptions<D> | FlowOptions<D, D>) {\n let job: Job<D>\n\n if ('children' in opts && Array.isArray(opts.children)) {\n if (!opts.parent) {\n throw new Error('Flow must include a parent job')\n }\n\n if (opts.children.length === 0) {\n throw new Error('Flow must include at least one child job')\n }\n\n job = await this.queue.addFlow(opts)\n } else {\n job = await this.queue.add(opts as AddOptions<D>)\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n job['finished'] = async (timeoutMs?: number) => {\n return job.waitUntilFinished(timeoutMs)\n }\n\n return job as Job<D> & { finished: (timeoutMs?: number) => Promise<any> }\n }\n\n async close() {\n await this.worker.close()\n }\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["connection: IORedis","Queue","Worker","options: RetryOptions","UnrecoverableError","baseDelay: number","job: Job<D>"],"sources":["../src/BaseTask.ts"],"sourcesContent":["import {\n AddOptions,\n FlowJob,\n FlowOptions,\n GroupOptions,\n Job,\n Queue,\n QueueOptions,\n UnrecoverableError,\n Worker,\n WorkerOptions,\n} from 'groupmq-plus'\nimport IORedis from 'ioredis'\n\nexport type { FlowChildResult } from 'groupmq-plus'\n\nexport interface BackoffOptions {\n /** 延迟策略:固定间隔、指数递增 */\n type: 'fixed' | 'exponential'\n /** 延迟时间为毫秒 */\n delay?: number\n /**\n * 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。\n * 抖动用于增加随机性,以防止因多个任务同时重试而导致的惊群效应。\n * 最终的延迟时间将在 `delay ± (delay * jitter)` 的范围内随机波动。\n * @defaultValue 0\n * @example 0.1 // 为延迟添加 ±10% 的随机抖动\n * @example 0.25 // 为延迟添加 ±25% 的随机抖动\n */\n jitter?: number\n}\n\nexport interface RetryOptions {\n error?: Error | unknown\n message?: string\n delay?: number\n}\n\n// [修复] 修改返回值类型为 Error,否则外层无法使用 throw opts.retryLater(...)\nexport interface HandleOptions {\n token: string\n retryLater(options?: RetryOptions): Error\n retryLater(errorOrMessage?: Error | string): Error\n retryLater(arg?: RetryOptions | Error | string): Error\n abort(message?: string): Error\n}\n\nexport interface BaseTaskOptions {\n name?: string\n autoStart?: boolean\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n debug?: boolean\n}\n\n/**\n * 任务启动选项,用于单个任务或 Flow 中的子任务\n */\nexport type TaskStartOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n delay?: number\n groupConfig?: GroupOptions\n} & Omit<AddOptions<D>, 'groupId' | 'data' | 'groupConfig'>\n\n/**\n * Flow 任务选项\n */\nexport type TaskFlowOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n children: Array<\n {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n } & Omit<FlowJob<D>, 'groupId' | 'data'>\n >\n} & Omit<FlowJob<D>, 'groupId' | 'data'>\n\nexport class TaskRetryLaterError extends Error {\n public customDelay?: number\n\n constructor(message?: string, delay?: number) {\n super(message)\n this.name = 'TaskRetryLaterError'\n this.customDelay = delay\n }\n}\n\nexport abstract class BaseTask<D = any> {\n public queue: Queue<D>\n public worker: Worker<D>\n protected opts: BaseTaskOptions\n\n get name() {\n return this.opts?.name || this.constructor.name\n }\n\n constructor(protected connection: IORedis, opts?: Partial<BaseTaskOptions>) {\n this.opts = Object.assign(\n {\n debug: false,\n autoStart: true,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n keepCompleted: 0,\n maxAttempts: 3,\n jobTimeoutMs: 5000,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign(\n {\n concurrency: 1,\n name: this.name,\n },\n this.opts.workerOptions,\n )\n\n this.queue = new Queue<D>(this.opts.queueOptions as QueueOptions)\n\n this.worker = new Worker<D>({\n ...(this.opts.workerOptions as WorkerOptions<D>),\n autoStart: this.opts.autoStart,\n queue: this.queue,\n handler: async (job) => {\n try {\n return await this.handle(job, {\n token: job.token!,\n retryLater: (arg: RetryOptions | Error | string): Error => {\n const currentAttempts = job.attemptsMade\n const maxAttempts = job.opts.attempts\n\n let options: RetryOptions = {}\n if (arg instanceof Error) {\n options = { error: arg, message: arg.message }\n } else if (typeof arg === 'string') {\n options = { message: arg }\n } else {\n options = arg || {}\n }\n\n const { error, delay } = options\n const message = options.message || (error instanceof Error ? error.message : undefined) || 'retry later'\n const errorObj = error instanceof Error ? error : undefined\n\n if (currentAttempts + 1 >= maxAttempts) {\n // [修改] 返回错误而不是抛出\n if (errorObj) return errorObj\n return new Error(message)\n }\n\n const retryErr = new TaskRetryLaterError(message, delay)\n if (errorObj) {\n retryErr.stack = errorObj.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n retryErr.cause = errorObj\n }\n // [修改] 返回错误\n return retryErr\n },\n abort: (message?: string) => {\n // [修改] 返回错误\n return new UnrecoverableError(message || 'failed')\n },\n })\n } catch (err) {\n if (err instanceof Error) {\n Object.defineProperty(err, '__isJobError', { value: true, enumerable: false })\n }\n throw err\n }\n },\n backoff: (attempt: number, error: unknown) => {\n if (error instanceof TaskRetryLaterError && typeof error.customDelay === 'number') {\n return error.customDelay\n }\n\n const backoffConfig = this.opts.workerOptions?.backoff\n\n if (typeof backoffConfig === 'function') {\n return backoffConfig(attempt, error)\n }\n\n if (backoffConfig && typeof backoffConfig === 'object') {\n const { type, delay = 1000, jitter = 0 } = backoffConfig\n let baseDelay: number\n\n if (type === 'fixed') {\n baseDelay = delay\n } else if (type === 'exponential') {\n baseDelay = delay * Math.pow(2, attempt - 1)\n } else {\n baseDelay = 1000 * Math.pow(2, attempt - 1)\n }\n\n if (jitter > 0) {\n const jitterAmount = baseDelay * jitter\n const randomJitter = Math.random() * jitterAmount * 2 - jitterAmount\n baseDelay = baseDelay + randomJitter\n }\n\n return Math.max(0, Math.round(baseDelay))\n }\n\n return 1000 * Math.pow(2, attempt - 1)\n },\n })\n\n this.setupEventListeners()\n }\n\n private setupEventListeners() {\n this.worker.on('completed', (job) => {\n if (this.opts.debug) {\n console.log(`[COMPLETED] ${this.name}-${job.id} (Group: ${job.groupId})`)\n }\n this.onCompleted(job)\n })\n\n this.worker.on('failed', async (job) => {\n const isFinalFailure = job.isFailed()\n const error = new Error(job.failedReason || 'task failed')\n if (job.stacktrace) {\n error.stack = job.stacktrace\n }\n\n if (this.opts.debug) {\n if (!isFinalFailure) {\n console.log(`[RETRY] ${this.name}-${job.id}: ${error.message}`)\n } else {\n console.error(`[FINAL FAILED] ${this.name}-${job.id}: ${error.message}`)\n }\n }\n\n await this.onFailed(job, error)\n\n if (isFinalFailure) {\n await this.onFinalFailed(job, error)\n }\n })\n\n this.worker.on('error', (err) => {\n if (err instanceof TaskRetryLaterError) return\n if (err instanceof Error && (err as any).__isJobError) return\n\n if (this.opts.debug) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\n }\n\n async run() {\n return this.worker.run()\n }\n\n abstract handle(job: Job<D>, opts: HandleOptions): Promise<any>\n\n async onFailed(job: Job<D>, error: Error) {}\n async onFinalFailed(job: Job<D>, error: Error) {}\n async onCompleted(job: Job<D>) {}\n\n setGroupConfig(groupId: string, config: { priority?: number; concurrency?: number }) {\n return this.queue.groups.setConfig(groupId, config)\n }\n\n async start(opts: AddOptions<D> | FlowOptions<D, D>) {\n let job: Job<D>\n\n if ('children' in opts && Array.isArray(opts.children)) {\n if (!opts.parent) {\n throw new Error('Flow must include a parent job')\n }\n\n if (opts.children.length === 0) {\n throw new Error('Flow must include at least one child job')\n }\n\n job = await this.queue.addFlow(opts)\n } else {\n job = await this.queue.add(opts as AddOptions<D>)\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n job['finished'] = async (timeoutMs?: number) => {\n return job.waitUntilFinished(timeoutMs)\n }\n\n return job as Job<D> & { finished: (timeoutMs?: number) => Promise<any> }\n }\n\n async close() {\n await this.worker.close()\n }\n}"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{Job as e,PriorityStrategy as t,Queue as n,Queue as r,UnrecoverableError as i,UnrecoverableError as a,Worker as o,Worker as s}from"groupmq-plus";var c=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}},l=class{get name(){return this.opts?.name||this.constructor.name}constructor(e,t){this.connection=e,this.opts=Object.assign({debug:!1,autoStart:!0,queueOptions:{},workerOptions:{}},t),this.opts.queueOptions=Object.assign({redis:e,namespace:this.name,keepCompleted:0,maxAttempts:3,jobTimeoutMs:5e3},this.opts.queueOptions),this.opts.workerOptions=Object.assign({concurrency:1,name:this.name},this.opts.workerOptions),this.queue=new r(this.opts.queueOptions),this.worker=new s({...this.opts.workerOptions,autoStart:this.opts.autoStart,queue:this.queue,handler:async e=>{try{return await this.handle(e,{token:e.token,retryLater:t=>{let n=e.attemptsMade,r=e.opts.attempts,i={};i=t instanceof Error?{error:t,message:t.message}:typeof t==`string`?{message:t}:t||{};let{error:a,delay:o}=i,s=i.message||(a instanceof Error?a.message:void 0)||`retry later`,l=a instanceof Error?a:void 0;if(n+1>=r)
|
|
1
|
+
import{Job as e,PriorityStrategy as t,Queue as n,Queue as r,UnrecoverableError as i,UnrecoverableError as a,Worker as o,Worker as s}from"groupmq-plus";var c=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}},l=class{get name(){return this.opts?.name||this.constructor.name}constructor(e,t){this.connection=e,this.opts=Object.assign({debug:!1,autoStart:!0,queueOptions:{},workerOptions:{}},t),this.opts.queueOptions=Object.assign({redis:e,namespace:this.name,keepCompleted:0,maxAttempts:3,jobTimeoutMs:5e3},this.opts.queueOptions),this.opts.workerOptions=Object.assign({concurrency:1,name:this.name},this.opts.workerOptions),this.queue=new r(this.opts.queueOptions),this.worker=new s({...this.opts.workerOptions,autoStart:this.opts.autoStart,queue:this.queue,handler:async e=>{try{return await this.handle(e,{token:e.token,retryLater:t=>{let n=e.attemptsMade,r=e.opts.attempts,i={};i=t instanceof Error?{error:t,message:t.message}:typeof t==`string`?{message:t}:t||{};let{error:a,delay:o}=i,s=i.message||(a instanceof Error?a.message:void 0)||`retry later`,l=a instanceof Error?a:void 0;if(n+1>=r)return l||Error(s);let u=new c(s,o);return l&&(u.stack=l.stack,u.cause=l),u},abort:e=>new a(e||`failed`)})}catch(e){throw e instanceof Error&&Object.defineProperty(e,`__isJobError`,{value:!0,enumerable:!1}),e}},backoff:(e,t)=>{if(t instanceof c&&typeof t.customDelay==`number`)return t.customDelay;let n=this.opts.workerOptions?.backoff;if(typeof n==`function`)return n(e,t);if(n&&typeof n==`object`){let{type:t,delay:r=1e3,jitter:i=0}=n,a;if(a=t===`fixed`?r:t===`exponential`?r*2**(e-1):1e3*2**(e-1),i>0){let e=a*i,t=Math.random()*e*2-e;a+=t}return Math.max(0,Math.round(a))}return 1e3*2**(e-1)}}),this.setupEventListeners()}setupEventListeners(){this.worker.on(`completed`,e=>{this.opts.debug&&console.log(`[COMPLETED] ${this.name}-${e.id} (Group: ${e.groupId})`),this.onCompleted(e)}),this.worker.on(`failed`,async e=>{let t=e.isFailed(),n=Error(e.failedReason||`task failed`);e.stacktrace&&(n.stack=e.stacktrace),this.opts.debug&&(t?console.error(`[FINAL FAILED] ${this.name}-${e.id}: ${n.message}`):console.log(`[RETRY] ${this.name}-${e.id}: ${n.message}`)),await this.onFailed(e,n),t&&await this.onFinalFailed(e,n)}),this.worker.on(`error`,e=>{e instanceof c||e instanceof Error&&e.__isJobError||this.opts.debug&&console.error(`[WORKER ERROR] ${this.name}`,e)})}async run(){return this.worker.run()}async onFailed(e,t){}async onFinalFailed(e,t){}async onCompleted(e){}setGroupConfig(e,t){return this.queue.groups.setConfig(e,t)}async start(e){let t;if(`children`in e&&Array.isArray(e.children)){if(!e.parent)throw Error(`Flow must include a parent job`);if(e.children.length===0)throw Error(`Flow must include at least one child job`);t=await this.queue.addFlow(e)}else t=await this.queue.add(e);return t.finished=async e=>t.waitUntilFinished(e),t}async close(){await this.worker.close()}};export{l as BaseTask,e as Job,t as PriorityStrategy,n as Queue,c as TaskRetryLaterError,i as UnrecoverableError,o as Worker};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["connection: IORedis","Queue","Worker","options: RetryOptions","UnrecoverableError","baseDelay: number","job: Job<D>"],"sources":["../src/BaseTask.ts"],"sourcesContent":["import {\n AddOptions,\n FlowJob,\n FlowOptions,\n GroupOptions,\n Job,\n Queue,\n QueueOptions,\n UnrecoverableError,\n Worker,\n WorkerOptions,\n} from 'groupmq-plus'\nimport IORedis from 'ioredis'\n\nexport type { FlowChildResult } from 'groupmq-plus'\n\nexport interface BackoffOptions {\n /** 延迟策略:固定间隔、指数递增 */\n type: 'fixed' | 'exponential'\n /** 延迟时间为毫秒 */\n delay?: number\n /**\n * 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。\n * 抖动用于增加随机性,以防止因多个任务同时重试而导致的惊群效应。\n * 最终的延迟时间将在 `delay ± (delay * jitter)` 的范围内随机波动。\n * @defaultValue 0\n * @example 0.1 // 为延迟添加 ±10% 的随机抖动\n * @example 0.25 // 为延迟添加 ±25% 的随机抖动\n */\n jitter?: number\n}\n\nexport interface RetryOptions {\n error?: Error | unknown\n message?: string\n delay?: number\n}\n\nexport interface HandleOptions {\n token: string\n retryLater(options?: RetryOptions): void\n retryLater(errorOrMessage?: Error | string): void\n retryLater(arg?: RetryOptions | Error | string): void\n abort(message?: string): void\n}\n\nexport interface BaseTaskOptions {\n name?: string\n autoStart?: boolean\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n debug?: boolean\n}\n\n/**\n * 任务启动选项,用于单个任务或 Flow 中的子任务\n */\nexport type TaskStartOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n delay?: number\n groupConfig?: GroupOptions\n} & Omit<AddOptions<D>, 'groupId' | 'data' | 'groupConfig'>\n\n/**\n * Flow 任务选项\n */\nexport type TaskFlowOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n children: Array<\n {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n } & Omit<FlowJob<D>, 'groupId' | 'data'>\n >\n} & Omit<FlowJob<D>, 'groupId' | 'data'>\n\nexport class TaskRetryLaterError extends Error {\n public customDelay?: number\n\n constructor(message?: string, delay?: number) {\n super(message)\n this.name = 'TaskRetryLaterError'\n this.customDelay = delay\n }\n}\n\nexport abstract class BaseTask<D = any> {\n public queue: Queue<D>\n public worker: Worker<D>\n protected opts: BaseTaskOptions\n\n get name() {\n return this.opts?.name || this.constructor.name\n }\n\n constructor(protected connection: IORedis, opts?: Partial<BaseTaskOptions>) {\n this.opts = Object.assign(\n {\n debug: false,\n autoStart: true,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n keepCompleted: 0,\n maxAttempts: 3,\n jobTimeoutMs: 5000,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign(\n {\n concurrency: 1,\n name: this.name,\n },\n this.opts.workerOptions,\n )\n\n this.queue = new Queue<D>(this.opts.queueOptions as QueueOptions)\n\n this.worker = new Worker<D>({\n ...(this.opts.workerOptions as WorkerOptions<D>),\n autoStart: this.opts.autoStart,\n queue: this.queue,\n handler: async (job) => {\n // [优化] 包裹 try-catch 以便给业务错误打标签\n try {\n return await this.handle(job, {\n token: job.token!,\n retryLater: (arg: RetryOptions | Error | string) => {\n const currentAttempts = job.attemptsMade\n const maxAttempts = job.opts.attempts\n\n let options: RetryOptions = {}\n if (arg instanceof Error) {\n options = { error: arg, message: arg.message }\n } else if (typeof arg === 'string') {\n options = { message: arg }\n } else {\n options = arg || {}\n }\n\n const { error, delay } = options\n const message = options.message || (error instanceof Error ? error.message : undefined) || 'retry later'\n const errorObj = error instanceof Error ? error : undefined\n\n if (currentAttempts + 1 >= maxAttempts) {\n if (errorObj) throw errorObj\n throw new Error(message)\n }\n\n const retryErr = new TaskRetryLaterError(message, delay)\n if (errorObj) {\n retryErr.stack = errorObj.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n retryErr.cause = errorObj\n }\n throw retryErr\n },\n abort: (message?: string) => {\n throw new UnrecoverableError(message || 'failed')\n },\n })\n } catch (err) {\n // [优化] 标记这个错误是来自业务逻辑的\n // 这样在 error 事件监听器中可以识别并忽略打印,因为 failed 事件已经处理了日志\n if (err instanceof Error) {\n Object.defineProperty(err, '__isJobError', { value: true, enumerable: false })\n }\n throw err\n }\n },\n backoff: (attempt: number, error: unknown) => {\n if (error instanceof TaskRetryLaterError && typeof error.customDelay === 'number') {\n return error.customDelay\n }\n\n const backoffConfig = this.opts.workerOptions?.backoff\n\n if (typeof backoffConfig === 'function') {\n return backoffConfig(attempt, error)\n }\n\n if (backoffConfig && typeof backoffConfig === 'object') {\n const { type, delay = 1000, jitter = 0 } = backoffConfig\n let baseDelay: number\n\n if (type === 'fixed') {\n baseDelay = delay\n } else if (type === 'exponential') {\n baseDelay = delay * Math.pow(2, attempt - 1)\n } else {\n baseDelay = 1000 * Math.pow(2, attempt - 1)\n }\n\n if (jitter > 0) {\n const jitterAmount = baseDelay * jitter\n const randomJitter = Math.random() * jitterAmount * 2 - jitterAmount\n baseDelay = baseDelay + randomJitter\n }\n\n return Math.max(0, Math.round(baseDelay))\n }\n\n return 1000 * Math.pow(2, attempt - 1)\n },\n })\n\n this.setupEventListeners()\n }\n\n private setupEventListeners() {\n this.worker.on('completed', (job) => {\n if (this.opts.debug) {\n console.log(`[COMPLETED] ${this.name}-${job.id} (Group: ${job.groupId})`)\n }\n this.onCompleted(job)\n })\n\n this.worker.on('failed', async (job) => {\n const isFinalFailure = job.isFailed()\n const error = new Error(job.failedReason || 'task failed')\n if (job.stacktrace) {\n error.stack = job.stacktrace\n }\n\n if (this.opts.debug) {\n // failed 事件负责打印业务重试日志,这通常更简洁、更有语义\n if (!isFinalFailure) {\n console.log(`[RETRY] ${this.name}-${job.id}: ${error.message}`)\n } else {\n console.error(`[FINAL FAILED] ${this.name}-${job.id}: ${error.message}`)\n }\n }\n\n await this.onFailed(job, error)\n\n if (isFinalFailure) {\n await this.onFinalFailed(job, error)\n }\n })\n\n this.worker.on('error', (err) => {\n // 1. 忽略 TaskRetryLaterError (这是流程控制信号,不是真正的错误)\n if (err instanceof TaskRetryLaterError) return\n\n // 2. [优化] 忽略被标记为业务逻辑错误的 Error\n // 因为这些错误已经在 failed 事件中作为 [RETRY] 或 [FINAL FAILED] 打印了\n // 我们只希望在这里看到真正的系统级错误(如 Redis 连接失败、Worker 内部崩溃等)\n if (err instanceof Error && (err as any).__isJobError) return\n\n if (this.opts.debug) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\n }\n\n async run() {\n return this.worker.run()\n }\n\n abstract handle(job: Job<D>, opts: HandleOptions): Promise<any>\n\n async onFailed(job: Job<D>, error: Error) {}\n async onFinalFailed(job: Job<D>, error: Error) {}\n async onCompleted(job: Job<D>) {}\n\n setGroupConfig(groupId: string, config: { priority?: number; concurrency?: number }) {\n return this.queue.groups.setConfig(groupId, config)\n }\n\n async start(opts: AddOptions<D> | FlowOptions<D, D>) {\n let job: Job<D>\n\n if ('children' in opts && Array.isArray(opts.children)) {\n if (!opts.parent) {\n throw new Error('Flow must include a parent job')\n }\n\n if (opts.children.length === 0) {\n throw new Error('Flow must include at least one child job')\n }\n\n job = await this.queue.addFlow(opts)\n } else {\n job = await this.queue.add(opts as AddOptions<D>)\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n job['finished'] = async (timeoutMs?: number) => {\n return job.waitUntilFinished(timeoutMs)\n }\n\n return job as Job<D> & { finished: (timeoutMs?: number) => Promise<any> }\n }\n\n async close() {\n await this.worker.close()\n }\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["connection: IORedis","Queue","Worker","options: RetryOptions","UnrecoverableError","baseDelay: number","job: Job<D>"],"sources":["../src/BaseTask.ts"],"sourcesContent":["import {\n AddOptions,\n FlowJob,\n FlowOptions,\n GroupOptions,\n Job,\n Queue,\n QueueOptions,\n UnrecoverableError,\n Worker,\n WorkerOptions,\n} from 'groupmq-plus'\nimport IORedis from 'ioredis'\n\nexport type { FlowChildResult } from 'groupmq-plus'\n\nexport interface BackoffOptions {\n /** 延迟策略:固定间隔、指数递增 */\n type: 'fixed' | 'exponential'\n /** 延迟时间为毫秒 */\n delay?: number\n /**\n * 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。\n * 抖动用于增加随机性,以防止因多个任务同时重试而导致的惊群效应。\n * 最终的延迟时间将在 `delay ± (delay * jitter)` 的范围内随机波动。\n * @defaultValue 0\n * @example 0.1 // 为延迟添加 ±10% 的随机抖动\n * @example 0.25 // 为延迟添加 ±25% 的随机抖动\n */\n jitter?: number\n}\n\nexport interface RetryOptions {\n error?: Error | unknown\n message?: string\n delay?: number\n}\n\n// [修复] 修改返回值类型为 Error,否则外层无法使用 throw opts.retryLater(...)\nexport interface HandleOptions {\n token: string\n retryLater(options?: RetryOptions): Error\n retryLater(errorOrMessage?: Error | string): Error\n retryLater(arg?: RetryOptions | Error | string): Error\n abort(message?: string): Error\n}\n\nexport interface BaseTaskOptions {\n name?: string\n autoStart?: boolean\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n debug?: boolean\n}\n\n/**\n * 任务启动选项,用于单个任务或 Flow 中的子任务\n */\nexport type TaskStartOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n delay?: number\n groupConfig?: GroupOptions\n} & Omit<AddOptions<D>, 'groupId' | 'data' | 'groupConfig'>\n\n/**\n * Flow 任务选项\n */\nexport type TaskFlowOptions<D> = {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n children: Array<\n {\n groupId: string\n data: D\n jobId?: string\n groupConfig?: GroupOptions\n } & Omit<FlowJob<D>, 'groupId' | 'data'>\n >\n} & Omit<FlowJob<D>, 'groupId' | 'data'>\n\nexport class TaskRetryLaterError extends Error {\n public customDelay?: number\n\n constructor(message?: string, delay?: number) {\n super(message)\n this.name = 'TaskRetryLaterError'\n this.customDelay = delay\n }\n}\n\nexport abstract class BaseTask<D = any> {\n public queue: Queue<D>\n public worker: Worker<D>\n protected opts: BaseTaskOptions\n\n get name() {\n return this.opts?.name || this.constructor.name\n }\n\n constructor(protected connection: IORedis, opts?: Partial<BaseTaskOptions>) {\n this.opts = Object.assign(\n {\n debug: false,\n autoStart: true,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n keepCompleted: 0,\n maxAttempts: 3,\n jobTimeoutMs: 5000,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign(\n {\n concurrency: 1,\n name: this.name,\n },\n this.opts.workerOptions,\n )\n\n this.queue = new Queue<D>(this.opts.queueOptions as QueueOptions)\n\n this.worker = new Worker<D>({\n ...(this.opts.workerOptions as WorkerOptions<D>),\n autoStart: this.opts.autoStart,\n queue: this.queue,\n handler: async (job) => {\n try {\n return await this.handle(job, {\n token: job.token!,\n retryLater: (arg: RetryOptions | Error | string): Error => {\n const currentAttempts = job.attemptsMade\n const maxAttempts = job.opts.attempts\n\n let options: RetryOptions = {}\n if (arg instanceof Error) {\n options = { error: arg, message: arg.message }\n } else if (typeof arg === 'string') {\n options = { message: arg }\n } else {\n options = arg || {}\n }\n\n const { error, delay } = options\n const message = options.message || (error instanceof Error ? error.message : undefined) || 'retry later'\n const errorObj = error instanceof Error ? error : undefined\n\n if (currentAttempts + 1 >= maxAttempts) {\n // [修改] 返回错误而不是抛出\n if (errorObj) return errorObj\n return new Error(message)\n }\n\n const retryErr = new TaskRetryLaterError(message, delay)\n if (errorObj) {\n retryErr.stack = errorObj.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n retryErr.cause = errorObj\n }\n // [修改] 返回错误\n return retryErr\n },\n abort: (message?: string) => {\n // [修改] 返回错误\n return new UnrecoverableError(message || 'failed')\n },\n })\n } catch (err) {\n if (err instanceof Error) {\n Object.defineProperty(err, '__isJobError', { value: true, enumerable: false })\n }\n throw err\n }\n },\n backoff: (attempt: number, error: unknown) => {\n if (error instanceof TaskRetryLaterError && typeof error.customDelay === 'number') {\n return error.customDelay\n }\n\n const backoffConfig = this.opts.workerOptions?.backoff\n\n if (typeof backoffConfig === 'function') {\n return backoffConfig(attempt, error)\n }\n\n if (backoffConfig && typeof backoffConfig === 'object') {\n const { type, delay = 1000, jitter = 0 } = backoffConfig\n let baseDelay: number\n\n if (type === 'fixed') {\n baseDelay = delay\n } else if (type === 'exponential') {\n baseDelay = delay * Math.pow(2, attempt - 1)\n } else {\n baseDelay = 1000 * Math.pow(2, attempt - 1)\n }\n\n if (jitter > 0) {\n const jitterAmount = baseDelay * jitter\n const randomJitter = Math.random() * jitterAmount * 2 - jitterAmount\n baseDelay = baseDelay + randomJitter\n }\n\n return Math.max(0, Math.round(baseDelay))\n }\n\n return 1000 * Math.pow(2, attempt - 1)\n },\n })\n\n this.setupEventListeners()\n }\n\n private setupEventListeners() {\n this.worker.on('completed', (job) => {\n if (this.opts.debug) {\n console.log(`[COMPLETED] ${this.name}-${job.id} (Group: ${job.groupId})`)\n }\n this.onCompleted(job)\n })\n\n this.worker.on('failed', async (job) => {\n const isFinalFailure = job.isFailed()\n const error = new Error(job.failedReason || 'task failed')\n if (job.stacktrace) {\n error.stack = job.stacktrace\n }\n\n if (this.opts.debug) {\n if (!isFinalFailure) {\n console.log(`[RETRY] ${this.name}-${job.id}: ${error.message}`)\n } else {\n console.error(`[FINAL FAILED] ${this.name}-${job.id}: ${error.message}`)\n }\n }\n\n await this.onFailed(job, error)\n\n if (isFinalFailure) {\n await this.onFinalFailed(job, error)\n }\n })\n\n this.worker.on('error', (err) => {\n if (err instanceof TaskRetryLaterError) return\n if (err instanceof Error && (err as any).__isJobError) return\n\n if (this.opts.debug) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\n }\n\n async run() {\n return this.worker.run()\n }\n\n abstract handle(job: Job<D>, opts: HandleOptions): Promise<any>\n\n async onFailed(job: Job<D>, error: Error) {}\n async onFinalFailed(job: Job<D>, error: Error) {}\n async onCompleted(job: Job<D>) {}\n\n setGroupConfig(groupId: string, config: { priority?: number; concurrency?: number }) {\n return this.queue.groups.setConfig(groupId, config)\n }\n\n async start(opts: AddOptions<D> | FlowOptions<D, D>) {\n let job: Job<D>\n\n if ('children' in opts && Array.isArray(opts.children)) {\n if (!opts.parent) {\n throw new Error('Flow must include a parent job')\n }\n\n if (opts.children.length === 0) {\n throw new Error('Flow must include at least one child job')\n }\n\n job = await this.queue.addFlow(opts)\n } else {\n job = await this.queue.add(opts as AddOptions<D>)\n }\n\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n job['finished'] = async (timeoutMs?: number) => {\n return job.waitUntilFinished(timeoutMs)\n }\n\n return job as Job<D> & { finished: (timeoutMs?: number) => Promise<any> }\n }\n\n async close() {\n await this.worker.close()\n }\n}"],"mappings":"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"}
|