@boringnode/queue 0.0.1-alpha.4 → 0.1.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.
- package/README.md +119 -10
- package/build/{chunk-5PDDRF5O.js → chunk-NPQKBCCY.js} +2 -2
- package/build/{chunk-HMGNQSSG.js → chunk-SMOKFZ46.js} +15 -1
- package/build/chunk-SMOKFZ46.js.map +1 -0
- package/build/{chunk-CD45GT6E.js → chunk-US7THLSZ.js} +2 -2
- package/build/{index-C3_tlebh.d.ts → index-2Ng_OpVK.d.ts} +243 -6
- package/build/index.d.ts +81 -4
- package/build/index.js +345 -7
- package/build/index.js.map +1 -1
- package/build/src/contracts/adapter.d.ts +1 -1
- package/build/src/drivers/knex_adapter.d.ts +8 -1
- package/build/src/drivers/knex_adapter.js +186 -34
- package/build/src/drivers/knex_adapter.js.map +1 -1
- package/build/src/drivers/redis_adapter.d.ts +7 -1
- package/build/src/drivers/redis_adapter.js +193 -2
- package/build/src/drivers/redis_adapter.js.map +1 -1
- package/build/src/drivers/sync_adapter.d.ts +7 -1
- package/build/src/drivers/sync_adapter.js +20 -2
- package/build/src/drivers/sync_adapter.js.map +1 -1
- package/build/src/types/index.d.ts +1 -1
- package/build/src/types/main.d.ts +1 -1
- package/package.json +3 -2
- package/build/chunk-HMGNQSSG.js.map +0 -1
- /package/build/{chunk-5PDDRF5O.js.map → chunk-NPQKBCCY.js.map} +0 -0
- /package/build/{chunk-CD45GT6E.js.map → chunk-US7THLSZ.js.map} +0 -0
package/build/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
parse
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NPQKBCCY.js";
|
|
4
4
|
import {
|
|
5
5
|
Locator,
|
|
6
6
|
QueueManager,
|
|
7
7
|
debug_default
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-US7THLSZ.js";
|
|
9
9
|
import {
|
|
10
10
|
DEFAULT_ERROR_RETRY_DELAY,
|
|
11
11
|
DEFAULT_IDLE_DELAY,
|
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
DEFAULT_STALLED_INTERVAL,
|
|
14
14
|
DEFAULT_STALLED_THRESHOLD,
|
|
15
15
|
E_INVALID_BASE_DELAY,
|
|
16
|
+
E_INVALID_CRON_EXPRESSION,
|
|
16
17
|
E_INVALID_MAX_DELAY,
|
|
17
18
|
E_INVALID_MULTIPLIER,
|
|
19
|
+
E_INVALID_SCHEDULE_CONFIG,
|
|
18
20
|
E_JOB_MAX_ATTEMPTS_REACHED,
|
|
19
21
|
E_JOB_TIMEOUT,
|
|
20
22
|
exceptions_exports
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-SMOKFZ46.js";
|
|
22
24
|
|
|
23
25
|
// src/job_dispatcher.ts
|
|
24
26
|
import { randomUUID } from "crypto";
|
|
@@ -120,11 +122,11 @@ var JobDispatcher = class {
|
|
|
120
122
|
/**
|
|
121
123
|
* Dispatch the job to the queue.
|
|
122
124
|
*
|
|
123
|
-
* @returns
|
|
125
|
+
* @returns A DispatchResult containing the jobId
|
|
124
126
|
*
|
|
125
127
|
* @example
|
|
126
128
|
* ```typescript
|
|
127
|
-
* const jobId = await SendEmailJob.dispatch(payload).run()
|
|
129
|
+
* const { jobId } = await SendEmailJob.dispatch(payload).run()
|
|
128
130
|
* console.log(`Dispatched job: ${jobId}`)
|
|
129
131
|
* ```
|
|
130
132
|
*/
|
|
@@ -145,7 +147,9 @@ var JobDispatcher = class {
|
|
|
145
147
|
} else {
|
|
146
148
|
await adapter.pushOn(this.#queue, payload);
|
|
147
149
|
}
|
|
148
|
-
return
|
|
150
|
+
return {
|
|
151
|
+
jobId: id
|
|
152
|
+
};
|
|
149
153
|
}
|
|
150
154
|
/**
|
|
151
155
|
* Thenable implementation for auto-dispatch when awaited.
|
|
@@ -154,7 +158,7 @@ var JobDispatcher = class {
|
|
|
154
158
|
*
|
|
155
159
|
* @param onFulfilled - Success callback
|
|
156
160
|
* @param onRejected - Error callback
|
|
157
|
-
* @returns Promise resolving to the
|
|
161
|
+
* @returns Promise resolving to the DispatchResult
|
|
158
162
|
*/
|
|
159
163
|
then(onFulfilled, onRejected) {
|
|
160
164
|
return this.run().then(onFulfilled, onRejected);
|
|
@@ -170,6 +174,159 @@ var JobDispatcher = class {
|
|
|
170
174
|
}
|
|
171
175
|
};
|
|
172
176
|
|
|
177
|
+
// src/schedule_builder.ts
|
|
178
|
+
import { CronExpressionParser } from "cron-parser";
|
|
179
|
+
var ScheduleBuilder = class {
|
|
180
|
+
#jobName;
|
|
181
|
+
#payload;
|
|
182
|
+
#id;
|
|
183
|
+
#cronExpression;
|
|
184
|
+
#everyMs;
|
|
185
|
+
#timezone = "UTC";
|
|
186
|
+
#from;
|
|
187
|
+
#to;
|
|
188
|
+
#limit;
|
|
189
|
+
constructor(jobName, payload) {
|
|
190
|
+
this.#jobName = jobName;
|
|
191
|
+
this.#payload = payload;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Set a custom schedule ID.
|
|
195
|
+
* If not specified, defaults to the job name.
|
|
196
|
+
* If a schedule with this ID exists, it will be updated (upsert).
|
|
197
|
+
*/
|
|
198
|
+
id(scheduleId) {
|
|
199
|
+
this.#id = scheduleId;
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Set a cron expression for the schedule.
|
|
204
|
+
* Mutually exclusive with `every()`.
|
|
205
|
+
*/
|
|
206
|
+
cron(expression) {
|
|
207
|
+
this.#cronExpression = expression;
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Set a repeating interval for the schedule.
|
|
212
|
+
* Mutually exclusive with `cron()`.
|
|
213
|
+
*/
|
|
214
|
+
every(interval) {
|
|
215
|
+
this.#everyMs = parse(interval);
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Set the timezone for cron evaluation.
|
|
220
|
+
* @default 'UTC'
|
|
221
|
+
*/
|
|
222
|
+
timezone(tz) {
|
|
223
|
+
this.#timezone = tz;
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Set the start boundary for the schedule.
|
|
228
|
+
* No jobs will be dispatched before this date.
|
|
229
|
+
*/
|
|
230
|
+
from(date) {
|
|
231
|
+
this.#from = date;
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Set the end boundary for the schedule.
|
|
236
|
+
* No jobs will be dispatched after this date.
|
|
237
|
+
*/
|
|
238
|
+
to(date) {
|
|
239
|
+
this.#to = date;
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Set both start and end boundaries for the schedule.
|
|
244
|
+
* Shorthand for `.from(start).to(end)`.
|
|
245
|
+
*/
|
|
246
|
+
between(from, to) {
|
|
247
|
+
return this.from(from).to(to);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Set the maximum number of runs for this schedule.
|
|
251
|
+
*/
|
|
252
|
+
limit(maxRuns) {
|
|
253
|
+
this.#limit = maxRuns;
|
|
254
|
+
return this;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Create the schedule and return the schedule ID.
|
|
258
|
+
*/
|
|
259
|
+
async run() {
|
|
260
|
+
if (!this.#cronExpression && !this.#everyMs) {
|
|
261
|
+
throw new E_INVALID_SCHEDULE_CONFIG([
|
|
262
|
+
"Schedule must have either a cron expression or an interval"
|
|
263
|
+
]);
|
|
264
|
+
}
|
|
265
|
+
if (this.#cronExpression && this.#everyMs) {
|
|
266
|
+
throw new E_INVALID_SCHEDULE_CONFIG([
|
|
267
|
+
"Schedule cannot have both a cron expression and an interval"
|
|
268
|
+
]);
|
|
269
|
+
}
|
|
270
|
+
if (this.#cronExpression) {
|
|
271
|
+
try {
|
|
272
|
+
CronExpressionParser.parse(this.#cronExpression, { tz: this.#timezone });
|
|
273
|
+
} catch (error) {
|
|
274
|
+
throw new E_INVALID_CRON_EXPRESSION([this.#cronExpression, error.message]);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const config = {
|
|
278
|
+
id: this.#id ?? this.#jobName,
|
|
279
|
+
jobName: this.#jobName,
|
|
280
|
+
payload: this.#payload,
|
|
281
|
+
cronExpression: this.#cronExpression,
|
|
282
|
+
everyMs: this.#everyMs,
|
|
283
|
+
timezone: this.#timezone,
|
|
284
|
+
from: this.#from,
|
|
285
|
+
to: this.#to,
|
|
286
|
+
limit: this.#limit
|
|
287
|
+
};
|
|
288
|
+
const adapter = QueueManager.use();
|
|
289
|
+
const scheduleId = await adapter.createSchedule(config);
|
|
290
|
+
const nextRunAt = this.#calculateNextRunAt();
|
|
291
|
+
await adapter.updateSchedule(scheduleId, { nextRunAt });
|
|
292
|
+
return { scheduleId };
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Calculate the next run time based on cron or interval.
|
|
296
|
+
*/
|
|
297
|
+
#calculateNextRunAt() {
|
|
298
|
+
const now = /* @__PURE__ */ new Date();
|
|
299
|
+
let nextRun;
|
|
300
|
+
if (this.#cronExpression) {
|
|
301
|
+
const cron = CronExpressionParser.parse(this.#cronExpression, {
|
|
302
|
+
currentDate: now,
|
|
303
|
+
tz: this.#timezone
|
|
304
|
+
});
|
|
305
|
+
nextRun = cron.next().toDate();
|
|
306
|
+
} else {
|
|
307
|
+
nextRun = new Date(now.getTime() + this.#everyMs);
|
|
308
|
+
}
|
|
309
|
+
if (this.#from && nextRun < this.#from) {
|
|
310
|
+
if (this.#cronExpression) {
|
|
311
|
+
const cron = CronExpressionParser.parse(this.#cronExpression, {
|
|
312
|
+
currentDate: this.#from,
|
|
313
|
+
tz: this.#timezone
|
|
314
|
+
});
|
|
315
|
+
nextRun = cron.next().toDate();
|
|
316
|
+
} else {
|
|
317
|
+
nextRun = this.#from;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return nextRun;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Implement PromiseLike to allow `await builder.every('5m')` syntax.
|
|
324
|
+
*/
|
|
325
|
+
then(onfulfilled, onrejected) {
|
|
326
|
+
return this.run().then(onfulfilled, onrejected);
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
173
330
|
// src/job.ts
|
|
174
331
|
var Job = class {
|
|
175
332
|
#payload;
|
|
@@ -248,6 +405,34 @@ var Job = class {
|
|
|
248
405
|
}
|
|
249
406
|
return dispatcher;
|
|
250
407
|
}
|
|
408
|
+
/**
|
|
409
|
+
* Create a schedule for this job.
|
|
410
|
+
*
|
|
411
|
+
* Returns a ScheduleBuilder for fluent configuration before creating the schedule.
|
|
412
|
+
* The schedule is not actually created until `.run()` is called or the
|
|
413
|
+
* builder is awaited.
|
|
414
|
+
*
|
|
415
|
+
* @param payload - The data to pass to the job on each run
|
|
416
|
+
* @returns A ScheduleBuilder for fluent configuration
|
|
417
|
+
*
|
|
418
|
+
* @example
|
|
419
|
+
* ```typescript
|
|
420
|
+
* // Cron schedule
|
|
421
|
+
* await CleanupJob.schedule({ days: 30 })
|
|
422
|
+
* .id('cleanup-daily')
|
|
423
|
+
* .cron('0 0 * * *')
|
|
424
|
+
* .timezone('Europe/Paris')
|
|
425
|
+
* .run()
|
|
426
|
+
*
|
|
427
|
+
* // Interval schedule
|
|
428
|
+
* await SyncJob.schedule({ source: 'api' })
|
|
429
|
+
* .every('5m')
|
|
430
|
+
* .run()
|
|
431
|
+
* ```
|
|
432
|
+
*/
|
|
433
|
+
static schedule(payload) {
|
|
434
|
+
return new ScheduleBuilder(this.jobName, payload);
|
|
435
|
+
}
|
|
251
436
|
};
|
|
252
437
|
|
|
253
438
|
// src/worker.ts
|
|
@@ -510,6 +695,7 @@ var Worker = class {
|
|
|
510
695
|
while (this.#running) {
|
|
511
696
|
try {
|
|
512
697
|
await this.#checkStalledJobs(queues);
|
|
698
|
+
await this.#dispatchDueSchedules();
|
|
513
699
|
yield* this.#fillPool(queues);
|
|
514
700
|
if (this.#pool.isEmpty()) {
|
|
515
701
|
yield { type: "idle", suggestedDelay: this.#idleDelay };
|
|
@@ -678,6 +864,156 @@ var Worker = class {
|
|
|
678
864
|
this.#shutdownHandler = void 0;
|
|
679
865
|
}
|
|
680
866
|
}
|
|
867
|
+
/**
|
|
868
|
+
* Dispatch any due scheduled jobs.
|
|
869
|
+
*
|
|
870
|
+
* Claims due schedules from the adapter and dispatches the corresponding
|
|
871
|
+
* jobs to their configured queues.
|
|
872
|
+
*/
|
|
873
|
+
async #dispatchDueSchedules() {
|
|
874
|
+
while (true) {
|
|
875
|
+
const schedule = await this.#adapter.claimDueSchedule();
|
|
876
|
+
if (!schedule) {
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
debug_default(
|
|
880
|
+
"worker %s: dispatching scheduled job %s (schedule: %s, runCount: %d)",
|
|
881
|
+
this.#id,
|
|
882
|
+
schedule.jobName,
|
|
883
|
+
schedule.id,
|
|
884
|
+
schedule.runCount + 1
|
|
885
|
+
);
|
|
886
|
+
const JobClass = Locator.get(schedule.jobName);
|
|
887
|
+
const queue = JobClass?.options?.queue ?? "default";
|
|
888
|
+
await this.#adapter.pushOn(queue, {
|
|
889
|
+
id: randomUUID2(),
|
|
890
|
+
name: schedule.jobName,
|
|
891
|
+
payload: schedule.payload,
|
|
892
|
+
attempts: 0,
|
|
893
|
+
priority: JobClass?.options?.priority
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
// src/schedule.ts
|
|
900
|
+
var Schedule = class _Schedule {
|
|
901
|
+
#data;
|
|
902
|
+
constructor(data) {
|
|
903
|
+
this.#data = data;
|
|
904
|
+
}
|
|
905
|
+
get id() {
|
|
906
|
+
return this.#data.id;
|
|
907
|
+
}
|
|
908
|
+
get jobName() {
|
|
909
|
+
return this.#data.jobName;
|
|
910
|
+
}
|
|
911
|
+
get payload() {
|
|
912
|
+
return this.#data.payload;
|
|
913
|
+
}
|
|
914
|
+
get cronExpression() {
|
|
915
|
+
return this.#data.cronExpression;
|
|
916
|
+
}
|
|
917
|
+
get everyMs() {
|
|
918
|
+
return this.#data.everyMs;
|
|
919
|
+
}
|
|
920
|
+
get timezone() {
|
|
921
|
+
return this.#data.timezone;
|
|
922
|
+
}
|
|
923
|
+
get from() {
|
|
924
|
+
return this.#data.from;
|
|
925
|
+
}
|
|
926
|
+
get to() {
|
|
927
|
+
return this.#data.to;
|
|
928
|
+
}
|
|
929
|
+
get limit() {
|
|
930
|
+
return this.#data.limit;
|
|
931
|
+
}
|
|
932
|
+
get runCount() {
|
|
933
|
+
return this.#data.runCount;
|
|
934
|
+
}
|
|
935
|
+
get nextRunAt() {
|
|
936
|
+
return this.#data.nextRunAt;
|
|
937
|
+
}
|
|
938
|
+
get lastRunAt() {
|
|
939
|
+
return this.#data.lastRunAt;
|
|
940
|
+
}
|
|
941
|
+
get status() {
|
|
942
|
+
return this.#data.status;
|
|
943
|
+
}
|
|
944
|
+
get createdAt() {
|
|
945
|
+
return this.#data.createdAt;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Find a schedule by ID.
|
|
949
|
+
*
|
|
950
|
+
* @param id - The schedule ID
|
|
951
|
+
* @returns The schedule instance, or null if not found
|
|
952
|
+
*/
|
|
953
|
+
static async find(id) {
|
|
954
|
+
const adapter = QueueManager.use();
|
|
955
|
+
const data = await adapter.getSchedule(id);
|
|
956
|
+
if (!data) return null;
|
|
957
|
+
return new _Schedule(data);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* List all schedules matching the given options.
|
|
961
|
+
*
|
|
962
|
+
* @param options - Optional filters for listing
|
|
963
|
+
* @returns Array of schedule instances
|
|
964
|
+
*/
|
|
965
|
+
static async list(options) {
|
|
966
|
+
const adapter = QueueManager.use();
|
|
967
|
+
const schedules = await adapter.listSchedules(options);
|
|
968
|
+
return schedules.map((data) => new _Schedule(data));
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Pause this schedule.
|
|
972
|
+
* No jobs will be dispatched while paused.
|
|
973
|
+
*/
|
|
974
|
+
async pause() {
|
|
975
|
+
const adapter = QueueManager.use();
|
|
976
|
+
await adapter.updateSchedule(this.#data.id, { status: "paused" });
|
|
977
|
+
this.#data.status = "paused";
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Resume this schedule.
|
|
981
|
+
* Jobs will be dispatched according to the schedule.
|
|
982
|
+
*/
|
|
983
|
+
async resume() {
|
|
984
|
+
const adapter = QueueManager.use();
|
|
985
|
+
await adapter.updateSchedule(this.#data.id, { status: "active" });
|
|
986
|
+
this.#data.status = "active";
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Delete this schedule permanently.
|
|
990
|
+
*/
|
|
991
|
+
async delete() {
|
|
992
|
+
const adapter = QueueManager.use();
|
|
993
|
+
await adapter.deleteSchedule(this.#data.id);
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Trigger immediate execution of this schedule's job.
|
|
997
|
+
* Also updates runCount and lastRunAt.
|
|
998
|
+
*
|
|
999
|
+
* If the schedule has reached its limit, the job will not be dispatched.
|
|
1000
|
+
*/
|
|
1001
|
+
async trigger() {
|
|
1002
|
+
if (this.#data.limit !== null && this.#data.runCount >= this.#data.limit) {
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const adapter = QueueManager.use();
|
|
1006
|
+
const dispatcher = new JobDispatcher(this.#data.jobName, this.#data.payload);
|
|
1007
|
+
await dispatcher.run();
|
|
1008
|
+
const now = /* @__PURE__ */ new Date();
|
|
1009
|
+
const newRunCount = this.#data.runCount + 1;
|
|
1010
|
+
await adapter.updateSchedule(this.#data.id, {
|
|
1011
|
+
runCount: newRunCount,
|
|
1012
|
+
lastRunAt: now
|
|
1013
|
+
});
|
|
1014
|
+
this.#data.runCount = newRunCount;
|
|
1015
|
+
this.#data.lastRunAt = now;
|
|
1016
|
+
}
|
|
681
1017
|
};
|
|
682
1018
|
|
|
683
1019
|
// src/strategies/backoff_strategy.ts
|
|
@@ -829,6 +1165,8 @@ export {
|
|
|
829
1165
|
Job,
|
|
830
1166
|
Locator,
|
|
831
1167
|
QueueManager,
|
|
1168
|
+
Schedule,
|
|
1169
|
+
ScheduleBuilder,
|
|
832
1170
|
Worker,
|
|
833
1171
|
customBackoff,
|
|
834
1172
|
exceptions_exports as errors,
|