@tsed/bullmq 7.47.1 → 7.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,49 @@
1
- import { type ConnectionOptions, type DefaultJobOptions } from "bullmq";
1
+ import type { ConnectionOptions, DefaultJobOptions, QueueOptions, WorkerOptions } from "bullmq";
2
2
  export type BullMQConfig = {
3
+ /**
4
+ * Specify queue name's to create
5
+ */
3
6
  queues: string[];
7
+ /**
8
+ * Default connection to use for queue's and worker's
9
+ */
4
10
  connection: ConnectionOptions;
11
+ /**
12
+ * @deprecated Use defaultQueueOptions instead. Will be removed in the next major release
13
+ */
5
14
  defaultJobOptions?: DefaultJobOptions;
15
+ /**
16
+ * Default queue options which are applied to every queue
17
+ *
18
+ * Can be extended/overridden by `queueOptions`
19
+ */
20
+ defaultQueueOptions?: QueueOptions;
21
+ /**
22
+ * Specify additional queue options by queue name
23
+ */
24
+ queueOptions?: Record<string, QueueOptions>;
25
+ /**
26
+ * Disable the creation of any worker.
27
+ *
28
+ * All other worker configuration will be ignored
29
+ */
6
30
  disableWorker?: boolean;
31
+ /**
32
+ * Specify for which queues to start a worker for.
33
+ *
34
+ * Defaultly for every queue added in the `queues` parameter
35
+ */
7
36
  workerQueues?: string[];
37
+ /**
38
+ * Default worker options which are applied to every worker
39
+ *
40
+ * Can be extended/overridden by `workerOptions`
41
+ */
42
+ defaultWorkerOptions?: WorkerOptions;
43
+ /**
44
+ * Specify additional worker options by queue name
45
+ */
46
+ workerOptions?: Record<string, WorkerOptions>;
8
47
  };
