@boringnode/queue 0.2.0 → 0.3.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/build/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  parse
3
- } from "./chunk-NPQKBCCY.js";
3
+ } from "./chunk-PBGPIFI5.js";
4
4
  import {
5
5
  Locator,
6
6
  QueueManager,
7
7
  debug_default
8
- } from "./chunk-RIXMQXYJ.js";
8
+ } from "./chunk-LI2ZMCNO.js";
9
9
  import {
10
10
  DEFAULT_ERROR_RETRY_DELAY,
11
11
  DEFAULT_IDLE_DELAY,
@@ -31,6 +31,7 @@ var JobDispatcher = class {
31
31
  #adapter;
32
32
  #delay;
33
33
  #priority;
34
+ #groupId;
34
35
  /**
35
36
  * Create a new job dispatcher.
36
37
  *
@@ -100,6 +101,28 @@ var JobDispatcher = class {
100
101
  this.#priority = priority;
101
102
  return this;
102
103
  }
104
+ /**
105
+ * Assign this job to a group.
106
+ *
107
+ * Jobs with the same groupId can be filtered and displayed together
108
+ * in monitoring UIs. Useful for batch operations like newsletters
109
+ * or bulk exports.
110
+ *
111
+ * @param groupId - Group identifier
112
+ * @returns This dispatcher for chaining
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * // Group newsletter jobs together
117
+ * await SendEmailJob.dispatch({ to: 'user@example.com' })
118
+ * .group('newsletter-jan-2025')
119
+ * .run()
120
+ * ```
121
+ */
122
+ group(groupId) {
123
+ this.#groupId = groupId;
124
+ return this;
125
+ }
103
126
  /**
104
127
  * Use a specific adapter for this job.
105
128
  *
@@ -139,7 +162,8 @@ var JobDispatcher = class {
139
162
  name: this.#name,
140
163
  payload: this.#payload,
141
164
  attempts: 0,
142
- priority: this.#priority
165
+ priority: this.#priority,
166
+ groupId: this.#groupId
143
167
  };
144
168
  if (this.#delay) {
145
169
  const parsedDelay = parse(this.#delay);
@@ -174,6 +198,144 @@ var JobDispatcher = class {
174
198
  }
175
199
  };
176
200
 
201
+ // src/job_batch_dispatcher.ts
202
+ import { randomUUID as randomUUID2 } from "crypto";
203
+ var JobBatchDispatcher = class {
204
+ #name;
205
+ #payloads;
206
+ #queue = "default";
207
+ #adapter;
208
+ #priority;
209
+ #groupId;
210
+ /**
211
+ * Create a new batch job dispatcher.
212
+ *
213
+ * @param name - The job class name (used to locate the class at runtime)
214
+ * @param payloads - Array of data to pass to each job
215
+ */
216
+ constructor(name, payloads) {
217
+ this.#name = name;
218
+ this.#payloads = payloads;
219
+ }
220
+ /**
221
+ * Set the target queue for all jobs.
222
+ *
223
+ * @param queue - Queue name (default: 'default')
224
+ * @returns This dispatcher for chaining
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * await SendEmailJob.dispatchMany(payloads).toQueue('emails')
229
+ * ```
230
+ */
231
+ toQueue(queue) {
232
+ this.#queue = queue;
233
+ return this;
234
+ }
235
+ /**
236
+ * Set the priority for all jobs.
237
+ *
238
+ * Lower numbers = higher priority. Jobs with lower priority values
239
+ * are processed before jobs with higher values.
240
+ *
241
+ * @param priority - Priority level (1-10, default: 5)
242
+ * @returns This dispatcher for chaining
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * await UrgentJob.dispatchMany(payloads).priority(1)
247
+ * ```
248
+ */
249
+ priority(priority) {
250
+ this.#priority = priority;
251
+ return this;
252
+ }
253
+ /**
254
+ * Assign all jobs to a group.
255
+ *
256
+ * Jobs with the same groupId can be filtered and displayed together
257
+ * in monitoring UIs. Useful for batch operations like newsletters
258
+ * or bulk exports.
259
+ *
260
+ * @param groupId - Group identifier
261
+ * @returns This dispatcher for chaining
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * await SendEmailJob.dispatchMany(recipients)
266
+ * .group('newsletter-jan-2025')
267
+ * .run()
268
+ * ```
269
+ */
270
+ group(groupId) {
271
+ this.#groupId = groupId;
272
+ return this;
273
+ }
274
+ /**
275
+ * Use a specific adapter for these jobs.
276
+ *
277
+ * @param adapter - Adapter name or factory function
278
+ * @returns This dispatcher for chaining
279
+ *
280
+ * @example
281
+ * ```typescript
282
+ * await Job.dispatchMany(payloads).with('redis')
283
+ * ```
284
+ */
285
+ with(adapter) {
286
+ this.#adapter = adapter;
287
+ return this;
288
+ }
289
+ /**
290
+ * Dispatch all jobs to the queue.
291
+ *
292
+ * @returns A DispatchManyResult containing all jobIds
293
+ *
294
+ * @example
295
+ * ```typescript
296
+ * const { jobIds } = await SendEmailJob.dispatchMany(payloads).run()
297
+ * console.log(`Dispatched ${jobIds.length} jobs`)
298
+ * ```
299
+ */
300
+ async run() {
301
+ debug_default("dispatching %d jobs of type %s", this.#payloads.length, this.#name);
302
+ const adapter = this.#getAdapterInstance();
303
+ const jobs = this.#payloads.map((payload) => ({
304
+ id: randomUUID2(),
305
+ name: this.#name,
306
+ payload,
307
+ attempts: 0,
308
+ priority: this.#priority,
309
+ groupId: this.#groupId
310
+ }));
311
+ await adapter.pushManyOn(this.#queue, jobs);
312
+ return {
313
+ jobIds: jobs.map((job) => job.id)
314
+ };
315
+ }
316
+ /**
317
+ * Thenable implementation for auto-dispatch when awaited.
318
+ *
319
+ * Allows `await Job.dispatchMany(payloads)` without explicit `.run()`.
320
+ *
321
+ * @param onFulfilled - Success callback
322
+ * @param onRejected - Error callback
323
+ * @returns Promise resolving to the DispatchManyResult
324
+ */
325
+ then(onFulfilled, onRejected) {
326
+ return this.run().then(onFulfilled, onRejected);
327
+ }
328
+ #getAdapterInstance() {
329
+ if (!this.#adapter) {
330
+ return QueueManager.use();
331
+ }
332
+ if (typeof this.#adapter === "string") {
333
+ return QueueManager.use(this.#adapter);
334
+ }
335
+ return this.#adapter();
336
+ }
337
+ };
338
+
177
339
  // src/schedule_builder.ts
