@orion-js/dogs 4.6.4 → 4.6.5

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.cjs CHANGED
@@ -92,27 +92,169 @@ __export(index_exports, {
92
92
  startWorkers: () => startWorkers
93
93
  });
94
94
  module.exports = __toCommonJS(index_exports);
95
- var import_services5 = require("@orion-js/services");
95
+ var import_services6 = require("@orion-js/services");
96
96
 
97
- // src/services/EventsService.ts
98
- var import_logger2 = require("@orion-js/logger");
99
- var import_services = require("@orion-js/services");
97
+ // src/repos/JobsHistoryRepo.ts
98
+ var import_mongodb = require("@orion-js/mongodb");
99
+
100
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/isInteger.js
101
+ function _isInteger(n) {
102
+ return n << 0 === n;
103
+ }
104
+ var isInteger = Number.isInteger || _isInteger;
105
+
106
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/createPath.js
107
+ function createPath(path, delimiter = ".") {
108
+ return typeof path === "string" ? path.split(delimiter).map((x) => isInteger(x) ? Number(x) : x) : path;
109
+ }
110
+
111
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/compare.js
112
+ function compare(a, b) {
113
+ return String(a) === String(b);
114
+ }
115
+
116
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/includes.js
117
+ function includes(a, list) {
118
+ let index = -1;
119
+ const { length } = list;
120
+ while (++index < length)
121
+ if (compare(list[index], a))
122
+ return true;
123
+ return false;
124
+ }
125
+
126
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/omit.js
127
+ function omit(propsToOmit, obj) {
128
+ if (arguments.length === 1) return (_obj) => omit(propsToOmit, _obj);
129
+ if (obj === null || obj === void 0)
130
+ return void 0;
131
+ const propsToOmitValue = createPath(propsToOmit, ",");
132
+ const willReturn = {};
133
+ for (const key in obj)
134
+ if (!includes(key, propsToOmitValue))
135
+ willReturn[key] = obj[key];
136
+ return willReturn;
137
+ }
138
+
139
+ // src/types/HistoryRecord.ts
140
+ var import_schema = require("@orion-js/schema");
141
+ var HistoryRecordSchema = (0, import_schema.schemaWithName)("HistoryRecord", {
142
+ _id: { type: "string" },
143
+ jobId: { type: "string" },
144
+ executionId: { type: "string" },
145
+ jobName: { type: "string" },
146
+ type: { type: "string" },
147
+ priority: { type: "number" },
148
+ tries: { type: "number" },
149
+ uniqueIdentifier: { type: "string", optional: true },
150
+ startedAt: { type: "date" },
151
+ endedAt: { type: "date" },
152
+ duration: { type: "number" },
153
+ expiresAt: { type: "date", optional: true },
154
+ status: { type: "string", enum: ["success", "error", "stale"] },
155
+ errorMessage: { type: "string", optional: true },
156
+ params: { type: "blackbox", optional: true },
157
+ result: { type: "any", optional: true }
158
+ });
159
+
160
+ // src/repos/JobsHistoryRepo.ts
161
+ var _history_dec, _JobsHistoryRepo_decorators, _init;
162
+ _JobsHistoryRepo_decorators = [(0, import_mongodb.Repository)()], _history_dec = [(0, import_mongodb.MongoCollection)({
163
+ name: "orionjs.jobs_dogs_history",
164
+ idGeneration: "uuid",
165
+ schema: HistoryRecordSchema,
166
+ indexes: [
167
+ {
168
+ keys: {
169
+ jobName: 1,
170
+ startedAt: 1
171
+ }
172
+ },
173
+ {
174
+ keys: {
175
+ executionId: 1
176
+ }
177
+ },
178
+ {
179
+ keys: {
180
+ expiresAt: 1
181
+ },
182
+ options: {
183
+ expireAfterSeconds: 0
184
+ }
185
+ }
186
+ ]
187
+ })];
188
+ var JobsHistoryRepo = class {
189
+ constructor() {
190
+ __publicField(this, "history", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
191
+ }
192
+ async saveExecution(record) {
193
+ await this.history.upsert(
194
+ { executionId: record.executionId },
195
+ {
196
+ $setOnInsert: {
197
+ status: record.status
198
+ },
199
+ $set: {
200
+ ...omit(["status"], record)
201
+ }
202
+ }
203
+ );
204
+ }
205
+ async getExecutions(jobName, limit, skip) {
206
+ const cursor = this.history.find({ jobName }).sort({ startedAt: -1 });
207
+ if (skip) {
208
+ cursor.skip(skip);
209
+ }
210
+ if (limit) {
211
+ cursor.limit(limit);
212
+ }
213
+ return await cursor.toArray();
214
+ }
215
+ };
216
+ _init = __decoratorStart(null);
217
+ __decorateElement(_init, 5, "history", _history_dec, JobsHistoryRepo);
218
+ JobsHistoryRepo = __decorateElement(_init, 0, "JobsHistoryRepo", _JobsHistoryRepo_decorators, JobsHistoryRepo);
219
+ __runInitializers(_init, 1, JobsHistoryRepo);
100
220
 
101
221
  // src/repos/JobsRepo.ts
102
222
  var import_helpers = require("@orion-js/helpers");
103
223
  var import_logger = require("@orion-js/logger");
104
- var import_mongodb = require("@orion-js/mongodb");
224
+ var import_mongodb2 = require("@orion-js/mongodb");
225
+
226
+ // src/services/partitions.ts
227
+ function validatePartitionsCount(nPartitions) {
228
+ if (!Number.isInteger(nPartitions) || nPartitions <= 0) {
229
+ throw new Error("nPartitions must be a positive integer");
230
+ }
231
+ }
232
+ function getRandomPartition(nPartitions) {
233
+ return Math.floor(Math.random() * nPartitions);
234
+ }
235
+ function createShuffledPartitions(nPartitions) {
236
+ const partitions = Array.from({ length: nPartitions }, (_, partition) => partition);
237
+ for (let index = partitions.length - 1; index > 0; index--) {
238
+ const swapIndex = Math.floor(Math.random() * (index + 1));
239
+ [partitions[index], partitions[swapIndex]] = [partitions[swapIndex], partitions[index]];
240
+ }
241
+ return partitions;
242
+ }
105
243
 
106
244
  // src/types/JobRecord.ts
107
- var import_schema = require("@orion-js/schema");
108
- var JobStatusEnum = (0, import_schema.createEnum)("JobStatus", ["pending", "maxTriesReached"]);
109
- var JobRecordSchema = (0, import_schema.schemaWithName)("JobRecord", {
245
+ var import_schema2 = require("@orion-js/schema");
246
+ var JobStatusEnum = (0, import_schema2.createEnum)("JobStatus", ["pending", "maxTriesReached"]);
247
+ var JobRecordSchema = (0, import_schema2.schemaWithName)("JobRecord", {
110
248
  _id: { type: "string" },
111
249
  jobName: { type: "string" },
112
- type: { type: (0, import_schema.createEnum)("JobType", ["recurrent", "event"]) },
250
+ type: { type: (0, import_schema2.createEnum)("JobType", ["recurrent", "event"]) },
113
251
  priority: { type: "number" },
114
252
  uniqueIdentifier: { type: "string", optional: true },
115
253
  nextRunAt: { type: "date" },
254
+ /**
255
+ * Acquisition partition. Optional so records created by previous versions can be reconciled.
256
+ */
257
+ partition: { type: "number", optional: true },
116
258
  lastRunAt: { type: "date", optional: true },
117
259
  lockedUntil: { type: "date", optional: true },
118
260
  /**
@@ -120,6 +262,11 @@ var JobRecordSchema = (0, import_schema.schemaWithName)("JobRecord", {
120
262
  * this value so a stale execution cannot modify a newer claim.
121
263
  */
122
264
  lockId: { type: "string", optional: true },
265
+ /**
266
+ * Internal acquisition metadata used when MongoDB supports sorted updateOne. It preserves the
267
+ * pre-update stale state because updateOne intentionally does not return the previous document.
268
+ */
269
+ claimWasStale: { type: "boolean", optional: true },
123
270
  tries: { type: "number", optional: true },
124
271
  params: { type: "blackbox", optional: true },
125
272
  /**
@@ -144,23 +291,30 @@ var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
144
291
  // src/repos/JobsRepo.ts
145
292
  var JOB_ACQUISITION_HINTS = {
146
293
  byJobName: {
294
+ partition: 1,
147
295
  jobName: 1,
148
296
  priority: -1,
149
297
  nextRunAt: 1
150
298
  },
151
299
  globalPriority: {
300
+ partition: 1,
152
301
  priority: -1,
153
302
  nextRunAt: 1
154
303
  }
155
304
  };
156
305
  var INITIAL_JOB_ACQUISITION_HINT = "byJobName";
157
306
  var CANDIDATE_JOB_ACQUISITION_HINT = "globalPriority";
307
+ var SORTED_UPDATE_ONE_MIN_WIRE_VERSION = 25;
308
+ var LOCK_ID_INDEX_NAME = "jobs_dogs_records_lock_id";
309
+ var PARTITION_JOB_NAME_INDEX_NAME = "jobs_dogs_records_partition_job_name_priority_next_run";
310
+ var PARTITION_PRIORITY_INDEX_NAME = "jobs_dogs_records_partition_priority_next_run";
158
311
  var JOB_ACQUISITION_SORT = {
159
312
  priority: -1,
160
313
  nextRunAt: 1
161
314
  };
162
- function getJobAcquisitionSelector(jobNames, now) {
315
+ function getJobAcquisitionSelector(jobNames, now, partition) {
163
316
  return {
317
+ partition,
164
318
  jobName: { $in: jobNames },
165
319
  nextRunAt: { $lte: now },
166
320
  $and: [
@@ -171,17 +325,19 @@ function getJobAcquisitionSelector(jobNames, now) {
171
325
  ]
172
326
  };
173
327
  }
174
- var _jobs_dec, _JobsRepo_decorators, _init;
175
- _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, import_mongodb.MongoCollection)({
328
+ var _jobs_dec, _JobsRepo_decorators, _init2;
329
+ _JobsRepo_decorators = [(0, import_mongodb2.Repository)()], _jobs_dec = [(0, import_mongodb2.MongoCollection)({
176
330
  idGeneration: "uuid",
177
331
  name: "orionjs.jobs_dogs_records",
178
332
  schema: JobRecordSchema,
179
333
  indexes: [
180
334
  {
181
- keys: JOB_ACQUISITION_HINTS.byJobName
335
+ keys: JOB_ACQUISITION_HINTS.byJobName,
336
+ options: { name: PARTITION_JOB_NAME_INDEX_NAME }
182
337
  },
183
338
  {
184
- keys: JOB_ACQUISITION_HINTS.globalPriority
339
+ keys: JOB_ACQUISITION_HINTS.globalPriority,
340
+ options: { name: PARTITION_PRIORITY_INDEX_NAME }
185
341
  },
186
342
  {
187
343
  keys: {
@@ -201,6 +357,15 @@ _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, impo
201
357
  sparse: true
202
358
  }
203
359
  },
360
+ {
361
+ keys: {
362
+ lockId: 1
363
+ },
364
+ options: {
365
+ name: LOCK_ID_INDEX_NAME,
366
+ sparse: true
367
+ }
368
+ },
204
369
  {
205
370
  keys: {
206
371
  expiresAt: 1
@@ -212,14 +377,55 @@ _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, impo
212
377
  })];
213
378
  var JobsRepo = class {
214
379
  constructor() {
215
- __publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
380
+ __publicField(this, "jobs", __runInitializers(_init2, 8, this)), __runInitializers(_init2, 11, this);
381
+ __publicField(this, "sortedUpdateOneSupportPromise");
382
+ __publicField(this, "partitionsByJobName", /* @__PURE__ */ new Map());
383
+ }
384
+ configureJobPartitions(jobNames, nPartitions) {
385
+ for (const jobName of jobNames) {
386
+ this.partitionsByJobName.set(jobName, nPartitions);
387
+ }
388
+ }
389
+ getJobPartitionsCount(jobName) {
390
+ return this.partitionsByJobName.get(jobName);
391
+ }
392
+ async supportsSortedUpdateOne() {
393
+ if (!this.sortedUpdateOneSupportPromise) {
394
+ this.sortedUpdateOneSupportPromise = this.detectSortedUpdateOneSupport();
395
+ }
396
+ return this.sortedUpdateOneSupportPromise;
397
+ }
398
+ async detectSortedUpdateOneSupport() {
399
+ const rawCollection = await this.jobs.getRawCollection();
400
+ await this.jobs.createIndexesPromise;
401
+ const hello = await rawCollection.db.admin().command({ hello: 1 });
402
+ const supportsSortedUpdateOne = typeof hello.maxWireVersion === "number" && hello.maxWireVersion >= SORTED_UPDATE_ONE_MIN_WIRE_VERSION;
403
+ if (!supportsSortedUpdateOne) {
404
+ import_logger.logger.info("MongoDB does not support sorted updateOne; using findOneAndUpdate for jobs.");
405
+ return false;
406
+ }
407
+ const hasLockIdIndex = await rawCollection.indexExists(LOCK_ID_INDEX_NAME);
408
+ if (!hasLockIdIndex) {
409
+ import_logger.logger.warn(
410
+ `MongoDB supports sorted updateOne but index "${LOCK_ID_INDEX_NAME}" is unavailable; using findOneAndUpdate for jobs.`
411
+ );
412
+ return false;
413
+ }
414
+ import_logger.logger.info("MongoDB supports sorted updateOne; using it for job acquisition.");
415
+ return true;
416
+ }
417
+ async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT, partition = 0) {
418
+ if (await this.supportsSortedUpdateOne()) {
419
+ return this.getJobAndLockWithSortedUpdateOne(jobNames, lockTime, hint, partition);
420
+ }
421
+ return this.getJobAndLockWithFindOneAndUpdate(jobNames, lockTime, hint, partition);
216
422
  }
217
- async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
423
+ async getJobAndLockWithFindOneAndUpdate(jobNames, lockTime, hint, partition) {
218
424
  const executionId = (0, import_helpers.generateId)();
219
425
  const now = /* @__PURE__ */ new Date();
220
426
  const lockedUntil = new Date(now.getTime() + lockTime);
221
427
  const job = await this.jobs.findOneAndUpdate(
222
- getJobAcquisitionSelector(jobNames, now),
428
+ getJobAcquisitionSelector(jobNames, now, partition),
223
429
  {
224
430
  $set: { lockId: executionId, lockedUntil, lastRunAt: now },
225
431
  $inc: { tries: 1 }
@@ -235,6 +441,37 @@ var JobsRepo = class {
235
441
  if (!job) return;
236
442
  const tries = (job.tries || 0) + 1;
237
443
  const wasStale = Boolean(job.lockedUntil);
444
+ return this.toJobToRun(job, executionId, lockTime, tries, wasStale);
445
+ }
446
+ async getJobAndLockWithSortedUpdateOne(jobNames, lockTime, hint, partition) {
447
+ const executionId = (0, import_helpers.generateId)();
448
+ const now = /* @__PURE__ */ new Date();
449
+ const lockedUntil = new Date(now.getTime() + lockTime);
450
+ const rawCollection = await this.jobs.getRawCollection();
451
+ const result = await rawCollection.updateOne(
452
+ getJobAcquisitionSelector(jobNames, now, partition),
453
+ [
454
+ {
455
+ $set: {
456
+ lockId: { $literal: executionId },
457
+ lockedUntil: { $literal: lockedUntil },
458
+ lastRunAt: { $literal: now },
459
+ tries: { $add: [{ $ifNull: ["$tries", 0] }, 1] },
460
+ claimWasStale: { $eq: [{ $type: "$lockedUntil" }, "date"] }
461
+ }
462
+ }
463
+ ],
464
+ {
465
+ hint: JOB_ACQUISITION_HINTS[hint],
466
+ sort: JOB_ACQUISITION_SORT
467
+ }
468
+ );
469
+ if (result.matchedCount === 0) return;
470
+ const job = await rawCollection.findOne({ lockId: executionId });
471
+ if (!job) return;
472
+ return this.toJobToRun(job, executionId, lockTime, job.tries || 0, Boolean(job.claimWasStale));
473
+ }
474
+ toJobToRun(job, executionId, lockTime, tries, wasStale) {
238
475
  if (wasStale) {
239
476
  import_logger.logger.info(`Running job "${job.jobName}" that was staled`);
240
477
  }
@@ -252,9 +489,9 @@ var JobsRepo = class {
252
489
  wasStale
253
490
  };
254
491
  }
255
- async getJobAcquisitionExecutionTime(jobNames, hint) {
492
+ async getJobAcquisitionExecutionTime(jobNames, hint, partition = 0) {
256
493
  var _a;
257
- const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date()), {
494
+ const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date(), partition), {
258
495
  hint: JOB_ACQUISITION_HINTS[hint],
259
496
  maxTimeMS: 1e3,
260
497
  readPreference: "primary"
@@ -278,7 +515,7 @@ var JobsRepo = class {
278
515
  priority: options.priority,
279
516
  ...options.resetTries ? { tries: 0 } : {}
280
517
  },
281
- $unset: { lockId: "", lockedUntil: "" }
518
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
282
519
  };
283
520
  const result = await this.jobs.updateOne(
284
521
  options.lockId ? { _id: options.jobId, lockId: options.lockId } : options.jobId,
@@ -312,6 +549,7 @@ var JobsRepo = class {
312
549
  $unset: {
313
550
  lockId: "",
314
551
  lockedUntil: "",
552
+ claimWasStale: "",
315
553
  ...retentionMs === null ? { expiresAt: "" } : {}
316
554
  }
317
555
  }
@@ -368,12 +606,12 @@ var JobsRepo = class {
368
606
  lockedUntil: { $exists: true }
369
607
  },
370
608
  {
371
- $unset: { lockId: "", lockedUntil: "" }
609
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
372
610
  }
373
611
  );
374
612
  return result.modifiedCount;
375
613
  }
376
- async ensureJobRecord(job) {
614
+ async ensureJobRecord(job, partition = 0) {
377
615
  const result = await this.jobs.upsert(
378
616
  {
379
617
  jobName: job.name
@@ -387,7 +625,8 @@ var JobsRepo = class {
387
625
  status: ""
388
626
  },
389
627
  $setOnInsert: {
390
- nextRunAt: /* @__PURE__ */ new Date()
628
+ nextRunAt: /* @__PURE__ */ new Date(),
629
+ partition
391
630
  }
392
631
  }
393
632
  );
@@ -405,7 +644,8 @@ var JobsRepo = class {
405
644
  params: options.params,
406
645
  nextRunAt: options.nextRunAt,
407
646
  priority: options.priority,
408
- type: "event"
647
+ type: "event",
648
+ ...options.partition === void 0 ? {} : { partition: options.partition }
409
649
  });
410
650
  } catch (error) {
411
651
  if (error.isValidationError && Object.values(error.validationErrors).includes("notUnique") && options.uniqueIdentifier) {
@@ -433,7 +673,8 @@ var JobsRepo = class {
433
673
  params: job.params,
434
674
  nextRunAt: job.nextRunAt,
435
675
  priority: job.priority,
436
- type: "event"
676
+ type: "event",
677
+ ...job.partition === void 0 ? {} : { partition: job.partition }
437
678
  });
438
679
  scheduledCount++;
439
680
  } catch (error) {
@@ -458,11 +699,55 @@ var JobsRepo = class {
458
699
  errors
459
700
  };
460
701
  }
702
+ async reconcilePartitions(options) {
703
+ if (options.jobNames.length === 0) return 0;
704
+ const rawCollection = await this.jobs.getRawCollection();
705
+ await this.jobs.createIndexesPromise;
706
+ const now = /* @__PURE__ */ new Date();
707
+ const invalidPartition = {
708
+ $or: [
709
+ { partition: { $exists: false } },
710
+ { partition: { $lt: 0 } },
711
+ { partition: { $gte: options.nPartitions } }
712
+ ]
713
+ };
714
+ const availableLock = {
715
+ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: now } }]
716
+ };
717
+ const runnableJob = {
718
+ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }]
719
+ };
720
+ const jobs = await rawCollection.find({
721
+ jobName: { $in: options.jobNames },
722
+ $and: [invalidPartition, availableLock, runnableJob]
723
+ }).project({ _id: 1 }).limit(options.batchSize).toArray();
724
+ if (jobs.length === 0) return 0;
725
+ const result = await rawCollection.bulkWrite(
726
+ jobs.map((job) => ({
727
+ updateOne: {
728
+ filter: {
729
+ _id: job._id,
730
+ $and: [invalidPartition, availableLock, runnableJob]
731
+ },
732
+ update: { $set: { partition: getRandomPartition(options.nPartitions) } }
733
+ }
734
+ })),
735
+ { ordered: false }
736
+ );
737
+ return result.modifiedCount;
738
+ }
461
739
  };