9
48
  declare global {
10
49
  namespace TsED {
@@ -1,3 +1,4 @@
1
- export interface JobMethods {
2
- handle(payload: unknown): unknown;
1
+ import { Job } from "bullmq";
2
+ export interface JobMethods<DataType = unknown, ReturnType = unknown> {
3
+ handle(payload: DataType, job: Job<DataType, ReturnType>): ReturnType | Promise<ReturnType>;
3
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsed/bullmq",
3
- "version": "7.47.1",
3
+ "version": "7.48.0",
4
4
  "private": false,
5
5
  "description": "BullMQ integration for Ts.ED",
6
6
  "keywords": [
@@ -29,11 +29,11 @@
29
29
  "tslib": "2.5.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@tsed/common": "7.47.1",
33
- "@tsed/core": "7.47.1",
34
- "@tsed/di": "7.47.1",
32
+ "@tsed/common": "7.48.0",
33
+ "@tsed/core": "7.48.0",
34
+ "@tsed/di": "7.48.0",
35
35
  "@tsed/eslint": "7.44.1",
36
- "@tsed/schema": "7.47.1",
36
+ "@tsed/schema": "7.48.0",
37
37
  "@tsed/typescript": "7.44.1",
38
38
  "bullmq": "^4.12.3",
39
39
  "eslint": "^8.12.0",
@@ -1,7 +1,7 @@
1
- import {InjectorService, PlatformTest} from "@tsed/common";
1
+ import {PlatformTest} from "@tsed/common";
2
2
  import {catchAsyncError} from "@tsed/core";
3
3
  import {Queue, Worker} from "bullmq";
4
- import {anyString, anything, instance, mock, verify, when} from "ts-mockito";
4
+ import {anything, instance, mock, verify, when} from "ts-mockito";
5
5
 
6
6
  import "./BullMQModule";
7
7
  import {BullMQModule} from "./BullMQModule";
@@ -10,33 +10,26 @@ import {JobMethods} from "./contracts";
10
10
  import {JobController} from "./decorators";
11
11
  import {JobDispatcher} from "./dispatchers";
12
12
 
13
+ const queueConstructorSpy = jest.fn();
14
+ const workerConstructorSpy = jest.fn();
15
+
13
16
  jest.mock("bullmq", () => {
14
17
  return {
15
18
  Queue: class {
16
- public name: string;
17
-
18
- constructor(name: string) {
19
- this.name = name;
19
+ constructor(...args: any[]) {
20
+ queueConstructorSpy(...args);
20
21
  }
21
22
  close() {}
22
23
  },
23
24
  Worker: class {
24
- public name: string;
25
-
26
- constructor(name: string) {
27
- this.name = name;
25
+ constructor(...args: any[]) {
26
+ workerConstructorSpy(...args);
28
27
  }
29
28
  close() {}
30
29
  }
31
30
  };
32
31
  });
33
32
 
34
- const bullmq = {
35
- queues: ["default", "foo", "bar"],
36
- connection: {},
37
- workerQueues: ["default", "foo"]
38
- } as BullMQConfig;
39
-
40
33
  @JobController("cron", "default", {
41
34
  repeat: {
42
35
  pattern: "* * * * *"
@@ -51,154 +44,279 @@ class RegularJob {
51
44
  handle() {}
52
45
  }
53
46
 
54
- describe("module", () => {
47
+ describe("BullMQModule", () => {
55
48
  let dispatcher: JobDispatcher;
56
- beforeEach(async () => {
49
+
50
+ beforeEach(() => {
57
51
  dispatcher = mock(JobDispatcher);
58
52
  when(dispatcher.dispatch(CustomCronJob)).thenResolve();
59
-
60
- await PlatformTest.create({
61
- bullmq,
62
- imports: [
63
- {
64
- token: JobDispatcher,
65
- use: instance(dispatcher)
66
- }
67
- ]
68
- });
69
53
  });
70
- beforeEach(() => {});
54
+
71
55
  afterEach(PlatformTest.reset);
72
56
 
73
- describe("cronjobs", () => {
74
- it("should dispatch cron jobs", () => {
75
- verify(dispatcher.dispatch(CustomCronJob)).once();
57
+ describe("configuration", () => {
58
+ beforeEach(() => {
59
+ queueConstructorSpy.mockClear();
60
+ workerConstructorSpy.mockClear();
76
61
  });
77
- });
78
62
 
79
- describe("queues", () => {
80
- it("should get default", () => {
81
- const instance = PlatformTest.get<Queue>("bullmq.queue.default");
63
+ describe("merges config correctly", () => {
64
+ beforeEach(async () => {
65
+ await PlatformTest.create({
66
+ bullmq: {
67
+ queues: ["default", "special"],
68
+ connection: {
69
+ connectionName: "defaultConnectionName"
70
+ },
71
+ defaultQueueOptions: {
72
+ defaultJobOptions: {
73
+ delay: 100
74
+ },
75
+ blockingConnection: true
76
+ },
77
+ queueOptions: {
78
+ special: {
79
+ connection: {
80
+ connectionName: "specialConnectionName"
81
+ },
82
+ defaultJobOptions: {
83
+ attempts: 9
84
+ }
85
+ }
86
+ },
87
+ defaultWorkerOptions: {
88
+ connection: {
89
+ connectTimeout: 123
90
+ },
91
+ concurrency: 50
92
+ },
93
+ workerOptions: {
94
+ special: {
95
+ concurrency: 1,
96
+ lockDuration: 2
97
+ }
98
+ }
99
+ },
100
+ imports: [
101
+ {
102
+ token: JobDispatcher,
103
+ use: instance(dispatcher)
104
+ }
105
+ ]
106
+ });
107
+ });
82
108
 
83
- expect(instance).toBeInstanceOf(Queue);
84
- expect(instance.name).toBe("default");
109
+ it("queue", () => {
110
+ expect(queueConstructorSpy).toHaveBeenCalledTimes(2);
111
+
112
+ expect(queueConstructorSpy).toHaveBeenNthCalledWith(1, "default", {
113
+ connection: {
114
+ connectionName: "defaultConnectionName"
115
+ },
116
+ defaultJobOptions: {
117
+ delay: 100
118
+ },
119
+ blockingConnection: true
120
+ });
121
+
122
+ expect(queueConstructorSpy).toHaveBeenNthCalledWith(2, "special", {
123
+ connection: {
124
+ connectionName: "specialConnectionName"
125
+ },
126
+ defaultJobOptions: {
127
+ attempts: 9,
128
+ delay: 100
129
+ },
130
+ blockingConnection: true
131
+ });
132
+ });
133
+
134
+ it("worker", () => {
135
+ expect(workerConstructorSpy).toHaveBeenCalledTimes(2);
136
+
137
+ expect(workerConstructorSpy).toHaveBeenNthCalledWith(1, "default", expect.any(Function), {
138
+ connection: {
139
+ connectTimeout: 123
140
+ },
141
+ concurrency: 50
142
+ });
143
+
144
+ expect(workerConstructorSpy).toHaveBeenNthCalledWith(2, "special", expect.any(Function), {
145
+ connection: {
146
+ connectTimeout: 123
147
+ },
148
+ concurrency: 1,
149
+ lockDuration: 2
150
+ });
151
+ });
85
152
  });
86
153
 
87
- it.each(bullmq.queues)("should register queue(%s)", (queue) => {
88
- const instance = PlatformTest.get<Queue>(`bullmq.queue.${queue}`);
154
+ describe("disableWorker", () => {
155
+ const config = {
156
+ queues: ["default", "foo", "bar"],
157
+ connection: {},
158
+ disableWorker: true
159
+ } as BullMQConfig;
160
+
161
+ beforeEach(async () => {
162
+ await PlatformTest.create({
163
+ bullmq: config,
164
+ imports: [
165
+ {
166
+ token: JobDispatcher,
167
+ use: instance(dispatcher)
168
+ }
169
+ ]
170
+ });
171
+ });
89
172
 
90
- expect(instance).toBeInstanceOf(Queue);
91
- expect(instance.name).toBe(queue);
173
+ it("should not create any workers", () => {
174
+ expect(workerConstructorSpy).toHaveBeenCalledTimes(0);
175
+ });
92
176
  });
93
177
 
94
- it("should not allow direct injection of the queue", () => {
95
- expect(PlatformTest.get(Queue)).not.toBeInstanceOf(Queue);
178
+ describe("without", () => {
179
+ it("skips initialization", async () => {
180
+ await PlatformTest.create({
181
+ imports: [
182
+ {
183
+ token: JobDispatcher,
184
+ use: instance(dispatcher)
185
+ }
186
+ ]
187
+ });
188
+
189
+ expect(queueConstructorSpy).not.toHaveBeenCalled();
190
+ verify(dispatcher.dispatch(anything())).never();
191
+ });
96
192
  });
97
193
  });
98
194
 
99
- describe("workers", () => {
100
- it("should get default", () => {
101
- const instance = PlatformTest.get<Worker>("bullmq.worker.default");
195
+ describe("functionality", () => {
196
+ const config = {
197
+ queues: ["default", "foo", "bar"],
198
+ connection: {},
199
+ workerQueues: ["default", "foo"]
200
+ } as BullMQConfig;
201
+
202
+ beforeEach(async () => {
203
+ await PlatformTest.create({
204
+ bullmq: config,
205
+ imports: [
206
+ {
207
+ token: JobDispatcher,
208
+ use: instance(dispatcher)
209
+ }
210
+ ]
211
+ });
212
+ });
102
213
 
103
- expect(instance).toBeInstanceOf(Worker);
104
- expect(instance.name).toBe("default");
214
+ describe("cronjobs", () => {
215
+ it("should dispatch cron jobs automatically", () => {
216
+ verify(dispatcher.dispatch(CustomCronJob)).once();
217
+ });
105
218
  });
106
219
 
107
- it.each(bullmq.workerQueues)("should register worker(%s)", (queue) => {
108
- const instance = PlatformTest.get<Worker>(`bullmq.worker.${queue}`);
220
+ describe("queues", () => {
221
+ it("should get default", () => {
222
+ const instance = PlatformTest.get<Queue>("bullmq.queue.default");
109
223
 
110
- expect(instance).toBeInstanceOf(Worker);
111
- expect(instance.name).toBe(queue);
112
- });
224
+ expect(instance).toBeInstanceOf(Queue);
225
+ });
113
226
 
114
- it("should not register unspecified worker queue", () => {
115
- expect(PlatformTest.get("bullmq.worker.bar")).toBeUndefined();
116
- });
227
+ it.each(config.queues)("should register queue(%s)", (queue) => {
228
+ const instance = PlatformTest.get<Queue>(`bullmq.queue.${queue}`);
229
+
230
+ expect(instance).toBeInstanceOf(Queue);
231
+ });
117
232
 
118
- it("should not allow direct injection of the worker", () => {
119
- expect(PlatformTest.get(Worker)).not.toBeInstanceOf(Worker);
233
+ it("should not allow direct injection of the queue", () => {
234
+ expect(PlatformTest.get(Queue)).not.toBeInstanceOf(Queue);
235
+ });
120
236
  });
121
237
 
122
- it("should run worker and execute processor", async () => {
123
- const bullMQModule = PlatformTest.get<BullMQModule>(BullMQModule);
124
- const worker = PlatformTest.get<JobMethods>("bullmq.job.default.regular");
125
- const job = {
126
- name: "regular",
127
- queueName: "default",
128
- data: {test: "test"}
129
- };
238
+ describe("workers", () => {
239
+ it("should get default", () => {
240
+ const instance = PlatformTest.get<Worker>("bullmq.worker.default");
130
241
 
131
- jest.spyOn(worker, "handle").mockResolvedValueOnce(undefined as never);
242
+ expect(instance).toBeInstanceOf(Worker);
243
+ });
132
244
 
133
- await (bullMQModule as any).onProcess(job);
245
+ it.each(config.workerQueues)("should register worker(%s)", (queue) => {
246
+ const instance = PlatformTest.get<Worker>(`bullmq.worker.${queue}`);
134
247
 
135
- expect(worker.handle).toHaveBeenCalledWith({test: "test"}, job);
136
- });
248
+ expect(instance).toBeInstanceOf(Worker);
249
+ });
137
250
 
138
- it("should log warning when the worker doesn't exists", async () => {
139
- const bullMQModule = PlatformTest.get<BullMQModule>(BullMQModule);
251
+ it("should not register unspecified worker queue", () => {
252
+ expect(PlatformTest.get("bullmq.worker.bar")).toBeUndefined();
253
+ });
140
254
 
141
- jest.spyOn(PlatformTest.injector.logger, "warn");
255
+ it("should not allow direct injection of the worker", () => {
256
+ expect(PlatformTest.get(Worker)).not.toBeInstanceOf(Worker);
257
+ });
258
+
259
+ it("should run worker and execute processor", async () => {
260
+ const bullMQModule = PlatformTest.get<BullMQModule>(BullMQModule);
261
+ const worker = PlatformTest.get<JobMethods>("bullmq.job.default.regular");
262
+ const job = {
263
+ name: "regular",
264
+ queueName: "default",
265
+ data: {test: "test"}
266
+ };
142
267
 
143
- const job = {
144
- name: "regular",
145
- queueName: "toto",
146
- data: {test: "test"}
147
- };
268
+ jest.spyOn(worker, "handle").mockResolvedValueOnce(undefined as never);
148
269
 
149
- await (bullMQModule as any).onProcess(job);
270
+ await (bullMQModule as any).onProcess(job);
150
271
 
151
- expect(PlatformTest.injector.logger.warn).toHaveBeenCalledWith({
152
- event: "BULLMQ_JOB_NOT_FOUND",
153
- message: "Job regular toto not found"
272
+ expect(worker.handle).toHaveBeenCalledWith({test: "test"}, job);
154
273
  });
155
- });
156
274
 
157
- it("should run worker, execute processor and handle error", async () => {
158
- const bullMQModule = PlatformTest.get<BullMQModule>(BullMQModule);
159
- const worker = PlatformTest.get<JobMethods>("bullmq.job.default.regular");
160
- const job = {
161
- name: "regular",
162
- queueName: "default",
163
- data: {test: "test"}
164
- };
275
+ it("should log warning when the worker doesn't exists", async () => {
276
+ const bullMQModule = PlatformTest.get<BullMQModule>(BullMQModule);
165
277
 
166
- jest.spyOn(PlatformTest.injector.logger, "error");
278
+ jest.spyOn(PlatformTest.injector.logger, "warn");
167
279
 
168
- jest.spyOn(worker, "handle").mockRejectedValue(new Error("error") as never);
280
+ const job = {
281
+ name: "regular",
282
+ queueName: "toto",
283
+ data: {test: "test"}
284
+ };
169
285
 
170
- const error = await catchAsyncError(() => (bullMQModule as any).onProcess(job));
286
+ await (bullMQModule as any).onProcess(job);
287
+
288
+ expect(PlatformTest.injector.logger.warn).toHaveBeenCalledWith({
289
+ event: "BULLMQ_JOB_NOT_FOUND",
290
+ message: "Job regular toto not found"
291
+ });
292
+ });
171
293
 
172
- expect(worker.handle).toHaveBeenCalledWith({test: "test"}, job);
173
- expect(PlatformTest.injector.logger.error).toHaveBeenCalledWith({
174
- duration: expect.any(Number),
175
- event: "BULLMQ_JOB_ERROR",
176
- message: "error",
177
- reqId: expect.any(String),
178
- stack: expect.any(String),
179
- time: expect.any(Object)
294
+ it("should run worker, execute processor and handle error", async () => {
295
+ const bullMQModule = PlatformTest.get<BullMQModule>(BullMQModule);
296
+ const worker = PlatformTest.get<JobMethods>("bullmq.job.default.regular");
297
+ const job = {
298
+ name: "regular",
299
+ queueName: "default",
300
+ data: {test: "test"}
301
+ };
302
+
303
+ jest.spyOn(PlatformTest.injector.logger, "error");
304
+
305
+ jest.spyOn(worker, "handle").mockRejectedValue(new Error("error") as never);
306
+
307
+ const error = await catchAsyncError(() => (bullMQModule as any).onProcess(job));
308
+
309
+ expect(worker.handle).toHaveBeenCalledWith({test: "test"}, job);
310
+ expect(PlatformTest.injector.logger.error).toHaveBeenCalledWith({
311
+ duration: expect.any(Number),
312
+ event: "BULLMQ_JOB_ERROR",
313
+ message: "error",
314
+ reqId: expect.any(String),
315
+ stack: expect.any(String),
316
+ time: expect.any(Object)
317
+ });
318
+ expect(error?.message).toEqual("error");
180
319
  });
181
- expect(error?.message).toEqual("error");
182
320
  });
183
321
  });
184
322
  });
185
-
186
- it('skips initialization when no config is provided', async () => {
187
- const injector = mock(InjectorService)
188
- const dispatcher = mock(JobDispatcher);
189
- await PlatformTest.create({
190
- imports: [
191
- {
192
- token: InjectorService,
193
- use: instance(injector),
194
- },
195
- {
196
- token: JobDispatcher,
197
- use: instance(dispatcher)
198
- }
199
- ]
200
- })
201
-
202
- verify(injector.add(anyString(), anything())).never();
203
- verify(dispatcher.dispatch(anything())).never()
204
- });
@@ -1,7 +1,8 @@
1
1
  import {BeforeInit, DIContext, runInContext} from "@tsed/common";
2
2
  import {Constant, InjectorService, Module} from "@tsed/di";
3
+ import {deepMerge} from "@tsed/core";
3
4
  import {getComputedType} from "@tsed/schema";
4
- import {Job, Queue, Worker} from "bullmq";
5
+ import {Job, Queue, type QueueOptions, Worker, type WorkerOptions} from "bullmq";
5
6
  import {v4} from "uuid";
6
7
  import {BullMQConfig} from "./config/config";
7
8
  import {JobMethods} from "./contracts";
@@ -19,7 +20,9 @@ export class BullMQModule implements BeforeInit {
19
20
  return;
20
21
  }
21
22
 
22
- this.buildWorkers();
23
+ if (!this.bullmq.disableWorker) {
24
+ this.buildWorkers();
25
+ }
23
26
  this.buildQueues();
24
27
 
25
28
  this.injector.getMany<JobMethods>("bullmq:cron").map((job) => this.dispatcher.dispatch(getComputedType(job)));
@@ -27,13 +30,19 @@ export class BullMQModule implements BeforeInit {
27
30
 
28
31
  private buildQueues() {
29
32
  this.bullmq.queues.forEach((queue) => {
33
+ const ops = deepMerge<QueueOptions, QueueOptions>(
34
+ {
35
+ connection: this.bullmq.connection,
36
+ defaultJobOptions: this.bullmq.defaultJobOptions,
37
+ ...this.bullmq.defaultQueueOptions
38
+ },
39
+ this.bullmq.queueOptions?.[queue]
40
+ )!;
41
+
30
42
  this.injector
31
43
  .add(`bullmq.queue.${queue}`, {
32
44
  type: "bullmq:queue",
33
- useValue: new Queue(queue, {
34
- connection: this.bullmq.connection,
35
- defaultJobOptions: this.bullmq.defaultJobOptions
36
- }),
45
+ useValue: new Queue(queue, ops),
37
46
  hooks: {
38
47
  $onDestroy: (queue) => queue.close()
39
48
  }
@@ -44,12 +53,18 @@ export class BullMQModule implements BeforeInit {
44
53
 
45
54
  private buildWorkers() {
46
55
  (this.bullmq.workerQueues ?? this.bullmq.queues).forEach((queue) => {
56
+ const ops = deepMerge<WorkerOptions, WorkerOptions>(
57
+ {
58
+ connection: this.bullmq.connection,
59
+ ...this.bullmq.defaultWorkerOptions
60
+ },
61
+ this.bullmq.workerOptions?.[queue]
62
+ )!;
63
+
47
64
  this.injector
48
65
  .add(`bullmq.worker.${queue}`, {
49
66
  type: "bullmq:worker",
50
- useValue: new Worker(queue, this.onProcess.bind(this), {
51
- connection: this.bullmq.connection
52
- }),
67
+ useValue: new Worker(queue, this.onProcess.bind(this), ops),
53
68
  hooks: {
54
69
  $onDestroy: (worker) => worker.close()
55
70
  }
@@ -59,7 +74,7 @@ export class BullMQModule implements BeforeInit {
59
74
  }
60
75
 
61
76
  private getJob(name: string, queueName: string) {
62
- return this.injector.get(`bullmq.job.${queueName}.${name}`);
77
+ return this.injector.get<JobMethods>(`bullmq.job.${queueName}.${name}`);
63
78
  }
64
79
 
65
80
  private async onProcess(job: Job) {
@@ -1,13 +1,58 @@
1
- import {type ConnectionOptions, type DefaultJobOptions} from "bullmq";
1
+ import type {ConnectionOptions, DefaultJobOptions, QueueOptions, WorkerOptions} from "bullmq";
2
2
 
3
3
  export type BullMQConfig = {
4
+ /**
5
+ * Specify queue name's to create
6
+ */
4
7
  queues: string[];
8
+
9
+ /**
10
+ * Default connection to use for queue's and worker's
11
+ */
5
12
  connection: ConnectionOptions;
13
+
14
+ /**
15
+ * @deprecated Use defaultQueueOptions instead. Will be removed in the next major release
16
+ */
6
17
  defaultJobOptions?: DefaultJobOptions;
18
+
19
+ /**
20
+ * Default queue options which are applied to every queue
21
+ *
22
+ * Can be extended/overridden by `queueOptions`
23
+ */
24
+ defaultQueueOptions?: QueueOptions;
25
+
26
+ /**
27
+ * Specify additional queue options by queue name
28
+ */
29
+ queueOptions?: Record<string, QueueOptions>;
30
+
31
+ /**
32
+ * Disable the creation of any worker.
33
+ *
34
+ * All other worker configuration will be ignored
35
+ */
7
36
  disableWorker?: boolean;
8
- // optionally specify for which queues to start a worker for
9
- // in case not all queues should be worked on
37
+
38
+ /**
39
+ * Specify for which queues to start a worker for.
40
+ *
41
+ * Defaultly for every queue added in the `queues` parameter
42
+ */
10
43
  workerQueues?: string[];
44
+
45
+ /**
46
+ * Default worker options which are applied to every worker
47
+ *
48
+ * Can be extended/overridden by `workerOptions`
49
+ */
50
+ defaultWorkerOptions?: WorkerOptions;
51
+
52
+ /**
53
+ * Specify additional worker options by queue name
54
+ */
55
+ workerOptions?: Record<string, WorkerOptions>;
11
56
  };
12
57
 
13
58
  declare global {
@@ -1,3 +1,5 @@
1
- export interface JobMethods {
2
- handle(payload: unknown): unknown;
1
+ import {Job} from "bullmq";
2
+
3
+ export interface JobMethods<DataType = unknown, ReturnType = unknown> {
4
+ handle(payload: DataType, job: Job<DataType, ReturnType>): ReturnType | Promise<ReturnType>;
3
5
  }