178
340
  import { CronExpressionParser } from "cron-parser";
179
341
  var ScheduleBuilder = class {
@@ -461,6 +623,48 @@ var Job = class {
461
623
  }
462
624
  return dispatcher;
463
625
  }
626
+ /**
627
+ * Dispatch multiple jobs to the queue in a single batch.
628
+ *
629
+ * Returns a JobBatchDispatcher for fluent configuration before dispatching.
630
+ * The jobs are not actually dispatched until `.run()` is called or the
631
+ * dispatcher is awaited.
632
+ *
633
+ * This is more efficient than calling `dispatch()` multiple times as it
634
+ * uses batched operations (e.g., Redis pipeline, SQL batch insert).
635
+ *
636
+ * @param payloads - Array of data to pass to each job
637
+ * @returns A JobBatchDispatcher for fluent configuration
638
+ *
639
+ * @example
640
+ * ```typescript
641
+ * // Batch dispatch for newsletter
642
+ * const { jobIds } = await SendEmailJob.dispatchMany([
643
+ * { to: 'user1@example.com', subject: 'Newsletter' },
644
+ * { to: 'user2@example.com', subject: 'Newsletter' },
645
+ * ])
646
+ * .group('newsletter-jan-2025')
647
+ * .toQueue('emails')
648
+ * .run()
649
+ *
650
+ * console.log(`Dispatched ${jobIds.length} jobs`)
651
+ * ```
652
+ */
653
+ static dispatchMany(payloads) {
654
+ const options = this.options || {};
655
+ const jobName = options.name || this.name;
656
+ const dispatcher = new JobBatchDispatcher(jobName, payloads);
657
+ if (options.queue) {
658
+ dispatcher.toQueue(options.queue);
659
+ }
660
+ if (options.adapter) {
661
+ dispatcher.with(options.adapter);
662
+ }
663
+ if (options.priority !== void 0) {
664
+ dispatcher.priority(options.priority);
665
+ }
666
+ return dispatcher;
667
+ }
464
668
  /**
465
669
  * Create a schedule for this job.
466
670
  *
@@ -494,7 +698,7 @@ var Job = class {
494
698
  };
495
699
 
496
700
  // src/worker.ts
497
- import { randomUUID as randomUUID2 } from "crypto";
701
+ import { randomUUID as randomUUID3 } from "crypto";
498
702
  import { setTimeout } from "timers/promises";
499
703
 
500
704
  // src/job_pool.ts
@@ -600,7 +804,7 @@ var Worker = class {
600
804
  */