462
- _init = __decoratorStart(null);
463
- __decorateElement(_init, 5, "jobs", _jobs_dec, JobsRepo);
464
- JobsRepo = __decorateElement(_init, 0, "JobsRepo", _JobsRepo_decorators, JobsRepo);
465
- __runInitializers(_init, 1, JobsRepo);
740
+ _init2 = __decoratorStart(null);
741
+ __decorateElement(_init2, 5, "jobs", _jobs_dec, JobsRepo);
742
+ JobsRepo = __decorateElement(_init2, 0, "JobsRepo", _JobsRepo_decorators, JobsRepo);
743
+ __runInitializers(_init2, 1, JobsRepo);
744
+
745
+ // src/schedule.ts
746
+ var import_services2 = require("@orion-js/services");
747
+
748
+ // src/services/EventsService.ts
749
+ var import_logger2 = require("@orion-js/logger");
750
+ var import_services = require("@orion-js/services");
466
751
 
467
752
  // src/services/getNextRunDate.ts
468
753
  var import_cron_parser = require("cron-parser");
@@ -492,176 +777,65 @@ var getNextRunDate = (options) => {
492
777
  };
493
778
 
494
779
  // src/services/EventsService.ts
495
- var _jobsRepo_dec, _EventsService_decorators, _init2;
780
+ var _jobsRepo_dec, _EventsService_decorators, _init3;
496
781
  _EventsService_decorators = [(0, import_services.Service)()], _jobsRepo_dec = [(0, import_services.Inject)(() => JobsRepo)];
