@libeilong/mq 0.2.8 → 0.2.10
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 +29 -19
- package/dist/index.d.ts +29 -19
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AddOptions,
|
|
1
|
+
import { AddOptions, FlowOptions, Job, Queue, QueueOptions, Worker, WorkerOptions } from "groupmq-plus";
|
|
2
2
|
import IORedis from "ioredis";
|
|
3
|
+
export * from "groupmq-plus";
|
|
3
4
|
|
|
4
5
|
//#region src/BaseTask.d.ts
|
|
5
6
|
interface BackoffOptions {
|
|
@@ -9,11 +10,7 @@ interface BackoffOptions {
|
|
|
9
10
|
delay?: number;
|
|
10
11
|
/**
|
|
11
12
|
* 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。
|
|
12
|
-
* 抖动用于增加随机性,以防止因多个任务同时重试而导致的惊群效应。
|
|
13
|
-
* 最终的延迟时间将在 `delay ± (delay * jitter)` 的范围内随机波动。
|
|
14
13
|
* @defaultValue 0
|
|
15
|
-
* @example 0.1 // 为延迟添加 ±10% 的随机抖动
|
|
16
|
-
* @example 0.25 // 为延迟添加 ±25% 的随机抖动
|
|
17
14
|
*/
|
|
18
15
|
jitter?: number;
|
|
19
16
|
}
|
|
@@ -24,10 +21,8 @@ interface RetryOptions {
|
|
|
24
21
|
}
|
|
25
22
|
interface HandleOptions {
|
|
26
23
|
token: string;
|
|
27
|
-
retryLater(
|
|
28
|
-
|
|
29
|
-
retryLater(arg?: RetryOptions | Error | string): Error;
|
|
30
|
-
abort(arg?: Error | string | number): Error;
|
|
24
|
+
retryLater(arg?: RetryOptions | Error | string | number | object): Error | TaskRetryLaterError;
|
|
25
|
+
abort(arg?: Error | string | number | object): Error;
|
|
31
26
|
}
|
|
32
27
|
interface BaseTaskOptions {
|
|
33
28
|
name?: string;
|
|
@@ -41,28 +36,43 @@ declare class TaskRetryLaterError extends Error {
|
|
|
41
36
|
customDelay?: number;
|
|
42
37
|
constructor(message?: string, delay?: number);
|
|
43
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* 扩展 Job 类型,注入 finished 方法语法糖
|
|
41
|
+
*/
|
|
42
|
+
type TaskJob<D> = Job<D> & {
|
|
43
|
+
finished: (timeoutMs?: number) => Promise<any>;
|
|
44
|
+
};
|
|
44
45
|
declare abstract class BaseTask<D = any> {
|
|
45
46
|
protected connection: IORedis;
|
|
46
|
-
queue: Queue
|
|
47
|
-
worker: Worker
|
|
47
|
+
queue: Queue<D>;
|
|
48
|
+
worker: Worker<D>;
|
|
48
49
|
protected opts: BaseTaskOptions;
|
|
49
50
|
get name(): string;
|
|
50
51
|
constructor(connection: IORedis, opts?: Partial<BaseTaskOptions>);
|
|
52
|
+
/**
|
|
53
|
+
* 创建退避策略函数
|
|
54
|
+
*/
|
|
55
|
+
private createBackoffStrategy;
|
|
51
56
|
private setupEventListeners;
|
|
52
|
-
abstract handle(job: Job
|
|
53
|
-
onFailed(job: Job
|
|
54
|
-
onFinalFailed(job: Job
|
|
55
|
-
onCompleted(job: Job
|
|
57
|
+
abstract handle(job: Job<D>, opts: HandleOptions): Promise<any>;
|
|
58
|
+
onFailed(job: Job<D>, error: Error): Promise<void>;
|
|
59
|
+
onFinalFailed(job: Job<D>, error: Error): Promise<void>;
|
|
60
|
+
onCompleted(job: Job<D>): Promise<void>;
|
|
56
61
|
setGroupConfig(groupId: string, config: {
|
|
57
62
|
priority?: number;
|
|
58
63
|
concurrency?: number;
|
|
59
64
|
}): Promise<void>;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
/**
|
|
66
|
+
* 添加单个任务
|
|
67
|
+
*/
|
|
68
|
+
start(opts: AddOptions<D>): Promise<TaskJob<D>>;
|
|
69
|
+
/**
|
|
70
|
+
* 添加 Flow(父子任务)
|
|
71
|
+
*/
|
|
72
|
+
start(opts: FlowOptions<D, D>): Promise<TaskJob<D>>;
|
|
63
73
|
run(): Promise<void>;
|
|
64
74
|
close(): Promise<void>;
|
|
65
75
|
}
|
|
66
76
|
//#endregion
|
|
67
|
-
export {
|
|
77
|
+
export { BaseTask, type BaseTaskOptions, type HandleOptions, type RetryOptions, TaskRetryLaterError };
|
|
68
78
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { AddOptions,
|
|
1
|
+
import { AddOptions, FlowOptions, Job, Queue, QueueOptions, Worker, WorkerOptions } from "groupmq-plus";
|
|
2
2
|
import IORedis from "ioredis";
|
|
3
|
+
export * from "groupmq-plus";
|
|
3
4
|
|
|
4
5
|
//#region src/BaseTask.d.ts
|
|
5
6
|
interface BackoffOptions {
|
|
@@ -9,11 +10,7 @@ interface BackoffOptions {
|
|
|
9
10
|
delay?: number;
|
|
10
11
|
/**
|
|
11
12
|
* 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。
|
|
12
|
-
* 抖动用于增加随机性,以防止因多个任务同时重试而导致的惊群效应。
|
|
13
|
-
* 最终的延迟时间将在 `delay ± (delay * jitter)` 的范围内随机波动。
|
|
14
13
|
* @defaultValue 0
|
|
15
|
-
* @example 0.1 // 为延迟添加 ±10% 的随机抖动
|
|
16
|
-
* @example 0.25 // 为延迟添加 ±25% 的随机抖动
|
|
17
14
|
*/
|
|
18
15
|
jitter?: number;
|
|
19
16
|
}
|
|
@@ -24,10 +21,8 @@ interface RetryOptions {
|
|
|
24
21
|
}
|
|
25
22
|
interface HandleOptions {
|
|
26
23
|
token: string;
|
|
27
|
-
retryLater(
|
|
28
|
-
|
|
29
|
-
retryLater(arg?: RetryOptions | Error | string): Error;
|
|
30
|
-
abort(arg?: Error | string | number): Error;
|
|
24
|
+
retryLater(arg?: RetryOptions | Error | string | number | object): Error | TaskRetryLaterError;
|
|
25
|
+
abort(arg?: Error | string | number | object): Error;
|
|
31
26
|
}
|
|
32
27
|
interface BaseTaskOptions {
|
|
33
28
|
name?: string;
|
|
@@ -41,28 +36,43 @@ declare class TaskRetryLaterError extends Error {
|
|
|
41
36
|
customDelay?: number;
|
|
42
37
|
constructor(message?: string, delay?: number);
|
|
43
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* 扩展 Job 类型,注入 finished 方法语法糖
|
|
41
|
+
*/
|
|
42
|
+
type TaskJob<D> = Job<D> & {
|
|
43
|
+
finished: (timeoutMs?: number) => Promise<any>;
|
|
44
|
+
};
|
|
44
45
|
declare abstract class BaseTask<D = any> {
|
|
45
46
|
protected connection: IORedis;
|
|
46
|
-
queue: Queue
|
|
47
|
-
worker: Worker
|
|
47
|
+
queue: Queue<D>;
|
|
48
|
+
worker: Worker<D>;
|
|
48
49
|
protected opts: BaseTaskOptions;
|
|
49
50
|
get name(): string;
|
|
50
51
|
constructor(connection: IORedis, opts?: Partial<BaseTaskOptions>);
|
|
52
|
+
/**
|
|
53
|
+
* 创建退避策略函数
|
|
54
|
+
*/
|
|
55
|
+
private createBackoffStrategy;
|
|
51
56
|
private setupEventListeners;
|
|
52
|
-
abstract handle(job: Job
|
|
53
|
-
onFailed(job: Job
|
|
54
|
-
onFinalFailed(job: Job
|
|
55
|
-
onCompleted(job: Job
|
|
57
|
+
abstract handle(job: Job<D>, opts: HandleOptions): Promise<any>;
|
|
58
|
+
onFailed(job: Job<D>, error: Error): Promise<void>;
|
|
59
|
+
onFinalFailed(job: Job<D>, error: Error): Promise<void>;
|
|
60
|
+
onCompleted(job: Job<D>): Promise<void>;
|
|
56
61
|
setGroupConfig(groupId: string, config: {
|
|
57
62
|
priority?: number;
|
|
58
63
|
concurrency?: number;
|
|
59
64
|
}): Promise<void>;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
65
|
+
/**
|
|
66
|
+
* 添加单个任务
|
|
67
|
+
*/
|
|
68
|
+
start(opts: AddOptions<D>): Promise<TaskJob<D>>;
|
|
69
|
+
/**
|
|
70
|
+
* 添加 Flow(父子任务)
|
|
71
|
+
*/
|
|
72
|
+
start(opts: FlowOptions<D, D>): Promise<TaskJob<D>>;
|
|
63
73
|
run(): Promise<void>;
|
|
64
74
|
close(): Promise<void>;
|
|
65
75
|
}
|
|
66
76
|
//#endregion
|
|
67
|
-
export {
|
|
77
|
+
export { BaseTask, type BaseTaskOptions, type HandleOptions, type RetryOptions, TaskRetryLaterError };
|
|
68
78
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
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);let l=require(`@libeilong/func`);l=s(l);var u=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}}
|
|
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);let l=require(`@libeilong/func`);l=s(l);var u=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}};function d(e){return typeof e==`object`&&!!e&&`delay`in e}var f=class{get name(){return this.opts?.name||this.constructor.name}constructor(e,t){this.connection=e,this.opts=Object.assign({logger:!1,queueOptions:{},workerOptions:{}},t),this.opts.queueOptions=Object.assign({redis:e,namespace:this.name},this.opts.queueOptions),this.opts.workerOptions=Object.assign({name:this.name},this.opts.workerOptions),this.queue=new c.Queue(this.opts.queueOptions),this.worker=new c.Worker({...this.opts.workerOptions,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;if(d(t)&&typeof t.delay==`number`&&(i=t.delay),n+1>=r)return t instanceof Error?t:Error((0,l.extractErrorMessage)(t)||`retry limit reached`);let a=new u((0,l.extractErrorMessage)(t)||`retry later`,i);return typeof t==`object`&&t&&t instanceof Error&&(a.stack=t.stack,a.cause=t),a},abort:e=>{let t=new c.UnrecoverableError((0,l.extractErrorMessage)(e)||`abort task`);return typeof e==`object`&&e&&e instanceof Error&&(t.stack=e.stack,t.cause=e),t}})}catch(t){let n=t instanceof u,r=t instanceof c.UnrecoverableError;throw this.opts.logger&&!n&&!r&&(console.error(`\n--- [UNEXPECTED ERROR] ${this.name} (Job: ${e.id}) ---`),console.error(`Data:`,JSON.stringify(e.data,null,2)),console.error(t),console.error(`----------------------------------------------------
|
|
2
|
+
`)),t instanceof Error&&Object.defineProperty(t,`__isJobError`,{value:!0,enumerable:!1}),t}},backoff:this.createBackoffStrategy()}),this.setupEventListeners()}createBackoffStrategy(){return(e,t)=>{if(t instanceof u&&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;a+=Math.random()*e*2-e}return Math.max(0,Math.round(a))}return 1e3*2**(e-1)}}setupEventListeners(){this.worker.on(`completed`,e=>{this.opts.logger&&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.logger&&(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 u||e instanceof Error&&e.__isJobError||this.opts.logger&&console.error(`[WORKER ERROR] ${this.name}`,e)})}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);let n=t;return n.finished=e=>t.waitUntilFinished(e),n}async run(){return this.worker.run()}async close(){await this.worker.close()}};exports.BaseTask=f,exports.TaskRetryLaterError=u,Object.keys(c).forEach(function(e){e!==`default`&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return c[e]}})});
|
|
2
3
|
//# 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 { extractErrorMessage } from '@libeilong/func'\nimport {\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): Error\n retryLater(errorOrMessage?: Error | string): Error\n retryLater(arg?: RetryOptions | Error | string): Error\n abort(arg?: Error | string | number): Error\n}\n\nexport interface BaseTaskOptions {\n name?: string\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n logger?: 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 logger: false,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign({ name: this.name }, this.opts.workerOptions)\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 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: (arg?: Error | string | number) => {\n const error = new UnrecoverableError(extractErrorMessage(arg) || 'failed')\n if (arg instanceof Error) {\n error.stack = arg.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n error.cause = arg\n }\n return error\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.logger) {\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.logger) {\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.logger) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\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 run() {\n return this.worker.run()\n }\n\n async close() {\n await this.worker.close()\n }\n}\n"],"mappings":"2iBAqFA,IAAa,EAAb,cAAyC,KAAM,CAG7C,YAAY,EAAkB,EAAgB,CAC5C,MAAM,EAAQ,CACd,KAAK,KAAO,sBACZ,KAAK,YAAc,IAID,EAAtB,KAAwC,CAKtC,IAAI,MAAO,CACT,OAAO,KAAK,MAAM,MAAQ,KAAK,YAAY,KAG7C,YAAY,EAA+B,EAAiC,CAAtD,KAAA,WAAA,EACpB,KAAK,KAAO,OAAO,OACjB,CACE,OAAQ,GACR,aAAc,EAAE,CAChB,cAAe,EAAE,CAClB,CACD,EACD,CAED,KAAK,KAAK,aAAe,OAAO,OAC9B,CACE,MAAO,EACP,UAAW,KAAK,KACjB,CACD,KAAK,KAAK,aACX,CAED,KAAK,KAAK,cAAgB,OAAO,OAAO,CAAE,KAAM,KAAK,KAAM,CAAE,KAAK,KAAK,cAAc,CAErF,KAAK,MAAQ,IAAIC,EAAAA,MAAS,KAAK,KAAK,aAA6B,CAEjE,KAAK,OAAS,IAAIC,EAAAA,OAAU,CAC1B,GAAI,KAAK,KAAK,cACd,MAAO,KAAK,MACZ,QAAS,KAAO,IAAQ,CACtB,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,EAAK,CAC5B,MAAO,EAAI,MACX,WAAa,GAA8C,CACzD,IAAM,EAAkB,EAAI,aACtB,EAAc,EAAI,KAAK,SAEzBC,EAAwB,EAAE,CAC9B,AAKE,EALE,aAAe,MACP,CAAE,MAAO,EAAK,QAAS,EAAI,QAAS,CACrC,OAAO,GAAQ,SACd,CAAE,QAAS,EAAK,CAEhB,GAAO,EAAE,CAGrB,GAAM,CAAE,QAAO,SAAU,EACnB,EAAU,EAAQ,UAAY,aAAiB,MAAQ,EAAM,QAAU,IAAA,KAAc,cACrF,EAAW,aAAiB,MAAQ,EAAQ,IAAA,GAElD,GAAI,EAAkB,GAAK,EAGzB,OADI,GACO,MAAM,EAAQ,CAG3B,IAAM,EAAW,IAAI,EAAoB,EAAS,EAAM,CAQxD,OAPI,IACF,EAAS,MAAQ,EAAS,MAG1B,EAAS,MAAQ,GAGZ,GAET,MAAQ,GAAkC,CACxC,IAAM,EAAQ,IAAIC,EAAAA,oBAAAA,EAAAA,EAAAA,qBAAuC,EAAI,EAAI,SAAS,CAO1E,OANI,aAAe,QACjB,EAAM,MAAQ,EAAI,MAGlB,EAAM,MAAQ,GAET,GAEV,CAAC,OACK,EAAK,CAIZ,MAHI,aAAe,OACjB,OAAO,eAAe,EAAK,eAAgB,CAAE,MAAO,GAAM,WAAY,GAAO,CAAC,CAE1E,IAGV,SAAU,EAAiB,IAAmB,CAC5C,GAAI,aAAiB,GAAuB,OAAO,EAAM,aAAgB,SACvE,OAAO,EAAM,YAGf,IAAM,EAAgB,KAAK,KAAK,eAAe,QAE/C,GAAI,OAAO,GAAkB,WAC3B,OAAO,EAAc,EAAS,EAAM,CAGtC,GAAI,GAAiB,OAAO,GAAkB,SAAU,CACtD,GAAM,CAAE,OAAM,QAAQ,IAAM,SAAS,GAAM,EACvCC,EAUJ,GARA,AAKE,EALE,IAAS,QACC,EACH,IAAS,cACN,EAAiB,IAAG,EAAU,GAE9B,IAAgB,IAAG,EAAU,GAGvC,EAAS,EAAG,CACd,IAAM,EAAe,EAAY,EAC3B,EAAe,KAAK,QAAQ,CAAG,EAAe,EAAI,EACxD,GAAwB,EAG1B,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,EAAU,CAAC,CAG3C,MAAO,KAAgB,IAAG,EAAU,IAEvC,CAAC,CAEF,KAAK,qBAAqB,CAG5B,qBAA8B,CAC5B,KAAK,OAAO,GAAG,YAAc,GAAQ,CAC/B,KAAK,KAAK,QACZ,QAAQ,IAAI,eAAe,KAAK,KAAK,GAAG,EAAI,GAAG,WAAW,EAAI,QAAQ,GAAG,CAE3E,KAAK,YAAY,EAAI,EACrB,CAEF,KAAK,OAAO,GAAG,SAAU,KAAO,IAAQ,CACtC,IAAM,EAAiB,EAAI,UAAU,CAC/B,EAAY,MAAM,EAAI,cAAgB,cAAc,CACtD,EAAI,aACN,EAAM,MAAQ,EAAI,YAGhB,KAAK,KAAK,SACP,EAGH,QAAQ,MAAM,kBAAkB,KAAK,KAAK,GAAG,EAAI,GAAG,IAAI,EAAM,UAAU,CAFxE,QAAQ,IAAI,WAAW,KAAK,KAAK,GAAG,EAAI,GAAG,IAAI,EAAM,UAAU,EAMnE,MAAM,KAAK,SAAS,EAAK,EAAM,CAE3B,GACF,MAAM,KAAK,cAAc,EAAK,EAAM,EAEtC,CAEF,KAAK,OAAO,GAAG,QAAU,GAAQ,CAC3B,aAAe,GACf,aAAe,OAAU,EAAY,cAErC,KAAK,KAAK,QACZ,QAAQ,MAAM,kBAAkB,KAAK,OAAQ,EAAI,EAEnD,CAKJ,MAAM,SAAS,EAAa,EAAc,EAC1C,MAAM,cAAc,EAAa,EAAc,EAC/C,MAAM,YAAY,EAAa,EAE/B,eAAe,EAAiB,EAAqD,CACnF,OAAO,KAAK,MAAM,OAAO,UAAU,EAAS,EAAO,CAGrD,MAAM,MAAM,EAAyC,CACnD,IAAIC,EAEJ,GAAI,aAAc,GAAQ,MAAM,QAAQ,EAAK,SAAS,CAAE,CACtD,GAAI,CAAC,EAAK,OACR,MAAU,MAAM,iCAAiC,CAGnD,GAAI,EAAK,SAAS,SAAW,EAC3B,MAAU,MAAM,2CAA2C,CAG7D,EAAM,MAAM,KAAK,MAAM,QAAQ,EAAK,MAEpC,EAAM,MAAM,KAAK,MAAM,IAAI,EAAsB,CASnD,MAJA,GAAI,SAAc,KAAO,IAChB,EAAI,kBAAkB,EAAU,CAGlC,EAGT,MAAM,KAAM,CACV,OAAO,KAAK,OAAO,KAAK,CAG1B,MAAM,OAAQ,CACZ,MAAM,KAAK,OAAO,OAAO"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["connection: IORedis","Queue","Worker","delay: number | undefined","UnrecoverableError","baseDelay: number","job: Job<D>"],"sources":["../src/BaseTask.ts"],"sourcesContent":["import { extractErrorMessage } from '@libeilong/func'\nimport {\n AddOptions,\n FlowOptions,\n Job,\n Queue,\n QueueOptions,\n UnrecoverableError,\n Worker,\n WorkerOptions\n} from 'groupmq-plus'\nimport IORedis from 'ioredis'\n\nexport interface BackoffOptions {\n /** 延迟策略:固定间隔、指数递增 */\n type: 'fixed' | 'exponential'\n /** 延迟时间为毫秒 */\n delay?: number\n /**\n * 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。\n * @defaultValue 0\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(arg?: RetryOptions | Error | string | number | object): Error | TaskRetryLaterError\n abort(arg?: Error | string | number | object): Error\n}\n\nexport interface BaseTaskOptions {\n name?: string\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n logger?: boolean\n}\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\n/**\n * 扩展 Job 类型,注入 finished 方法语法糖\n */\nexport type TaskJob<D> = Job<D> & {\n finished: (timeoutMs?: number) => Promise<any>\n}\n\n// 辅助类型守卫,用于 retryLater 中安全判断\nfunction isRetryOptions(arg: any): arg is RetryOptions {\n return typeof arg === 'object' && arg !== null && 'delay' in arg\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 logger: false,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign({ name: this.name }, this.opts.workerOptions)\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 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 | number | object): Error => {\n const currentAttempts = job.attemptsMade\n const maxAttempts = job.opts.attempts\n\n let delay: number | undefined\n \n // [修复] 使用类型守卫防止 null 报错\n if (isRetryOptions(arg) && typeof arg.delay === 'number') {\n delay = arg.delay\n }\n\n // 如果已达最大重试次数,不再抛出 TaskRetryLaterError,而是直接返回错误让任务失败\n if (currentAttempts + 1 >= maxAttempts) {\n if (arg instanceof Error) return arg\n return new Error(extractErrorMessage(arg) || 'retry limit reached')\n }\n\n const retryErr = new TaskRetryLaterError(extractErrorMessage(arg) || 'retry later', delay)\n if (typeof arg === 'object' && arg !== null && arg instanceof Error) {\n retryErr.stack = arg.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n retryErr.cause = arg\n }\n return retryErr\n },\n abort: (arg?: Error | string | number | object) => {\n const error = new UnrecoverableError(extractErrorMessage(arg) || 'abort task')\n if (typeof arg === 'object' && arg !== null && arg instanceof Error) {\n error.stack = arg.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n error.cause = arg\n }\n return error\n },\n })\n } catch (err) {\n // 1. 检查是否是 retryLater 抛出的错误\n const isRetryError = err instanceof TaskRetryLaterError\n\n // 2. 检查是否是 abort 抛出的错误 (UnrecoverableError)\n const isAbortError = err instanceof UnrecoverableError\n\n // 3. 只有当开启日志 且 不是预期内的错误时,才打印详细堆栈\n if (this.opts.logger && !isRetryError && !isAbortError) {\n console.error(`\\n--- [UNEXPECTED ERROR] ${this.name} (Job: ${job.id}) ---`)\n console.error('Data:', JSON.stringify(job.data, null, 2))\n console.error(err)\n console.error('----------------------------------------------------\\n')\n }\n\n if (err instanceof Error) {\n Object.defineProperty(err, '__isJobError', { value: true, enumerable: false })\n }\n throw err\n }\n },\n backoff: this.createBackoffStrategy(),\n })\n\n this.setupEventListeners()\n }\n\n /**\n * 创建退避策略函数\n */\n private createBackoffStrategy() {\n return (attempt: number, error: unknown): number => {\n // 1. 优先处理显式指定的延迟(来自 retryLater)\n if (error instanceof TaskRetryLaterError && typeof error.customDelay === 'number') {\n return error.customDelay\n }\n\n const backoffConfig = this.opts.workerOptions?.backoff\n\n // 2. 处理自定义函数策略\n if (typeof backoffConfig === 'function') {\n return backoffConfig(attempt, error)\n }\n\n // 3. 处理配置对象策略\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 jitterRange = baseDelay * jitter\n baseDelay += Math.random() * jitterRange * 2 - jitterRange\n }\n\n return Math.max(0, Math.round(baseDelay))\n }\n\n // 4. 默认策略(指数退避)\n return 1000 * Math.pow(2, attempt - 1)\n }\n }\n\n private setupEventListeners() {\n this.worker.on('completed', (job) => {\n if (this.opts.logger) {\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.logger) {\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.logger) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\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 /**\n * 添加单个任务\n */\n async start(opts: AddOptions<D>): Promise<TaskJob<D>>\n /**\n * 添加 Flow(父子任务)\n */\n async start(opts: FlowOptions<D, D>): Promise<TaskJob<D>>\n /**\n * 实现\n */\n async start(opts: AddOptions<D> | FlowOptions<D, D>): Promise<TaskJob<D>> {\n let job: Job<D>\n\n // 运行时检查:如果有 children 数组,则视为 Flow\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 // 显式断言为 FlowOptions 以满足类型系统(虽然运行时检查已通过)\n job = await this.queue.addFlow(opts as FlowOptions<D, D>)\n } else {\n // 默认为单个任务\n job = await this.queue.add(opts as AddOptions<D>)\n }\n\n // 注入 finished 方法语法糖\n const taskJob = job as TaskJob<D>\n taskJob.finished = (timeoutMs?: number) => job.waitUntilFinished(timeoutMs)\n\n return taskJob\n }\n\n async run() {\n return this.worker.run()\n }\n\n async close() {\n await this.worker.close()\n // await this.queue.close()\n }\n}"],"mappings":"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"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{Queue as e,UnrecoverableError as t,Worker as n}from"groupmq-plus";import{extractErrorMessage as r}from"@libeilong/func";export*from"groupmq-plus";var i=class extends Error{constructor(e,t){super(e),this.name=`TaskRetryLaterError`,this.customDelay=t}};function a(e){return typeof e==`object`&&!!e&&`delay`in e}var o=class{get name(){return this.opts?.name||this.constructor.name}constructor(o,s){this.connection=o,this.opts=Object.assign({logger:!1,queueOptions:{},workerOptions:{}},s),this.opts.queueOptions=Object.assign({redis:o,namespace:this.name},this.opts.queueOptions),this.opts.workerOptions=Object.assign({name:this.name},this.opts.workerOptions),this.queue=new e(this.opts.queueOptions),this.worker=new n({...this.opts.workerOptions,queue:this.queue,handler:async e=>{try{return await this.handle(e,{token:e.token,retryLater:t=>{let n=e.attemptsMade,o=e.opts.attempts,s;if(a(t)&&typeof t.delay==`number`&&(s=t.delay),n+1>=o)return t instanceof Error?t:Error(r(t)||`retry limit reached`);let c=new i(r(t)||`retry later`,s);return typeof t==`object`&&t&&t instanceof Error&&(c.stack=t.stack,c.cause=t),c},abort:e=>{let n=new t(r(e)||`abort task`);return typeof e==`object`&&e&&e instanceof Error&&(n.stack=e.stack,n.cause=e),n}})}catch(n){let r=n instanceof i,a=n instanceof t;throw this.opts.logger&&!r&&!a&&(console.error(`\n--- [UNEXPECTED ERROR] ${this.name} (Job: ${e.id}) ---`),console.error(`Data:`,JSON.stringify(e.data,null,2)),console.error(n),console.error(`----------------------------------------------------
|
|
2
|
+
`)),n instanceof Error&&Object.defineProperty(n,`__isJobError`,{value:!0,enumerable:!1}),n}},backoff:this.createBackoffStrategy()}),this.setupEventListeners()}createBackoffStrategy(){return(e,t)=>{if(t instanceof i&&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;a+=Math.random()*e*2-e}return Math.max(0,Math.round(a))}return 1e3*2**(e-1)}}setupEventListeners(){this.worker.on(`completed`,e=>{this.opts.logger&&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.logger&&(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 i||e instanceof Error&&e.__isJobError||this.opts.logger&&console.error(`[WORKER ERROR] ${this.name}`,e)})}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);let n=t;return n.finished=e=>t.waitUntilFinished(e),n}async run(){return this.worker.run()}async close(){await this.worker.close()}};export{o as BaseTask,i as TaskRetryLaterError};
|
|
2
3
|
//# 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 { extractErrorMessage } from '@libeilong/func'\nimport {\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): Error\n retryLater(errorOrMessage?: Error | string): Error\n retryLater(arg?: RetryOptions | Error | string): Error\n abort(arg?: Error | string | number): Error\n}\n\nexport interface BaseTaskOptions {\n name?: string\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n logger?: 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 logger: false,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign({ name: this.name }, this.opts.workerOptions)\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 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: (arg?: Error | string | number) => {\n const error = new UnrecoverableError(extractErrorMessage(arg) || 'failed')\n if (arg instanceof Error) {\n error.stack = arg.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n error.cause = arg\n }\n return error\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.logger) {\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.logger) {\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.logger) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\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 run() {\n return this.worker.run()\n }\n\n async close() {\n await this.worker.close()\n }\n}\n"],"mappings":"6MAqFA,IAAa,EAAb,cAAyC,KAAM,CAG7C,YAAY,EAAkB,EAAgB,CAC5C,MAAM,EAAQ,CACd,KAAK,KAAO,sBACZ,KAAK,YAAc,IAID,EAAtB,KAAwC,CAKtC,IAAI,MAAO,CACT,OAAO,KAAK,MAAM,MAAQ,KAAK,YAAY,KAG7C,YAAY,EAA+B,EAAiC,CAAtD,KAAA,WAAA,EACpB,KAAK,KAAO,OAAO,OACjB,CACE,OAAQ,GACR,aAAc,EAAE,CAChB,cAAe,EAAE,CAClB,CACD,EACD,CAED,KAAK,KAAK,aAAe,OAAO,OAC9B,CACE,MAAO,EACP,UAAW,KAAK,KACjB,CACD,KAAK,KAAK,aACX,CAED,KAAK,KAAK,cAAgB,OAAO,OAAO,CAAE,KAAM,KAAK,KAAM,CAAE,KAAK,KAAK,cAAc,CAErF,KAAK,MAAQ,IAAIC,EAAS,KAAK,KAAK,aAA6B,CAEjE,KAAK,OAAS,IAAIC,EAAU,CAC1B,GAAI,KAAK,KAAK,cACd,MAAO,KAAK,MACZ,QAAS,KAAO,IAAQ,CACtB,GAAI,CACF,OAAO,MAAM,KAAK,OAAO,EAAK,CAC5B,MAAO,EAAI,MACX,WAAa,GAA8C,CACzD,IAAM,EAAkB,EAAI,aACtB,EAAc,EAAI,KAAK,SAEzBC,EAAwB,EAAE,CAC9B,AAKE,EALE,aAAe,MACP,CAAE,MAAO,EAAK,QAAS,EAAI,QAAS,CACrC,OAAO,GAAQ,SACd,CAAE,QAAS,EAAK,CAEhB,GAAO,EAAE,CAGrB,GAAM,CAAE,QAAO,SAAU,EACnB,EAAU,EAAQ,UAAY,aAAiB,MAAQ,EAAM,QAAU,IAAA,KAAc,cACrF,EAAW,aAAiB,MAAQ,EAAQ,IAAA,GAElD,GAAI,EAAkB,GAAK,EAGzB,OADI,GACO,MAAM,EAAQ,CAG3B,IAAM,EAAW,IAAI,EAAoB,EAAS,EAAM,CAQxD,OAPI,IACF,EAAS,MAAQ,EAAS,MAG1B,EAAS,MAAQ,GAGZ,GAET,MAAQ,GAAkC,CACxC,IAAM,EAAQ,IAAIC,EAAmB,EAAoB,EAAI,EAAI,SAAS,CAO1E,OANI,aAAe,QACjB,EAAM,MAAQ,EAAI,MAGlB,EAAM,MAAQ,GAET,GAEV,CAAC,OACK,EAAK,CAIZ,MAHI,aAAe,OACjB,OAAO,eAAe,EAAK,eAAgB,CAAE,MAAO,GAAM,WAAY,GAAO,CAAC,CAE1E,IAGV,SAAU,EAAiB,IAAmB,CAC5C,GAAI,aAAiB,GAAuB,OAAO,EAAM,aAAgB,SACvE,OAAO,EAAM,YAGf,IAAM,EAAgB,KAAK,KAAK,eAAe,QAE/C,GAAI,OAAO,GAAkB,WAC3B,OAAO,EAAc,EAAS,EAAM,CAGtC,GAAI,GAAiB,OAAO,GAAkB,SAAU,CACtD,GAAM,CAAE,OAAM,QAAQ,IAAM,SAAS,GAAM,EACvCC,EAUJ,GARA,AAKE,EALE,IAAS,QACC,EACH,IAAS,cACN,EAAiB,IAAG,EAAU,GAE9B,IAAgB,IAAG,EAAU,GAGvC,EAAS,EAAG,CACd,IAAM,EAAe,EAAY,EAC3B,EAAe,KAAK,QAAQ,CAAG,EAAe,EAAI,EACxD,GAAwB,EAG1B,OAAO,KAAK,IAAI,EAAG,KAAK,MAAM,EAAU,CAAC,CAG3C,MAAO,KAAgB,IAAG,EAAU,IAEvC,CAAC,CAEF,KAAK,qBAAqB,CAG5B,qBAA8B,CAC5B,KAAK,OAAO,GAAG,YAAc,GAAQ,CAC/B,KAAK,KAAK,QACZ,QAAQ,IAAI,eAAe,KAAK,KAAK,GAAG,EAAI,GAAG,WAAW,EAAI,QAAQ,GAAG,CAE3E,KAAK,YAAY,EAAI,EACrB,CAEF,KAAK,OAAO,GAAG,SAAU,KAAO,IAAQ,CACtC,IAAM,EAAiB,EAAI,UAAU,CAC/B,EAAY,MAAM,EAAI,cAAgB,cAAc,CACtD,EAAI,aACN,EAAM,MAAQ,EAAI,YAGhB,KAAK,KAAK,SACP,EAGH,QAAQ,MAAM,kBAAkB,KAAK,KAAK,GAAG,EAAI,GAAG,IAAI,EAAM,UAAU,CAFxE,QAAQ,IAAI,WAAW,KAAK,KAAK,GAAG,EAAI,GAAG,IAAI,EAAM,UAAU,EAMnE,MAAM,KAAK,SAAS,EAAK,EAAM,CAE3B,GACF,MAAM,KAAK,cAAc,EAAK,EAAM,EAEtC,CAEF,KAAK,OAAO,GAAG,QAAU,GAAQ,CAC3B,aAAe,GACf,aAAe,OAAU,EAAY,cAErC,KAAK,KAAK,QACZ,QAAQ,MAAM,kBAAkB,KAAK,OAAQ,EAAI,EAEnD,CAKJ,MAAM,SAAS,EAAa,EAAc,EAC1C,MAAM,cAAc,EAAa,EAAc,EAC/C,MAAM,YAAY,EAAa,EAE/B,eAAe,EAAiB,EAAqD,CACnF,OAAO,KAAK,MAAM,OAAO,UAAU,EAAS,EAAO,CAGrD,MAAM,MAAM,EAAyC,CACnD,IAAIC,EAEJ,GAAI,aAAc,GAAQ,MAAM,QAAQ,EAAK,SAAS,CAAE,CACtD,GAAI,CAAC,EAAK,OACR,MAAU,MAAM,iCAAiC,CAGnD,GAAI,EAAK,SAAS,SAAW,EAC3B,MAAU,MAAM,2CAA2C,CAG7D,EAAM,MAAM,KAAK,MAAM,QAAQ,EAAK,MAEpC,EAAM,MAAM,KAAK,MAAM,IAAI,EAAsB,CASnD,MAJA,GAAI,SAAc,KAAO,IAChB,EAAI,kBAAkB,EAAU,CAGlC,EAGT,MAAM,KAAM,CACV,OAAO,KAAK,OAAO,KAAK,CAG1B,MAAM,OAAQ,CACZ,MAAM,KAAK,OAAO,OAAO"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["connection: IORedis","delay: number | undefined","baseDelay: number","job: Job<D>"],"sources":["../src/BaseTask.ts"],"sourcesContent":["import { extractErrorMessage } from '@libeilong/func'\nimport {\n AddOptions,\n FlowOptions,\n Job,\n Queue,\n QueueOptions,\n UnrecoverableError,\n Worker,\n WorkerOptions\n} from 'groupmq-plus'\nimport IORedis from 'ioredis'\n\nexport interface BackoffOptions {\n /** 延迟策略:固定间隔、指数递增 */\n type: 'fixed' | 'exponential'\n /** 延迟时间为毫秒 */\n delay?: number\n /**\n * 添加到延迟中的随机抖动比例,取值范围为 0 到 1 之间的小数。\n * @defaultValue 0\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(arg?: RetryOptions | Error | string | number | object): Error | TaskRetryLaterError\n abort(arg?: Error | string | number | object): Error\n}\n\nexport interface BaseTaskOptions {\n name?: string\n queueOptions?: Partial<QueueOptions>\n workerOptions?: Omit<Partial<WorkerOptions<any>>, 'backoff'> & {\n backoff?: BackoffOptions | ((attempt: number, error: unknown) => number)\n }\n logger?: boolean\n}\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\n/**\n * 扩展 Job 类型,注入 finished 方法语法糖\n */\nexport type TaskJob<D> = Job<D> & {\n finished: (timeoutMs?: number) => Promise<any>\n}\n\n// 辅助类型守卫,用于 retryLater 中安全判断\nfunction isRetryOptions(arg: any): arg is RetryOptions {\n return typeof arg === 'object' && arg !== null && 'delay' in arg\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 logger: false,\n queueOptions: {},\n workerOptions: {},\n },\n opts,\n )\n\n this.opts.queueOptions = Object.assign(\n {\n redis: connection,\n namespace: this.name,\n },\n this.opts.queueOptions,\n )\n\n this.opts.workerOptions = Object.assign({ name: this.name }, this.opts.workerOptions)\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 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 | number | object): Error => {\n const currentAttempts = job.attemptsMade\n const maxAttempts = job.opts.attempts\n\n let delay: number | undefined\n \n // [修复] 使用类型守卫防止 null 报错\n if (isRetryOptions(arg) && typeof arg.delay === 'number') {\n delay = arg.delay\n }\n\n // 如果已达最大重试次数,不再抛出 TaskRetryLaterError,而是直接返回错误让任务失败\n if (currentAttempts + 1 >= maxAttempts) {\n if (arg instanceof Error) return arg\n return new Error(extractErrorMessage(arg) || 'retry limit reached')\n }\n\n const retryErr = new TaskRetryLaterError(extractErrorMessage(arg) || 'retry later', delay)\n if (typeof arg === 'object' && arg !== null && arg instanceof Error) {\n retryErr.stack = arg.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n retryErr.cause = arg\n }\n return retryErr\n },\n abort: (arg?: Error | string | number | object) => {\n const error = new UnrecoverableError(extractErrorMessage(arg) || 'abort task')\n if (typeof arg === 'object' && arg !== null && arg instanceof Error) {\n error.stack = arg.stack\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n error.cause = arg\n }\n return error\n },\n })\n } catch (err) {\n // 1. 检查是否是 retryLater 抛出的错误\n const isRetryError = err instanceof TaskRetryLaterError\n\n // 2. 检查是否是 abort 抛出的错误 (UnrecoverableError)\n const isAbortError = err instanceof UnrecoverableError\n\n // 3. 只有当开启日志 且 不是预期内的错误时,才打印详细堆栈\n if (this.opts.logger && !isRetryError && !isAbortError) {\n console.error(`\\n--- [UNEXPECTED ERROR] ${this.name} (Job: ${job.id}) ---`)\n console.error('Data:', JSON.stringify(job.data, null, 2))\n console.error(err)\n console.error('----------------------------------------------------\\n')\n }\n\n if (err instanceof Error) {\n Object.defineProperty(err, '__isJobError', { value: true, enumerable: false })\n }\n throw err\n }\n },\n backoff: this.createBackoffStrategy(),\n })\n\n this.setupEventListeners()\n }\n\n /**\n * 创建退避策略函数\n */\n private createBackoffStrategy() {\n return (attempt: number, error: unknown): number => {\n // 1. 优先处理显式指定的延迟(来自 retryLater)\n if (error instanceof TaskRetryLaterError && typeof error.customDelay === 'number') {\n return error.customDelay\n }\n\n const backoffConfig = this.opts.workerOptions?.backoff\n\n // 2. 处理自定义函数策略\n if (typeof backoffConfig === 'function') {\n return backoffConfig(attempt, error)\n }\n\n // 3. 处理配置对象策略\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 jitterRange = baseDelay * jitter\n baseDelay += Math.random() * jitterRange * 2 - jitterRange\n }\n\n return Math.max(0, Math.round(baseDelay))\n }\n\n // 4. 默认策略(指数退避)\n return 1000 * Math.pow(2, attempt - 1)\n }\n }\n\n private setupEventListeners() {\n this.worker.on('completed', (job) => {\n if (this.opts.logger) {\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.logger) {\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.logger) {\n console.error(`[WORKER ERROR] ${this.name}`, err)\n }\n })\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 /**\n * 添加单个任务\n */\n async start(opts: AddOptions<D>): Promise<TaskJob<D>>\n /**\n * 添加 Flow(父子任务)\n */\n async start(opts: FlowOptions<D, D>): Promise<TaskJob<D>>\n /**\n * 实现\n */\n async start(opts: AddOptions<D> | FlowOptions<D, D>): Promise<TaskJob<D>> {\n let job: Job<D>\n\n // 运行时检查:如果有 children 数组,则视为 Flow\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 // 显式断言为 FlowOptions 以满足类型系统(虽然运行时检查已通过)\n job = await this.queue.addFlow(opts as FlowOptions<D, D>)\n } else {\n // 默认为单个任务\n job = await this.queue.add(opts as AddOptions<D>)\n }\n\n // 注入 finished 方法语法糖\n const taskJob = job as TaskJob<D>\n taskJob.finished = (timeoutMs?: number) => job.waitUntilFinished(timeoutMs)\n\n return taskJob\n }\n\n async run() {\n return this.worker.run()\n }\n\n async close() {\n await this.worker.close()\n // await this.queue.close()\n }\n}"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@libeilong/mq",
|
|
3
3
|
"author": "lblblong",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.10",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"node": ">=18"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"groupmq-plus": "^1.4.
|
|
23
|
+
"groupmq-plus": "^1.4.3",
|
|
24
24
|
"@libeilong/func": "^0.33.1"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|