601
805
  constructor(config) {
602
806
  this.#config = config;
603
- this.#id = randomUUID2();
807
+ this.#id = randomUUID3();
604
808
  this.#idleDelay = parse(config.worker?.idleDelay ?? DEFAULT_IDLE_DELAY);
605
809
  this.#stalledInterval = parse(config.worker?.stalledInterval ?? DEFAULT_STALLED_INTERVAL);
606
810
  this.#stalledThreshold = parse(config.worker?.stalledThreshold ?? DEFAULT_STALLED_THRESHOLD);
@@ -787,23 +991,24 @@ var Worker = class {
787
991
  const startTime = performance.now();
788
992
  debug_default("worker %s: executing job %s (%s)", this.#id, job.id, job.name);
789
993
  const { instance, options, timeout, context, payload } = await this.#initJob(job, queue);
994
+ const retention = QueueManager.getMergedJobOptions(queue, options);
790
995
  try {
791
996
  await this.#executeWithTimeout(instance, payload, context, timeout);
792
- await this.#adapter.completeJob(job.id, queue);
997
+ await this.#adapter.completeJob(job.id, queue, retention.removeOnComplete);
793
998
  const duration = (performance.now() - startTime).toFixed(2);
794
999
  debug_default("worker %s: successfully executed job %s in %dms", this.#id, job.id, duration);
795
1000
  } catch (e) {
796
1001
  const isTimeout = e instanceof E_JOB_TIMEOUT;
797
1002
  if (isTimeout && options.failOnTimeout) {
798
1003
  debug_default("worker %s: job %s timed out and failOnTimeout is set", this.#id, job.id);
799
- await this.#adapter.failJob(job.id, queue, e);
1004
+ await this.#adapter.failJob(job.id, queue, e, retention.removeOnFail);
800
1005
  await instance.failed?.(e);
801
1006
  return;
802
1007
  }
803
1008
  const mergedConfig = QueueManager.getMergedRetryConfig(queue, options.retry);
804
1009
  if (typeof mergedConfig.maxRetries === "undefined" || mergedConfig.maxRetries <= 0) {
805
1010
  debug_default("worker %s: job %s has no retries configured, marking as failed", this.#id, job.id);
806
- await this.#adapter.failJob(job.id, queue, e);
1011
+ await this.#adapter.failJob(job.id, queue, e, retention.removeOnFail);
807
1012
  await instance.failed?.(e);
808
1013
  return;
809
1014
  }
@@ -814,7 +1019,7 @@ var Worker = class {
814
1019
  job.id,
815
1020
  mergedConfig.maxRetries
816
1021
  );
817
- await this.#adapter.failJob(job.id, queue, e);
1022
+ await this.#adapter.failJob(job.id, queue, e, retention.removeOnFail);
818
1023
  const exception = new E_JOB_MAX_ATTEMPTS_REACHED([job.name]);
819
1024
  await instance.failed?.(exception);
820
1025
  return;
@@ -848,7 +1053,8 @@ var Worker = class {
848
1053
  return { instance, options, timeout, context, payload: job.payload };
849
1054
  } catch (error) {
850
1055
  debug_default("worker %s: failed to initialize job %s (%s)", this.#id, job.id, job.name);
851
- await this.#adapter.failJob(job.id, queue, error);
1056
+ const retention = QueueManager.getMergedJobOptions(queue);
1057
+ await this.#adapter.failJob(job.id, queue, error, retention.removeOnFail);
852
1058
  throw error;
853
1059
  }
854
1060
  }
@@ -946,7 +1152,7 @@ var Worker = class {
946
1152
  const JobClass = Locator.get(schedule.name);
947
1153
  const queue = JobClass?.options?.queue ?? "default";
948
1154
  await this.#adapter.pushOn(queue, {
949
- id: randomUUID2(),
1155
+ id: randomUUID3(),
950
1156
  name: schedule.name,
951
1157
  payload: schedule.payload,
952
1158
  attempts: 0,
@@ -1223,6 +1429,7 @@ function customBackoff(config) {
1223
1429
  }
1224
1430
  export {
1225
1431
  Job,
1432
+ JobBatchDispatcher,
1226
1433
  Locator,
1227
1434
  QueueManager,
1228
1435
  Schedule,