497
782
  var EventsService = class {
498
783
  constructor() {
499
- __publicField(this, "jobsRepo", __runInitializers(_init2, 8, this)), __runInitializers(_init2, 11, this);
784
+ __publicField(this, "jobsRepo", __runInitializers(_init3, 8, this)), __runInitializers(_init3, 11, this);
500
785
  }
501
- async scheduleJob(options) {
786
+ async scheduleJob(options, nPartitions) {
502
787
  import_logger2.logger.debug("Scheduling job...", options);
788
+ const configuredPartitions = nPartitions ?? this.jobsRepo.getJobPartitionsCount(options.name);
503
789
  await this.jobsRepo.scheduleJob({
504
790
  name: options.name,
505
791
  priority: options.priority || 100,
506
792
  nextRunAt: getNextRunDate(options),
507
793
  params: options.params || null,
508
- uniqueIdentifier: options.uniqueIdentifier
794
+ uniqueIdentifier: options.uniqueIdentifier,
795
+ partition: configuredPartitions === void 0 ? void 0 : getRandomPartition(configuredPartitions)
509
796
  });
510
797
  }
511
- async scheduleJobs(jobs) {
798
+ async scheduleJobs(jobs, nPartitions) {
512
799
  import_logger2.logger.debug(`Scheduling ${jobs.length} jobs...`);
513
- const jobRecords = jobs.map((options) => ({
514
- name: options.name,
515
- priority: options.priority || 100,
516
- nextRunAt: getNextRunDate(options),
517
- params: options.params || null,
518
- uniqueIdentifier: options.uniqueIdentifier
519
- }));
800
+ const jobRecords = jobs.map((options) => {
801
+ const configuredPartitions = nPartitions ?? this.jobsRepo.getJobPartitionsCount(options.name);
802
+ return {
803
+ name: options.name,
804
+ priority: options.priority || 100,
805
+ nextRunAt: getNextRunDate(options),
806
+ params: options.params || null,
807
+ uniqueIdentifier: options.uniqueIdentifier,
808
+ partition: configuredPartitions === void 0 ? void 0 : getRandomPartition(configuredPartitions)
809
+ };
810
+ });
520
811
  return await this.jobsRepo.scheduleJobs(jobRecords);
521
812
  }
522
813
  };
523
- _init2 = __decoratorStart(null);
524
- __decorateElement(_init2, 5, "jobsRepo", _jobsRepo_dec, EventsService);
525
- EventsService = __decorateElement(_init2, 0, "EventsService", _EventsService_decorators, EventsService);
526
- __runInitializers(_init2, 1, EventsService);
814
+ _init3 = __decoratorStart(null);
815
+ __decorateElement(_init3, 5, "jobsRepo", _jobsRepo_dec, EventsService);
816
+ EventsService = __decorateElement(_init3, 0, "EventsService", _EventsService_decorators, EventsService);
817
+ __runInitializers(_init3, 1, EventsService);
818
+
819
+ // src/schedule.ts
820
+ var eventsService = (0, import_services2.getInstance)(EventsService);
821
+ function scheduleJobInternal(options, nPartitions) {
822
+ return eventsService.scheduleJob(options, nPartitions);
823
+ }
824
+ function scheduleJobsInternal(jobs, nPartitions) {
825
+ return eventsService.scheduleJobs(jobs, nPartitions);
826
+ }
527
827
 
528
828
  // src/services/WorkerService.ts
529
829
  var import_helpers2 = require("@orion-js/helpers");
530
830
  var import_logger4 = require("@orion-js/logger");
531
- var import_services3 = require("@orion-js/services");
831
+ var import_services4 = require("@orion-js/services");
532
832
 
533
833
  // src/services/Executor.ts
534
834
  var import_api = require("@opentelemetry/api");
535
835
  var import_logger3 = require("@orion-js/logger");
536
- var import_services2 = require("@orion-js/services");
537
-
538
- // src/repos/JobsHistoryRepo.ts
539
- var import_mongodb2 = require("@orion-js/mongodb");
540
-
541
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/isInteger.js
542
- function _isInteger(n) {
543
- return n << 0 === n;
544
- }
545
- var isInteger = Number.isInteger || _isInteger;
546
-
547
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/createPath.js
548
- function createPath(path, delimiter = ".") {
549
- return typeof path === "string" ? path.split(delimiter).map((x) => isInteger(x) ? Number(x) : x) : path;
550
- }
551
-
552
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/compare.js
553
- function compare(a, b) {
554
- return String(a) === String(b);
555
- }
556
-
557
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/includes.js
558
- function includes(a, list) {
559
- let index = -1;
560
- const { length } = list;
561
- while (++index < length)
562
- if (compare(list[index], a))
563
- return true;
564
- return false;
565
- }
566
-
567
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/omit.js
568
- function omit(propsToOmit, obj) {
569
- if (arguments.length === 1) return (_obj) => omit(propsToOmit, _obj);
570
- if (obj === null || obj === void 0)
571
- return void 0;
572
- const propsToOmitValue = createPath(propsToOmit, ",");
573
- const willReturn = {};
574
- for (const key in obj)
575
- if (!includes(key, propsToOmitValue))
576
- willReturn[key] = obj[key];
577
- return willReturn;
578
- }
579
-
580
- // src/types/HistoryRecord.ts
581
- var import_schema2 = require("@orion-js/schema");
582
- var HistoryRecordSchema = (0, import_schema2.schemaWithName)("HistoryRecord", {
583
- _id: { type: "string" },
584
- jobId: { type: "string" },
585
- executionId: { type: "string" },
586
- jobName: { type: "string" },
587
- type: { type: "string" },
588
- priority: { type: "number" },
589
- tries: { type: "number" },
590
- uniqueIdentifier: { type: "string", optional: true },
591
- startedAt: { type: "date" },
592
- endedAt: { type: "date" },
593
- duration: { type: "number" },
594
- expiresAt: { type: "date", optional: true },
595
- status: { type: "string", enum: ["success", "error", "stale"] },
596
- errorMessage: { type: "string", optional: true },
597
- params: { type: "blackbox", optional: true },
598
- result: { type: "any", optional: true }
599
- });
600
-
601
- // src/repos/JobsHistoryRepo.ts
602
- var _history_dec, _JobsHistoryRepo_decorators, _init3;
603
- _JobsHistoryRepo_decorators = [(0, import_mongodb2.Repository)()], _history_dec = [(0, import_mongodb2.MongoCollection)({
604
- name: "orionjs.jobs_dogs_history",
605
- idGeneration: "uuid",
606
- schema: HistoryRecordSchema,
607
- indexes: [
608
- {
609
- keys: {
610
- jobName: 1,
611
- startedAt: 1
612
- }
613
- },
614
- {
615
- keys: {
616
- executionId: 1
617
- }
618
- },
619
- {
620
- keys: {
621
- expiresAt: 1
622
- },
623
- options: {
624
- expireAfterSeconds: 0
625
- }
626
- }
627
- ]
628
- })];
629
- var JobsHistoryRepo = class {
630
- constructor() {
631
- __publicField(this, "history", __runInitializers(_init3, 8, this)), __runInitializers(_init3, 11, this);
632
- }
633
- async saveExecution(record) {
634
- await this.history.upsert(
635
- { executionId: record.executionId },
636
- {
637
- $setOnInsert: {
638
- status: record.status
639
- },
640
- $set: {
641
- ...omit(["status"], record)
642
- }
643
- }
644
- );
645
- }
646
- async getExecutions(jobName, limit, skip) {
647
- const cursor = this.history.find({ jobName }).sort({ startedAt: -1 });
648
- if (skip) {
649
- cursor.skip(skip);
650
- }
651
- if (limit) {
652
- cursor.limit(limit);
653
- }
654
- return await cursor.toArray();
655
- }
656
- };
657
- _init3 = __decoratorStart(null);
658
- __decorateElement(_init3, 5, "history", _history_dec, JobsHistoryRepo);
659
- JobsHistoryRepo = __decorateElement(_init3, 0, "JobsHistoryRepo", _JobsHistoryRepo_decorators, JobsHistoryRepo);
660
- __runInitializers(_init3, 1, JobsHistoryRepo);
661
-
662
- // src/services/Executor.ts
836
+ var import_services3 = require("@orion-js/services");
663
837
  var _jobsHistoryRepo_dec, _jobsRepo_dec2, _Executor_decorators, _init4;
664
- _Executor_decorators = [(0, import_services2.Service)()], _jobsRepo_dec2 = [(0, import_services2.Inject)(() => JobsRepo)], _jobsHistoryRepo_dec = [(0, import_services2.Inject)(() => JobsHistoryRepo)];
838
+ _Executor_decorators = [(0, import_services3.Service)()], _jobsRepo_dec2 = [(0, import_services3.Inject)(() => JobsRepo)], _jobsHistoryRepo_dec = [(0, import_services3.Inject)(() => JobsHistoryRepo)];
665
839
  var Executor = class {
666
840
  constructor() {
667
841
  __publicField(this, "jobsRepo", __runInitializers(_init4, 8, this)), __runInitializers(_init4, 11, this);
@@ -1001,10 +1175,14 @@ __runInitializers(_init4, 1, Executor);
1001
1175
  // src/services/WorkerService.ts
1002
1176
  var FILTERED_JOB_NAMES_ACQUISITION_HINT = "byJobName";
1003
1177
  var _executor_dec, _jobsRepo_dec3, _WorkerService_decorators, _init5;
1004
- _WorkerService_decorators = [(0, import_services3.Service)()], _jobsRepo_dec3 = [(0, import_services3.Inject)(() => JobsRepo)], _executor_dec = [(0, import_services3.Inject)(() => Executor)];
1178
+ _WorkerService_decorators = [(0, import_services4.Service)()], _jobsRepo_dec3 = [(0, import_services4.Inject)(() => JobsRepo)], _executor_dec = [(0, import_services4.Inject)(() => Executor)];
1005
1179
  var WorkerService = class {
1006
1180
  constructor() {
1007
1181
  __publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
1182
+ __publicField(this, "partitionReconcilerIntervalMs", 5 * 60 * 1e3);
1183
+ __publicField(this, "partitionReconcilerJitterMs", 30 * 1e3);
1184
+ __publicField(this, "partitionReconcilerBatchPauseMs", 250);
1185
+ __publicField(this, "partitionReconcilerBatchSize", 500);
1008
1186
  __publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
1009
1187
  __publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
1010
1188
  }
@@ -1089,6 +1267,15 @@ var WorkerService = class {
1089
1267
  if (capacityPromises.length === 0) return;
1090
1268
  await Promise.race([...capacityPromises, workersInstance.stopSignal]);
1091
1269
  }
1270
+ getNextPartition(config, workersInstance) {
1271
+ const partition = workersInstance.partitionOrder[workersInstance.partitionCursor];
1272
+ workersInstance.partitionCursor++;
1273
+ if (workersInstance.partitionCursor >= config.nPartitions) {
1274
+ workersInstance.partitionOrder = createShuffledPartitions(config.nPartitions);
1275
+ workersInstance.partitionCursor = 0;
1276
+ }
1277
+ return partition;
1278
+ }
1092
1279
  async runSchedulerLoop(config, workersInstance) {
1093
1280
  const jobNames = this.getJobNames(config.jobs);
1094
1281
  const executeConfig = {
@@ -1098,6 +1285,7 @@ var WorkerService = class {
1098
1285
  onMaxTriesReached: config.onMaxTriesReached
1099
1286
  };
1100
1287
  import_logger4.logger.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
1288
+ let consecutivePartitionMisses = 0;
1101
1289
  while (workersInstance.running) {
1102
1290
  let didMiss = false;
1103
1291
  let allJobNamesAtCapacity = false;
@@ -1109,21 +1297,30 @@ var WorkerService = class {
1109
1297
  break;
1110
1298
  }
1111
1299
  const jobAcquisitionHint = availableJobNames.length < jobNames.length ? FILTERED_JOB_NAMES_ACQUISITION_HINT : workersInstance.jobAcquisitionHint;
1300
+ const partition = this.getNextPartition(config, workersInstance);
1112
1301
  const jobToRun = await this.jobsRepo.getJobAndLock(
1113
1302
  availableJobNames,
1114
1303
  config.defaultLockTime,
1115
- jobAcquisitionHint
1304
+ jobAcquisitionHint,
1305
+ partition
1116
1306
  );
1117
1307
  if (!jobToRun) {
1118
- import_logger4.logger.debug("No job to run");
1119
- didMiss = true;
1120
- break;
1308
+ consecutivePartitionMisses++;
1309
+ import_logger4.logger.debug(`No job to run in partition ${partition}`);
1310
+ if (consecutivePartitionMisses >= config.nPartitions) {
1311
+ consecutivePartitionMisses = 0;
1312
+ didMiss = true;
1313
+ break;
1314
+ }
1315
+ continue;
1121
1316
  }
1317
+ consecutivePartitionMisses = 0;
1122
1318
  this.startExecution(executeConfig, workersInstance, jobToRun);
1123
1319
  if (!workersInstance.running) break;
1124
1320
  }
1125
1321
  } catch (error) {
1126
1322
  import_logger4.logger.error("Error in job scheduler.", { error });
1323
+ consecutivePartitionMisses = 0;
1127
1324
  didMiss = true;
1128
1325
  }
1129
1326
  if (!workersInstance.running) return;
@@ -1159,11 +1356,13 @@ var WorkerService = class {
1159
1356
  workersInstance.jobAcquisitionHintProbeCycle++;
1160
1357
  try {
1161
1358
  for (let sample = 0; sample < 3; sample++) {
1359
+ const partition = getRandomPartition(config.nPartitions);
1162
1360
  for (const hint of [firstHint, secondHint]) {
1163
1361
  if (!workersInstance.running) return;
1164
1362
  const executionTimeMillis = await this.jobsRepo.getJobAcquisitionExecutionTime(
1165
1363
  jobNames,
1166
- hint
1364
+ hint,
1365
+ partition
1167
1366
  );
1168
1367
  if (!workersInstance.running) return;
1169
1368
  byHint[hint].push(executionTimeMillis);
@@ -1235,6 +1434,44 @@ var WorkerService = class {
1235
1434
  await this.waitForNextJobAcquisitionHintProbe(workersInstance);
1236
1435
  }
1237
1436
  }
1437
+ async reconcilePartitionBatch(config) {
1438
+ if (typeof this.jobsRepo.reconcilePartitions !== "function") return 0;
1439
+ try {
1440
+ return await this.jobsRepo.reconcilePartitions({
1441
+ jobNames: this.getJobNames(config.jobs),
1442
+ nPartitions: config.nPartitions,
1443
+ batchSize: this.partitionReconcilerBatchSize
1444
+ });
1445
+ } catch (error) {
1446
+ import_logger4.logger.warn("Job partition reconciliation failed; it will be retried.", { error });
1447
+ return 0;
1448
+ }
1449
+ }
1450
+ async waitForNextPartitionReconcile(workersInstance, delayMs) {
1451
+ await new Promise((resolve) => {
1452
+ let resolved = false;
1453
+ const finishWait = () => {
1454
+ if (resolved) return;
1455
+ resolved = true;
1456
+ clearTimeout(workersInstance.partitionReconcilerTimer);
1457
+ workersInstance.partitionReconcilerTimer = void 0;
1458
+ workersInstance.cancelPartitionReconcilerWait = void 0;
1459
+ resolve();
1460
+ };
1461
+ workersInstance.partitionReconcilerTimer = setTimeout(finishWait, delayMs);
1462
+ workersInstance.cancelPartitionReconcilerWait = finishWait;
1463
+ });
1464
+ }
1465
+ async runPartitionReconcilerLoop(config, workersInstance, initialReconcilePromise) {
1466
+ let modifiedCount = await initialReconcilePromise;
1467
+ while (workersInstance.running) {
1468
+ const didFillBatch = modifiedCount >= this.partitionReconcilerBatchSize;
1469
+ const delayMs = didFillBatch ? this.partitionReconcilerBatchPauseMs : this.partitionReconcilerIntervalMs + Math.floor(Math.random() * (this.partitionReconcilerJitterMs + 1));
1470
+ await this.waitForNextPartitionReconcile(workersInstance, delayMs);
1471
+ if (!workersInstance.running) return;
1472
+ modifiedCount = await this.reconcilePartitionBatch(config);
1473
+ }
1474
+ }
1238
1475
  createWorkersInstanceDefinition(config) {
1239
1476
  let signalStop;
1240
1477
  const stopSignal = new Promise((resolve) => {
@@ -1250,7 +1487,10 @@ var WorkerService = class {
1250
1487
  jobAcquisitionHintProbeCycle: 0,
1251
1488
  hasCompletedInitialJobAcquisitionHintProbe: false,
1252
1489
  pendingJobAcquisitionHintWins: 0,
1490
+ partitionOrder: createShuffledPartitions(config.nPartitions),
1491
+ partitionCursor: 0,
1253
1492
  jobAcquisitionHintProbePromise: Promise.resolve(),
1493
+ partitionReconcilerPromise: Promise.resolve(),
1254
1494
  schedulerPromise: Promise.resolve(),
1255
1495
  stopSignal,
1256
1496
  signalStop,
@@ -1260,14 +1500,16 @@ var WorkerService = class {
1260
1500
  stop: async () => {
1261
1501
  if (workersInstance.stopPromise) return workersInstance.stopPromise;
1262
1502
  workersInstance.stopPromise = (async () => {
1263
- var _a;
1503
+ var _a, _b;
1264
1504
  import_logger4.logger.info("Stopping job scheduler...");
1265
1505
  workersInstance.running = false;
1266
1506
  workersInstance.signalStop();
1267
1507
  (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
1508
+ (_b = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b.call(workersInstance);
1268
1509
  await Promise.all([
1269
1510
  workersInstance.schedulerPromise,
1270
- workersInstance.jobAcquisitionHintProbePromise
1511
+ workersInstance.jobAcquisitionHintProbePromise,
1512
+ workersInstance.partitionReconcilerPromise
1271
1513
  ]);
1272
1514
  const activeCapacity = Array.from(
1273
1515
  workersInstance.activeExecutions.values(),
@@ -1285,7 +1527,7 @@ var WorkerService = class {
1285
1527
  await Promise.all(
1286
1528
  jobs.filter((job) => job.type === "recurrent").map(async (job) => {
1287
1529
  import_logger4.logger.debug(`Ensuring records for job "${job.name}"...`);
1288
- await this.jobsRepo.ensureJobRecord(job);
1530
+ await this.jobsRepo.ensureJobRecord(job, getRandomPartition(config.nPartitions));
1289
1531
  })
1290
1532
  );
1291
1533
  }
@@ -1302,29 +1544,41 @@ var WorkerService = class {
1302
1544
  * @param userConfig - Configuration for the scheduler. Required field: jobs
1303
1545
  */
1304
1546
  startWorkers(userConfig) {
1547
+ var _a, _b;
1305
1548
  const maxTriesReachedRetentionMs = userConfig.maxTriesReachedRetentionMs === void 0 ? DEFAULT_MAX_TRIES_REACHED_RETENTION_MS : userConfig.maxTriesReachedRetentionMs;
1306
1549
  if (maxTriesReachedRetentionMs !== null && (!Number.isFinite(maxTriesReachedRetentionMs) || maxTriesReachedRetentionMs < 0)) {
1307
1550
  throw new Error("maxTriesReachedRetentionMs must be null, zero, or a positive number");
1308
1551
  }
1552
+ const nPartitions = userConfig.nPartitions ?? 1;
1553
+ validatePartitionsCount(nPartitions);
1309
1554
  const config = {
1310
1555
  pollInterval: 3e3,
1311
1556
  workersCount: 4,
1312
1557
  defaultLockTime: 30 * 1e3,
1313
1558
  ...userConfig,
1559
+ nPartitions,
1314
1560
  maxTriesReachedRetentionMs
1315
1561
  };
1316
- setNameToJobs(config.jobs);
1562
+ setNameToJobs(config.jobs, config.nPartitions);
1563
+ (_b = (_a = this.jobsRepo).configureJobPartitions) == null ? void 0 : _b.call(_a, this.getJobNames(config.jobs), config.nPartitions);
1317
1564
  const workersInstance = this.createWorkersInstanceDefinition(config);
1318
1565
  import_logger4.logger.debug("Starting job scheduler", config);
1319
1566
  workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
1320
1567
  config,
1321
1568
  workersInstance
1322
1569
  );
1570
+ const initialReconcilePromise = this.reconcilePartitionBatch(config);
1571
+ workersInstance.partitionReconcilerPromise = this.runPartitionReconcilerLoop(
1572
+ config,
1573
+ workersInstance,
1574
+ initialReconcilePromise
1575
+ );
1323
1576
  workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
1324
- var _a;
1577
+ var _a2, _b2;
1325
1578
  workersInstance.running = false;
1326
1579
  workersInstance.signalStop();
1327
- (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
1580
+ (_a2 = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a2.call(workersInstance);
1581
+ (_b2 = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b2.call(workersInstance);
1328
1582
  import_logger4.logger.error("Job scheduler stopped after an unexpected error.", { error });
1329
1583
  });
1330
1584
  return workersInstance;
@@ -1335,15 +1589,13 @@ __decorateElement(_init5, 5, "jobsRepo", _jobsRepo_dec3, WorkerService);
1335
1589
  __decorateElement(_init5, 5, "executor", _executor_dec, WorkerService);
1336
1590
  WorkerService = __decorateElement(_init5, 0, "WorkerService", _WorkerService_decorators, WorkerService);
1337
1591
  __runInitializers(_init5, 1, WorkerService);
1338
- function setNameToJobs(jobs) {
1592
+ function setNameToJobs(jobs, nPartitions) {
1339
1593
  for (const name of Object.keys(jobs)) {
1340
1594
  jobs[name].jobName = name;
1595
+ jobs[name].nPartitions = nPartitions;
1341
1596
  }
1342
1597
  }
1343
1598
 
1344
- // src/service/index.ts
1345
- var import_services4 = require("@orion-js/services");
1346
-
1347
1599
  // src/defineJob/index.ts
1348
1600
  var import_schema3 = require("@orion-js/schema");
1349
1601
  var import_cron_parser2 = require("cron-parser");
@@ -1360,11 +1612,14 @@ function createEventJob(options) {
1360
1612
  throw new Error("This job has not been registered in the workers");
1361
1613
  }
1362
1614
  const params = jobDefinition.params ? await (0, import_schema3.cleanAndValidate)(jobDefinition.params, scheduleOptions.params) : scheduleOptions.params;
1363
- return await scheduleJob({
1364
- ...scheduleOptions,
1365
- name: jobDefinition.jobName,
1366
- params
1367
- });
1615
+ return await scheduleJobInternal(
1616
+ {
1617
+ ...scheduleOptions,
1618
+ name: jobDefinition.jobName,
1619
+ params
1620
+ },
1621
+ jobDefinition.nPartitions
1622
+ );
1368
1623
  };
1369
1624
  jobDefinition.scheduleJobs = async (jobs) => {
1370
1625
  if (!jobDefinition.jobName) {
@@ -1380,7 +1635,7 @@ function createEventJob(options) {
1380
1635
  };
1381
1636
  })
1382
1637
  );
1383
- return await scheduleJobs(processedJobs);
1638
+ return await scheduleJobsInternal(processedJobs, jobDefinition.nPartitions);
1384
1639
  };
1385
1640
  return jobDefinition;
1386
1641
  }
@@ -1409,13 +1664,14 @@ var defineJob = (options) => {
1409
1664
  };
1410
1665
 
1411
1666
  // src/service/index.ts
1667
+ var import_services5 = require("@orion-js/services");
1412
1668
  var serviceMetadata = /* @__PURE__ */ new WeakMap();
1413
1669
  var jobsMetadata = /* @__PURE__ */ new Map();
1414
1670
  var jobEntriesByClass = /* @__PURE__ */ new Map();
1415
1671
  var pendingJobEntries = {};
1416
1672
  function Jobs() {
1417
1673
  return (target, context) => {
1418
- (0, import_services4.Service)()(target, context);
1674
+ (0, import_services5.Service)()(target, context);
1419
1675
  serviceMetadata.set(target, { _serviceType: "jobs" });
1420
1676
  if (Object.keys(pendingJobEntries).length > 0) {
1421
1677
  jobEntriesByClass.set(target, pendingJobEntries);
@@ -1464,7 +1720,7 @@ function initializeJobsIfNeeded(instance) {
1464
1720
  jobsMetadata.set(instance, jobs);
1465
1721
  }
1466
1722
  function getServiceJobs(target) {
1467
- const instance = (0, import_services4.getInstance)(target);
1723
+ const instance = (0, import_services5.getInstance)(target);
1468
1724
  if (!serviceMetadata.has(instance.constructor)) {
1469
1725
  throw new Error("You must pass a class decorated with @Jobs to getServiceJobs");
1470
1726
  }
@@ -1478,18 +1734,17 @@ function getServiceJobs(target) {
1478
1734
  }
1479
1735
 
1480
1736
  // src/index.ts
1481
- var workerService = (0, import_services5.getInstance)(WorkerService);
1482
- var eventsService = (0, import_services5.getInstance)(EventsService);
1483
- var jobsHistoryRepo = (0, import_services5.getInstance)(JobsHistoryRepo);
1484
- var jobsRepo = (0, import_services5.getInstance)(JobsRepo);
1737
+ var workerService = (0, import_services6.getInstance)(WorkerService);
1738
+ var jobsHistoryRepo = (0, import_services6.getInstance)(JobsHistoryRepo);
1739
+ var jobsRepo = (0, import_services6.getInstance)(JobsRepo);
1485
1740
  var startWorkers = (config) => {
1486
1741
  return workerService.startWorkers(config);
1487
1742
  };
1488
1743
  var scheduleJob = (options) => {
1489
- return eventsService.scheduleJob(options);
1744
+ return scheduleJobInternal(options);
1490
1745
  };
1491
1746
  var scheduleJobs = (jobs) => {
1492
- return eventsService.scheduleJobs(jobs);
1747
+ return scheduleJobsInternal(jobs);
1493
1748
  };
1494
1749
  // Annotate the CommonJS export names for ESM import in node:
1495
1750
  0 && (module.exports = {