@orion-js/dogs 4.6.4 → 4.6.6

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.js CHANGED
@@ -48,27 +48,170 @@ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "
48
48
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
49
49
 
50
50
  // src/index.ts
51
- import { getInstance as getInstance2 } from "@orion-js/services";
51
+ import { getInstance as getInstance3 } from "@orion-js/services";
52
52
 
53
- // src/services/EventsService.ts
54
- import { logger as logger2 } from "@orion-js/logger";
55
- import { Inject, Service } from "@orion-js/services";
53
+ // src/repos/JobsHistoryRepo.ts
54
+ import { MongoCollection, Repository } from "@orion-js/mongodb";
55
+
56
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/isInteger.js
57
+ function _isInteger(n) {
58
+ return n << 0 === n;
59
+ }
60
+ var isInteger = Number.isInteger || _isInteger;
61
+
62
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/createPath.js
63
+ function createPath(path, delimiter = ".") {
64
+ return typeof path === "string" ? path.split(delimiter).map((x) => isInteger(x) ? Number(x) : x) : path;
65
+ }
66
+
67
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/compare.js
68
+ function compare(a, b) {
69
+ return String(a) === String(b);
70
+ }
71
+
72
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/includes.js
73
+ function includes(a, list) {
74
+ let index = -1;
75
+ const { length } = list;
76
+ while (++index < length)
77
+ if (compare(list[index], a))
78
+ return true;
79
+ return false;
80
+ }
81
+
82
+ // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/omit.js
83
+ function omit(propsToOmit, obj) {
84
+ if (arguments.length === 1) return (_obj) => omit(propsToOmit, _obj);
85
+ if (obj === null || obj === void 0)
86
+ return void 0;
87
+ const propsToOmitValue = createPath(propsToOmit, ",");
88
+ const willReturn = {};
89
+ for (const key in obj)
90
+ if (!includes(key, propsToOmitValue))
91
+ willReturn[key] = obj[key];
92
+ return willReturn;
93
+ }
94
+
95
+ // src/types/HistoryRecord.ts
96
+ import { schemaWithName } from "@orion-js/schema";
97
+ var HistoryRecordSchema = schemaWithName("HistoryRecord", {
98
+ _id: { type: "string" },
99
+ jobId: { type: "string" },
100
+ executionId: { type: "string" },
101
+ jobName: { type: "string" },
102
+ type: { type: "string" },
103
+ priority: { type: "number" },
104
+ tries: { type: "number" },
105
+ uniqueIdentifier: { type: "string", optional: true },
106
+ startedAt: { type: "date" },
107
+ endedAt: { type: "date" },
108
+ duration: { type: "number" },
109
+ expiresAt: { type: "date", optional: true },
110
+ status: { type: "string", enum: ["success", "error", "stale"] },
111
+ errorMessage: { type: "string", optional: true },
112
+ params: { type: "blackbox", optional: true },
113
+ result: { type: "any", optional: true }
114
+ });
115
+
116
+ // src/repos/JobsHistoryRepo.ts
117
+ var _history_dec, _JobsHistoryRepo_decorators, _init;
118
+ _JobsHistoryRepo_decorators = [Repository()], _history_dec = [MongoCollection({
119
+ name: "orionjs.jobs_dogs_history",
120
+ idGeneration: "uuid",
121
+ schema: HistoryRecordSchema,
122
+ indexes: [
123
+ {
124
+ keys: {
125
+ jobName: 1,
126
+ startedAt: 1
127
+ }
128
+ },
129
+ {
130
+ keys: {
131
+ executionId: 1
132
+ }
133
+ },
134
+ {
135
+ keys: {
136
+ expiresAt: 1
137
+ },
138
+ options: {
139
+ expireAfterSeconds: 0
140
+ }
141
+ }
142
+ ]
143
+ })];
144
+ var JobsHistoryRepo = class {
145
+ constructor() {
146
+ __publicField(this, "history", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
147
+ }
148
+ async saveExecution(record) {
149
+ await this.history.upsert(
150
+ { executionId: record.executionId },
151
+ {
152
+ $setOnInsert: {
153
+ status: record.status
154
+ },
155
+ $set: {
156
+ ...omit(["status"], record)
157
+ }
158
+ }
159
+ );
160
+ }
161
+ async getExecutions(jobName, limit, skip) {
162
+ const cursor = this.history.find({ jobName }).sort({ startedAt: -1 });
163
+ if (skip) {
164
+ cursor.skip(skip);
165
+ }
166
+ if (limit) {
167
+ cursor.limit(limit);
168
+ }
169
+ return await cursor.toArray();
170
+ }
171
+ };
172
+ _init = __decoratorStart(null);
173
+ __decorateElement(_init, 5, "history", _history_dec, JobsHistoryRepo);
174
+ JobsHistoryRepo = __decorateElement(_init, 0, "JobsHistoryRepo", _JobsHistoryRepo_decorators, JobsHistoryRepo);
175
+ __runInitializers(_init, 1, JobsHistoryRepo);
56
176
 
57
177
  // src/repos/JobsRepo.ts
58
178
  import { generateId } from "@orion-js/helpers";
59
179
  import { logger } from "@orion-js/logger";
60
- import { MongoCollection, Repository } from "@orion-js/mongodb";
180
+ import { MongoCollection as MongoCollection2, MongoDB as MongoDB2, Repository as Repository2 } from "@orion-js/mongodb";
181
+ import { clean, validate } from "@orion-js/schema";
182
+
183
+ // src/services/partitions.ts
184
+ function validatePartitionsCount(nPartitions) {
185
+ if (!Number.isInteger(nPartitions) || nPartitions <= 0) {
186
+ throw new Error("nPartitions must be a positive integer");
187
+ }
188
+ }
189
+ function getRandomPartition(nPartitions) {
190
+ return Math.floor(Math.random() * nPartitions);
191
+ }
192
+ function createShuffledPartitions(nPartitions) {
193
+ const partitions = Array.from({ length: nPartitions }, (_, partition) => partition);
194
+ for (let index = partitions.length - 1; index > 0; index--) {
195
+ const swapIndex = Math.floor(Math.random() * (index + 1));
196
+ [partitions[index], partitions[swapIndex]] = [partitions[swapIndex], partitions[index]];
197
+ }
198
+ return partitions;
199
+ }
61
200
 
62
201
  // src/types/JobRecord.ts
63
- import { createEnum, schemaWithName } from "@orion-js/schema";
202
+ import { createEnum, schemaWithName as schemaWithName2 } from "@orion-js/schema";
64
203
  var JobStatusEnum = createEnum("JobStatus", ["pending", "maxTriesReached"]);
65
- var JobRecordSchema = schemaWithName("JobRecord", {
204
+ var JobRecordSchema = schemaWithName2("JobRecord", {
66
205
  _id: { type: "string" },
67
206
  jobName: { type: "string" },
68
207
  type: { type: createEnum("JobType", ["recurrent", "event"]) },
69
208
  priority: { type: "number" },
70
209
  uniqueIdentifier: { type: "string", optional: true },
71
210
  nextRunAt: { type: "date" },
211
+ /**
212
+ * Acquisition partition. Optional so records created by previous versions can be reconciled.
213
+ */
214
+ partition: { type: "number", optional: true },
72
215
  lastRunAt: { type: "date", optional: true },
73
216
  lockedUntil: { type: "date", optional: true },
74
217
  /**
@@ -76,6 +219,11 @@ var JobRecordSchema = schemaWithName("JobRecord", {
76
219
  * this value so a stale execution cannot modify a newer claim.
77
220
  */
78
221
  lockId: { type: "string", optional: true },
222
+ /**
223
+ * Internal acquisition metadata used when MongoDB supports sorted updateOne. It preserves the
224
+ * pre-update stale state because updateOne intentionally does not return the previous document.
225
+ */
226
+ claimWasStale: { type: "boolean", optional: true },
79
227
  tries: { type: "number", optional: true },
80
228
  params: { type: "blackbox", optional: true },
81
229
  /**
@@ -100,23 +248,30 @@ var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
100
248
  // src/repos/JobsRepo.ts
101
249
  var JOB_ACQUISITION_HINTS = {
102
250
  byJobName: {
251
+ partition: 1,
103
252
  jobName: 1,
104
253
  priority: -1,
105
254
  nextRunAt: 1
106
255
  },
107
256
  globalPriority: {
257
+ partition: 1,
108
258
  priority: -1,
109
259
  nextRunAt: 1
110
260
  }
111
261
  };
112
262
  var INITIAL_JOB_ACQUISITION_HINT = "byJobName";
113
263
  var CANDIDATE_JOB_ACQUISITION_HINT = "globalPriority";
264
+ var SORTED_UPDATE_ONE_MIN_WIRE_VERSION = 25;
265
+ var LOCK_ID_INDEX_NAME = "jobs_dogs_records_lock_id";
266
+ var PARTITION_JOB_NAME_INDEX_NAME = "jobs_dogs_records_partition_job_name_priority_next_run";
267
+ var PARTITION_PRIORITY_INDEX_NAME = "jobs_dogs_records_partition_priority_next_run";
114
268
  var JOB_ACQUISITION_SORT = {
115
269
  priority: -1,
116
270
  nextRunAt: 1
117
271
  };
118
- function getJobAcquisitionSelector(jobNames, now) {
272
+ function getJobAcquisitionSelector(jobNames, now, partition) {
119
273
  return {
274
+ partition,
120
275
  jobName: { $in: jobNames },
121
276
  nextRunAt: { $lte: now },
122
277
  $and: [
@@ -127,17 +282,19 @@ function getJobAcquisitionSelector(jobNames, now) {
127
282
  ]
128
283
  };
129
284
  }
130
- var _jobs_dec, _JobsRepo_decorators, _init;
131
- _JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
285
+ var _jobs_dec, _JobsRepo_decorators, _init2;
286
+ _JobsRepo_decorators = [Repository2()], _jobs_dec = [MongoCollection2({
132
287
  idGeneration: "uuid",
133
288
  name: "orionjs.jobs_dogs_records",
134
289
  schema: JobRecordSchema,
135
290
  indexes: [
136
291
  {
137
- keys: JOB_ACQUISITION_HINTS.byJobName
292
+ keys: JOB_ACQUISITION_HINTS.byJobName,
293
+ options: { name: PARTITION_JOB_NAME_INDEX_NAME }
138
294
  },
139
295
  {
140
- keys: JOB_ACQUISITION_HINTS.globalPriority
296
+ keys: JOB_ACQUISITION_HINTS.globalPriority,
297
+ options: { name: PARTITION_PRIORITY_INDEX_NAME }
141
298
  },
142
299
  {
143
300
  keys: {
@@ -157,6 +314,15 @@ _JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
157
314
  sparse: true
158
315
  }
159
316
  },
317
+ {
318
+ keys: {
319
+ lockId: 1
320
+ },
321
+ options: {
322
+ name: LOCK_ID_INDEX_NAME,
323
+ sparse: true
324
+ }
325
+ },
160
326
  {
161
327
  keys: {
162
328
  expiresAt: 1
@@ -168,14 +334,55 @@ _JobsRepo_decorators = [Repository()], _jobs_dec = [MongoCollection({
168
334
  })];
169
335
  var JobsRepo = class {
170
336
  constructor() {
171
- __publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
337
+ __publicField(this, "jobs", __runInitializers(_init2, 8, this)), __runInitializers(_init2, 11, this);
338
+ __publicField(this, "sortedUpdateOneSupportPromise");
339
+ __publicField(this, "partitionsByJobName", /* @__PURE__ */ new Map());
340
+ }
341
+ configureJobPartitions(jobNames, nPartitions) {
342
+ for (const jobName of jobNames) {
343
+ this.partitionsByJobName.set(jobName, nPartitions);
344
+ }
345
+ }
346
+ getJobPartitionsCount(jobName) {
347
+ return this.partitionsByJobName.get(jobName);
348
+ }
349
+ async supportsSortedUpdateOne() {
350
+ if (!this.sortedUpdateOneSupportPromise) {
351
+ this.sortedUpdateOneSupportPromise = this.detectSortedUpdateOneSupport();
352
+ }
353
+ return this.sortedUpdateOneSupportPromise;
354
+ }
355
+ async detectSortedUpdateOneSupport() {
356
+ const rawCollection = await this.jobs.getRawCollection();
357
+ await this.jobs.createIndexesPromise;
358
+ const hello = await rawCollection.db.admin().command({ hello: 1 });
359
+ const supportsSortedUpdateOne = typeof hello.maxWireVersion === "number" && hello.maxWireVersion >= SORTED_UPDATE_ONE_MIN_WIRE_VERSION;
360
+ if (!supportsSortedUpdateOne) {
361
+ logger.info("MongoDB does not support sorted updateOne; using findOneAndUpdate for jobs.");
362
+ return false;
363
+ }
364
+ const hasLockIdIndex = await rawCollection.indexExists(LOCK_ID_INDEX_NAME);
365
+ if (!hasLockIdIndex) {
366
+ logger.warn(
367
+ `MongoDB supports sorted updateOne but index "${LOCK_ID_INDEX_NAME}" is unavailable; using findOneAndUpdate for jobs.`
368
+ );
369
+ return false;
370
+ }
371
+ logger.info("MongoDB supports sorted updateOne; using it for job acquisition.");
372
+ return true;
373
+ }
374
+ async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT, partition = 0) {
375
+ if (await this.supportsSortedUpdateOne()) {
376
+ return this.getJobAndLockWithSortedUpdateOne(jobNames, lockTime, hint, partition);
377
+ }
378
+ return this.getJobAndLockWithFindOneAndUpdate(jobNames, lockTime, hint, partition);
172
379
  }
173
- async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
380
+ async getJobAndLockWithFindOneAndUpdate(jobNames, lockTime, hint, partition) {
174
381
  const executionId = generateId();
175
382
  const now = /* @__PURE__ */ new Date();
176
383
  const lockedUntil = new Date(now.getTime() + lockTime);
177
384
  const job = await this.jobs.findOneAndUpdate(
178
- getJobAcquisitionSelector(jobNames, now),
385
+ getJobAcquisitionSelector(jobNames, now, partition),
179
386
  {
180
387
  $set: { lockId: executionId, lockedUntil, lastRunAt: now },
181
388
  $inc: { tries: 1 }
@@ -191,6 +398,37 @@ var JobsRepo = class {
191
398
  if (!job) return;
192
399
  const tries = (job.tries || 0) + 1;
193
400
  const wasStale = Boolean(job.lockedUntil);
401
+ return this.toJobToRun(job, executionId, lockTime, tries, wasStale);
402
+ }
403
+ async getJobAndLockWithSortedUpdateOne(jobNames, lockTime, hint, partition) {
404
+ const executionId = generateId();
405
+ const now = /* @__PURE__ */ new Date();
406
+ const lockedUntil = new Date(now.getTime() + lockTime);
407
+ const rawCollection = await this.jobs.getRawCollection();
408
+ const result = await rawCollection.updateOne(
409
+ getJobAcquisitionSelector(jobNames, now, partition),
410
+ [
411
+ {
412
+ $set: {
413
+ lockId: { $literal: executionId },
414
+ lockedUntil: { $literal: lockedUntil },
415
+ lastRunAt: { $literal: now },
416
+ tries: { $add: [{ $ifNull: ["$tries", 0] }, 1] },
417
+ claimWasStale: { $eq: [{ $type: "$lockedUntil" }, "date"] }
418
+ }
419
+ }
420
+ ],
421
+ {
422
+ hint: JOB_ACQUISITION_HINTS[hint],
423
+ sort: JOB_ACQUISITION_SORT
424
+ }
425
+ );
426
+ if (result.matchedCount === 0) return;
427
+ const job = await rawCollection.findOne({ lockId: executionId });
428
+ if (!job) return;
429
+ return this.toJobToRun(job, executionId, lockTime, job.tries || 0, Boolean(job.claimWasStale));
430
+ }
431
+ toJobToRun(job, executionId, lockTime, tries, wasStale) {
194
432
  if (wasStale) {
195
433
  logger.info(`Running job "${job.jobName}" that was staled`);
196
434
  }
@@ -208,9 +446,9 @@ var JobsRepo = class {
208
446
  wasStale
209
447
  };
210
448
  }
211
- async getJobAcquisitionExecutionTime(jobNames, hint) {
449
+ async getJobAcquisitionExecutionTime(jobNames, hint, partition = 0) {
212
450
  var _a;
213
- const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date()), {
451
+ const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date(), partition), {
214
452
  hint: JOB_ACQUISITION_HINTS[hint],
215
453
  maxTimeMS: 1e3,
216
454
  readPreference: "primary"
@@ -234,7 +472,7 @@ var JobsRepo = class {
234
472
  priority: options.priority,
235
473
  ...options.resetTries ? { tries: 0 } : {}
236
474
  },
237
- $unset: { lockId: "", lockedUntil: "" }
475
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
238
476
  };
239
477
  const result = await this.jobs.updateOne(
240
478
  options.lockId ? { _id: options.jobId, lockId: options.lockId } : options.jobId,
@@ -268,6 +506,7 @@ var JobsRepo = class {
268
506
  $unset: {
269
507
  lockId: "",
270
508
  lockedUntil: "",
509
+ claimWasStale: "",
271
510
  ...retentionMs === null ? { expiresAt: "" } : {}
272
511
  }
273
512
  }
@@ -324,12 +563,12 @@ var JobsRepo = class {
324
563
  lockedUntil: { $exists: true }
325
564
  },
326
565
  {
327
- $unset: { lockId: "", lockedUntil: "" }
566
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
328
567
  }
329
568
  );
330
569
  return result.modifiedCount;
331
570
  }
332
- async ensureJobRecord(job) {
571
+ async ensureJobRecord(job, partition = 0) {
333
572
  const result = await this.jobs.upsert(
334
573
  {
335
574
  jobName: job.name
@@ -343,7 +582,8 @@ var JobsRepo = class {
343
582
  status: ""
344
583
  },
345
584
  $setOnInsert: {
346
- nextRunAt: /* @__PURE__ */ new Date()
585
+ nextRunAt: /* @__PURE__ */ new Date(),
586
+ partition
347
587
  }
348
588
  }
349
589
  );
@@ -361,7 +601,8 @@ var JobsRepo = class {
361
601
  params: options.params,
362
602
  nextRunAt: options.nextRunAt,
363
603
  priority: options.priority,
364
- type: "event"
604
+ type: "event",
605
+ ...options.partition === void 0 ? {} : { partition: options.partition }
365
606
  });
366
607
  } catch (error) {
367
608
  if (error.isValidationError && Object.values(error.validationErrors).includes("notUnique") && options.uniqueIdentifier) {
@@ -377,30 +618,75 @@ var JobsRepo = class {
377
618
  if (jobs.length === 0) {
378
619
  return { scheduledCount: 0, skippedCount: 0, errors: [] };
379
620
  }
380
- let scheduledCount = 0;
381
- let skippedCount = 0;
382
621
  const errors = [];
622
+ const preparedJobs = [];
383
623
  for (let i = 0; i < jobs.length; i++) {
384
624
  const job = jobs[i];
385
625
  try {
386
- await this.jobs.insertOne({
626
+ const record = await clean(JobRecordSchema, {
627
+ _id: this.jobs.generateId(),
387
628
  jobName: job.name,
388
629
  uniqueIdentifier: job.uniqueIdentifier,
389
630
  params: job.params,
390
631
  nextRunAt: job.nextRunAt,
391
632
  priority: job.priority,
392
- type: "event"
633
+ type: "event",
634
+ ...job.partition === void 0 ? {} : { partition: job.partition }
393
635
  });
394
- scheduledCount++;
636
+ await validate(JobRecordSchema, record);
637
+ preparedJobs.push({ index: i, job, record });
395
638
  } catch (error) {
396
- if (error.isValidationError && Object.values(error.validationErrors).includes("notUnique") && job.uniqueIdentifier) {
397
- logger.info(`Job "${job.name}" with identifier "${job.uniqueIdentifier}" already exists`);
398
- skippedCount++;
399
- } else {
639
+ errors.push({
640
+ index: i,
641
+ error: error instanceof Error ? error : new Error(String(error)),
642
+ job
643
+ });
644
+ }
645
+ }
646
+ if (preparedJobs.length === 0) {
647
+ return { scheduledCount: 0, skippedCount: 0, errors };
648
+ }
649
+ const rawCollection = await this.jobs.getRawCollection();
650
+ await this.jobs.createIndexesPromise;
651
+ const operations = preparedJobs.map(
652
+ ({ record }) => record.uniqueIdentifier === void 0 ? { insertOne: { document: record } } : {
653
+ updateOne: {
654
+ filter: { uniqueIdentifier: record.uniqueIdentifier },
655
+ update: { $setOnInsert: record },
656
+ upsert: true
657
+ }
658
+ }
659
+ );
660
+ let scheduledCount = 0;
661
+ let skippedCount = 0;
662
+ try {
663
+ const result = await rawCollection.bulkWrite(operations, { ordered: false });
664
+ scheduledCount = result.insertedCount + result.upsertedCount;
665
+ skippedCount = result.matchedCount;
666
+ } catch (error) {
667
+ const writeErrors = error instanceof MongoDB2.MongoBulkWriteError ? Array.isArray(error.writeErrors) ? error.writeErrors : [error.writeErrors] : [];
668
+ if (!(error instanceof MongoDB2.MongoBulkWriteError) || writeErrors.length === 0) {
669
+ for (const preparedJob of preparedJobs) {
400
670
  errors.push({
401
- index: i,
671
+ index: preparedJob.index,
402
672
  error: error instanceof Error ? error : new Error(String(error)),
403
- job
673
+ job: preparedJob.job
674
+ });
675
+ }
676
+ } else {
677
+ scheduledCount = error.result.insertedCount + error.result.upsertedCount;
678
+ skippedCount = error.result.matchedCount;
679
+ for (const writeError of writeErrors) {
680
+ const preparedJob = preparedJobs[writeError.index];
681
+ if (!preparedJob) continue;
682
+ if (writeError.code === 11e3 && preparedJob.record.uniqueIdentifier !== void 0) {
683
+ skippedCount++;
684
+ continue;
685
+ }
686
+ errors.push({
687
+ index: preparedJob.index,
688
+ error: new Error(writeError.errmsg),
689
+ job: preparedJob.job
404
690
  });
405
691
  }
406
692
  }
@@ -414,11 +700,55 @@ var JobsRepo = class {
414
700
  errors
415
701
  };
416
702
  }
703
+ async reconcilePartitions(options) {
704
+ if (options.jobNames.length === 0) return 0;
705
+ const rawCollection = await this.jobs.getRawCollection();
706
+ await this.jobs.createIndexesPromise;
707
+ const now = /* @__PURE__ */ new Date();
708
+ const invalidPartition = {
709
+ $or: [
710
+ { partition: { $exists: false } },
711
+ { partition: { $lt: 0 } },
712
+ { partition: { $gte: options.nPartitions } }
713
+ ]
714
+ };
715
+ const availableLock = {
716
+ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: now } }]
717
+ };
718
+ const runnableJob = {
719
+ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }]
720
+ };
721
+ const jobs = await rawCollection.find({
722
+ jobName: { $in: options.jobNames },
723
+ $and: [invalidPartition, availableLock, runnableJob]
724
+ }).project({ _id: 1 }).limit(options.batchSize).toArray();
725
+ if (jobs.length === 0) return 0;
726
+ const result = await rawCollection.bulkWrite(
727
+ jobs.map((job) => ({
728
+ updateOne: {
729
+ filter: {
730
+ _id: job._id,
731
+ $and: [invalidPartition, availableLock, runnableJob]
732
+ },
733
+ update: { $set: { partition: getRandomPartition(options.nPartitions) } }
734
+ }
735
+ })),
736
+ { ordered: false }
737
+ );
738
+ return result.modifiedCount;
739
+ }
417
740
  };
418
- _init = __decoratorStart(null);
419
- __decorateElement(_init, 5, "jobs", _jobs_dec, JobsRepo);
420
- JobsRepo = __decorateElement(_init, 0, "JobsRepo", _JobsRepo_decorators, JobsRepo);
421
- __runInitializers(_init, 1, JobsRepo);
741
+ _init2 = __decoratorStart(null);
742
+ __decorateElement(_init2, 5, "jobs", _jobs_dec, JobsRepo);
743
+ JobsRepo = __decorateElement(_init2, 0, "JobsRepo", _JobsRepo_decorators, JobsRepo);
744
+ __runInitializers(_init2, 1, JobsRepo);
745
+
746
+ // src/schedule.ts
747
+ import { getInstance } from "@orion-js/services";
748
+
749
+ // src/services/EventsService.ts
750
+ import { logger as logger2 } from "@orion-js/logger";
751
+ import { Inject, Service } from "@orion-js/services";
422
752
 
423
753
  // src/services/getNextRunDate.ts
424
754
  import { CronExpressionParser } from "cron-parser";
@@ -448,38 +778,53 @@ var getNextRunDate = (options) => {
448
778
  };
449
779
 
450
780
  // src/services/EventsService.ts
451
- var _jobsRepo_dec, _EventsService_decorators, _init2;
781
+ var _jobsRepo_dec, _EventsService_decorators, _init3;
452
782
  _EventsService_decorators = [Service()], _jobsRepo_dec = [Inject(() => JobsRepo)];
453
783
  var EventsService = class {
454
784
  constructor() {
455
- __publicField(this, "jobsRepo", __runInitializers(_init2, 8, this)), __runInitializers(_init2, 11, this);
785
+ __publicField(this, "jobsRepo", __runInitializers(_init3, 8, this)), __runInitializers(_init3, 11, this);
456
786
  }
457
- async scheduleJob(options) {
787
+ async scheduleJob(options, nPartitions) {
458
788
  logger2.debug("Scheduling job...", options);
789
+ const configuredPartitions = nPartitions ?? this.jobsRepo.getJobPartitionsCount(options.name);
459
790
  await this.jobsRepo.scheduleJob({
460
791
  name: options.name,
461
792
  priority: options.priority || 100,
462
793
  nextRunAt: getNextRunDate(options),
463
794
  params: options.params || null,
464
- uniqueIdentifier: options.uniqueIdentifier
795
+ uniqueIdentifier: options.uniqueIdentifier,
796
+ partition: configuredPartitions === void 0 ? void 0 : getRandomPartition(configuredPartitions)
465
797
  });
466
798
  }
467
- async scheduleJobs(jobs) {
799
+ async scheduleJobs(jobs, nPartitions) {
468
800
  logger2.debug(`Scheduling ${jobs.length} jobs...`);
469
- const jobRecords = jobs.map((options) => ({
470
- name: options.name,
471
- priority: options.priority || 100,
472
- nextRunAt: getNextRunDate(options),
473
- params: options.params || null,
474
- uniqueIdentifier: options.uniqueIdentifier
475
- }));
801
+ const jobRecords = jobs.map((options) => {
802
+ const configuredPartitions = nPartitions ?? this.jobsRepo.getJobPartitionsCount(options.name);
803
+ return {
804
+ name: options.name,
805
+ priority: options.priority || 100,
806
+ nextRunAt: getNextRunDate(options),
807
+ params: options.params || null,
808
+ uniqueIdentifier: options.uniqueIdentifier,
809
+ partition: configuredPartitions === void 0 ? void 0 : getRandomPartition(configuredPartitions)
810
+ };
811
+ });
476
812
  return await this.jobsRepo.scheduleJobs(jobRecords);
477
813
  }
478
814
  };
479
- _init2 = __decoratorStart(null);
480
- __decorateElement(_init2, 5, "jobsRepo", _jobsRepo_dec, EventsService);
481
- EventsService = __decorateElement(_init2, 0, "EventsService", _EventsService_decorators, EventsService);
482
- __runInitializers(_init2, 1, EventsService);
815
+ _init3 = __decoratorStart(null);
816
+ __decorateElement(_init3, 5, "jobsRepo", _jobsRepo_dec, EventsService);
817
+ EventsService = __decorateElement(_init3, 0, "EventsService", _EventsService_decorators, EventsService);
818
+ __runInitializers(_init3, 1, EventsService);
819
+
820
+ // src/schedule.ts
821
+ var eventsService = getInstance(EventsService);
822
+ function scheduleJobInternal(options, nPartitions) {
823
+ return eventsService.scheduleJob(options, nPartitions);
824
+ }
825
+ function scheduleJobsInternal(jobs, nPartitions) {
826
+ return eventsService.scheduleJobs(jobs, nPartitions);
827
+ }
483
828
 
484
829
  // src/services/WorkerService.ts
485
830
  import { sleep } from "@orion-js/helpers";
@@ -490,132 +835,6 @@ import { Inject as Inject3, Service as Service3 } from "@orion-js/services";
490
835
  import { SpanStatusCode, trace } from "@opentelemetry/api";
491
836
  import { logger as logger3, runWithOrionAsyncContext, updateOrionAsyncContext } from "@orion-js/logger";
492
837
  import { Inject as Inject2, Service as Service2 } from "@orion-js/services";
493
-
494
- // src/repos/JobsHistoryRepo.ts
495
- import { MongoCollection as MongoCollection2, Repository as Repository2 } from "@orion-js/mongodb";
496
-
497
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/isInteger.js
498
- function _isInteger(n) {
499
- return n << 0 === n;
500
- }
501
- var isInteger = Number.isInteger || _isInteger;
502
-
503
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/createPath.js
504
- function createPath(path, delimiter = ".") {
505
- return typeof path === "string" ? path.split(delimiter).map((x) => isInteger(x) ? Number(x) : x) : path;
506
- }
507
-
508
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/compare.js
509
- function compare(a, b) {
510
- return String(a) === String(b);
511
- }
512
-
513
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/_internals/includes.js
514
- function includes(a, list) {
515
- let index = -1;
516
- const { length } = list;
517
- while (++index < length)
518
- if (compare(list[index], a))
519
- return true;
520
- return false;
521
- }
522
-
523
- // ../../node_modules/.bun/rambdax@11.3.1/node_modules/rambdax/src/omit.js
524
- function omit(propsToOmit, obj) {
525
- if (arguments.length === 1) return (_obj) => omit(propsToOmit, _obj);
526
- if (obj === null || obj === void 0)
527
- return void 0;
528
- const propsToOmitValue = createPath(propsToOmit, ",");
529
- const willReturn = {};
530
- for (const key in obj)
531
- if (!includes(key, propsToOmitValue))
532
- willReturn[key] = obj[key];
533
- return willReturn;
534
- }
535
-
536
- // src/types/HistoryRecord.ts
537
- import { schemaWithName as schemaWithName2 } from "@orion-js/schema";
538
- var HistoryRecordSchema = schemaWithName2("HistoryRecord", {
539
- _id: { type: "string" },
540
- jobId: { type: "string" },
541
- executionId: { type: "string" },
542
- jobName: { type: "string" },
543
- type: { type: "string" },
544
- priority: { type: "number" },
545
- tries: { type: "number" },
546
- uniqueIdentifier: { type: "string", optional: true },
547
- startedAt: { type: "date" },
548
- endedAt: { type: "date" },
549
- duration: { type: "number" },
550
- expiresAt: { type: "date", optional: true },
551
- status: { type: "string", enum: ["success", "error", "stale"] },
552
- errorMessage: { type: "string", optional: true },
553
- params: { type: "blackbox", optional: true },
554
- result: { type: "any", optional: true }
555
- });
556
-
557
- // src/repos/JobsHistoryRepo.ts
558
- var _history_dec, _JobsHistoryRepo_decorators, _init3;
559
- _JobsHistoryRepo_decorators = [Repository2()], _history_dec = [MongoCollection2({
560
- name: "orionjs.jobs_dogs_history",
561
- idGeneration: "uuid",
562
- schema: HistoryRecordSchema,
563
- indexes: [
564
- {
565
- keys: {
566
- jobName: 1,
567
- startedAt: 1
568
- }
569
- },
570
- {
571
- keys: {
572
- executionId: 1
573
- }
574
- },
575
- {
576
- keys: {
577
- expiresAt: 1
578
- },
579
- options: {
580
- expireAfterSeconds: 0
581
- }
582
- }
583
- ]
584
- })];
585
- var JobsHistoryRepo = class {
586
- constructor() {
587
- __publicField(this, "history", __runInitializers(_init3, 8, this)), __runInitializers(_init3, 11, this);
588
- }
589
- async saveExecution(record) {
590
- await this.history.upsert(
591
- { executionId: record.executionId },
592
- {
593
- $setOnInsert: {
594
- status: record.status
595
- },
596
- $set: {
597
- ...omit(["status"], record)
598
- }
599
- }
600
- );
601
- }
602
- async getExecutions(jobName, limit, skip) {
603
- const cursor = this.history.find({ jobName }).sort({ startedAt: -1 });
604
- if (skip) {
605
- cursor.skip(skip);
606
- }
607
- if (limit) {
608
- cursor.limit(limit);
609
- }
610
- return await cursor.toArray();
611
- }
612
- };
613
- _init3 = __decoratorStart(null);
614
- __decorateElement(_init3, 5, "history", _history_dec, JobsHistoryRepo);
615
- JobsHistoryRepo = __decorateElement(_init3, 0, "JobsHistoryRepo", _JobsHistoryRepo_decorators, JobsHistoryRepo);
616
- __runInitializers(_init3, 1, JobsHistoryRepo);
617
-
618
- // src/services/Executor.ts
619
838
  var _jobsHistoryRepo_dec, _jobsRepo_dec2, _Executor_decorators, _init4;
620
839
  _Executor_decorators = [Service2()], _jobsRepo_dec2 = [Inject2(() => JobsRepo)], _jobsHistoryRepo_dec = [Inject2(() => JobsHistoryRepo)];
621
840
  var Executor = class {
@@ -961,6 +1180,10 @@ _WorkerService_decorators = [Service3()], _jobsRepo_dec3 = [Inject3(() => JobsRe
961
1180
  var WorkerService = class {
962
1181
  constructor() {
963
1182
  __publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
1183
+ __publicField(this, "partitionReconcilerIntervalMs", 5 * 60 * 1e3);
1184
+ __publicField(this, "partitionReconcilerJitterMs", 30 * 1e3);
1185
+ __publicField(this, "partitionReconcilerBatchPauseMs", 250);
1186
+ __publicField(this, "partitionReconcilerBatchSize", 500);
964
1187
  __publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
965
1188
  __publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
966
1189
  }
@@ -1045,6 +1268,15 @@ var WorkerService = class {
1045
1268
  if (capacityPromises.length === 0) return;
1046
1269
  await Promise.race([...capacityPromises, workersInstance.stopSignal]);
1047
1270
  }
1271
+ getNextPartition(config, workersInstance) {
1272
+ const partition = workersInstance.partitionOrder[workersInstance.partitionCursor];
1273
+ workersInstance.partitionCursor++;
1274
+ if (workersInstance.partitionCursor >= config.nPartitions) {
1275
+ workersInstance.partitionOrder = createShuffledPartitions(config.nPartitions);
1276
+ workersInstance.partitionCursor = 0;
1277
+ }
1278
+ return partition;
1279
+ }
1048
1280
  async runSchedulerLoop(config, workersInstance) {
1049
1281
  const jobNames = this.getJobNames(config.jobs);
1050
1282
  const executeConfig = {
@@ -1054,6 +1286,7 @@ var WorkerService = class {
1054
1286
  onMaxTriesReached: config.onMaxTriesReached
1055
1287
  };
1056
1288
  logger4.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
1289
+ let consecutivePartitionMisses = 0;
1057
1290
  while (workersInstance.running) {
1058
1291
  let didMiss = false;
1059
1292
  let allJobNamesAtCapacity = false;
@@ -1065,21 +1298,30 @@ var WorkerService = class {
1065
1298
  break;
1066
1299
  }
1067
1300
  const jobAcquisitionHint = availableJobNames.length < jobNames.length ? FILTERED_JOB_NAMES_ACQUISITION_HINT : workersInstance.jobAcquisitionHint;
1301
+ const partition = this.getNextPartition(config, workersInstance);
1068
1302
  const jobToRun = await this.jobsRepo.getJobAndLock(
1069
1303
  availableJobNames,
1070
1304
  config.defaultLockTime,
1071
- jobAcquisitionHint
1305
+ jobAcquisitionHint,
1306
+ partition
1072
1307
  );
1073
1308
  if (!jobToRun) {
1074
- logger4.debug("No job to run");
1075
- didMiss = true;
1076
- break;
1309
+ consecutivePartitionMisses++;
1310
+ logger4.debug(`No job to run in partition ${partition}`);
1311
+ if (consecutivePartitionMisses >= config.nPartitions) {
1312
+ consecutivePartitionMisses = 0;
1313
+ didMiss = true;
1314
+ break;
1315
+ }
1316
+ continue;
1077
1317
  }
1318
+ consecutivePartitionMisses = 0;
1078
1319
  this.startExecution(executeConfig, workersInstance, jobToRun);
1079
1320
  if (!workersInstance.running) break;
1080
1321
  }
1081
1322
  } catch (error) {
1082
1323
  logger4.error("Error in job scheduler.", { error });
1324
+ consecutivePartitionMisses = 0;
1083
1325
  didMiss = true;
1084
1326
  }
1085
1327
  if (!workersInstance.running) return;
@@ -1115,11 +1357,13 @@ var WorkerService = class {
1115
1357
  workersInstance.jobAcquisitionHintProbeCycle++;
1116
1358
  try {
1117
1359
  for (let sample = 0; sample < 3; sample++) {
1360
+ const partition = getRandomPartition(config.nPartitions);
1118
1361
  for (const hint of [firstHint, secondHint]) {
1119
1362
  if (!workersInstance.running) return;
1120
1363
  const executionTimeMillis = await this.jobsRepo.getJobAcquisitionExecutionTime(
1121
1364
  jobNames,
1122
- hint
1365
+ hint,
1366
+ partition
1123
1367
  );
1124
1368
  if (!workersInstance.running) return;
1125
1369
  byHint[hint].push(executionTimeMillis);
@@ -1191,6 +1435,44 @@ var WorkerService = class {
1191
1435
  await this.waitForNextJobAcquisitionHintProbe(workersInstance);
1192
1436
  }
1193
1437
  }
1438
+ async reconcilePartitionBatch(config) {
1439
+ if (typeof this.jobsRepo.reconcilePartitions !== "function") return 0;
1440
+ try {
1441
+ return await this.jobsRepo.reconcilePartitions({
1442
+ jobNames: this.getJobNames(config.jobs),
1443
+ nPartitions: config.nPartitions,
1444
+ batchSize: this.partitionReconcilerBatchSize
1445
+ });
1446
+ } catch (error) {
1447
+ logger4.warn("Job partition reconciliation failed; it will be retried.", { error });
1448
+ return 0;
1449
+ }
1450
+ }
1451
+ async waitForNextPartitionReconcile(workersInstance, delayMs) {
1452
+ await new Promise((resolve) => {
1453
+ let resolved = false;
1454
+ const finishWait = () => {
1455
+ if (resolved) return;
1456
+ resolved = true;
1457
+ clearTimeout(workersInstance.partitionReconcilerTimer);
1458
+ workersInstance.partitionReconcilerTimer = void 0;
1459
+ workersInstance.cancelPartitionReconcilerWait = void 0;
1460
+ resolve();
1461
+ };
1462
+ workersInstance.partitionReconcilerTimer = setTimeout(finishWait, delayMs);
1463
+ workersInstance.cancelPartitionReconcilerWait = finishWait;
1464
+ });
1465
+ }
1466
+ async runPartitionReconcilerLoop(config, workersInstance, initialReconcilePromise) {
1467
+ let modifiedCount = await initialReconcilePromise;
1468
+ while (workersInstance.running) {
1469
+ const didFillBatch = modifiedCount >= this.partitionReconcilerBatchSize;
1470
+ const delayMs = didFillBatch ? this.partitionReconcilerBatchPauseMs : this.partitionReconcilerIntervalMs + Math.floor(Math.random() * (this.partitionReconcilerJitterMs + 1));
1471
+ await this.waitForNextPartitionReconcile(workersInstance, delayMs);
1472
+ if (!workersInstance.running) return;
1473
+ modifiedCount = await this.reconcilePartitionBatch(config);
1474
+ }
1475
+ }
1194
1476
  createWorkersInstanceDefinition(config) {
1195
1477
  let signalStop;
1196
1478
  const stopSignal = new Promise((resolve) => {
@@ -1206,7 +1488,10 @@ var WorkerService = class {
1206
1488
  jobAcquisitionHintProbeCycle: 0,
1207
1489
  hasCompletedInitialJobAcquisitionHintProbe: false,
1208
1490
  pendingJobAcquisitionHintWins: 0,
1491
+ partitionOrder: createShuffledPartitions(config.nPartitions),
1492
+ partitionCursor: 0,
1209
1493
  jobAcquisitionHintProbePromise: Promise.resolve(),
1494
+ partitionReconcilerPromise: Promise.resolve(),
1210
1495
  schedulerPromise: Promise.resolve(),
1211
1496
  stopSignal,
1212
1497
  signalStop,
@@ -1216,14 +1501,16 @@ var WorkerService = class {
1216
1501
  stop: async () => {
1217
1502
  if (workersInstance.stopPromise) return workersInstance.stopPromise;
1218
1503
  workersInstance.stopPromise = (async () => {
1219
- var _a;
1504
+ var _a, _b;
1220
1505
  logger4.info("Stopping job scheduler...");
1221
1506
  workersInstance.running = false;
1222
1507
  workersInstance.signalStop();
1223
1508
  (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
1509
+ (_b = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b.call(workersInstance);
1224
1510
  await Promise.all([
1225
1511
  workersInstance.schedulerPromise,
1226
- workersInstance.jobAcquisitionHintProbePromise
1512
+ workersInstance.jobAcquisitionHintProbePromise,
1513
+ workersInstance.partitionReconcilerPromise
1227
1514
  ]);
1228
1515
  const activeCapacity = Array.from(
1229
1516
  workersInstance.activeExecutions.values(),
@@ -1241,7 +1528,7 @@ var WorkerService = class {
1241
1528
  await Promise.all(
1242
1529
  jobs.filter((job) => job.type === "recurrent").map(async (job) => {
1243
1530
  logger4.debug(`Ensuring records for job "${job.name}"...`);
1244
- await this.jobsRepo.ensureJobRecord(job);
1531
+ await this.jobsRepo.ensureJobRecord(job, getRandomPartition(config.nPartitions));
1245
1532
  })
1246
1533
  );
1247
1534
  }
@@ -1258,29 +1545,41 @@ var WorkerService = class {
1258
1545
  * @param userConfig - Configuration for the scheduler. Required field: jobs
1259
1546
  */
1260
1547
  startWorkers(userConfig) {
1548
+ var _a, _b;
1261
1549
  const maxTriesReachedRetentionMs = userConfig.maxTriesReachedRetentionMs === void 0 ? DEFAULT_MAX_TRIES_REACHED_RETENTION_MS : userConfig.maxTriesReachedRetentionMs;
1262
1550
  if (maxTriesReachedRetentionMs !== null && (!Number.isFinite(maxTriesReachedRetentionMs) || maxTriesReachedRetentionMs < 0)) {
1263
1551
  throw new Error("maxTriesReachedRetentionMs must be null, zero, or a positive number");
1264
1552
  }
1553
+ const nPartitions = userConfig.nPartitions ?? 1;
1554
+ validatePartitionsCount(nPartitions);
1265
1555
  const config = {
1266
1556
  pollInterval: 3e3,
1267
1557
  workersCount: 4,
1268
1558
  defaultLockTime: 30 * 1e3,
1269
1559
  ...userConfig,
1560
+ nPartitions,
1270
1561
  maxTriesReachedRetentionMs
1271
1562
  };
1272
- setNameToJobs(config.jobs);
1563
+ setNameToJobs(config.jobs, config.nPartitions);
1564
+ (_b = (_a = this.jobsRepo).configureJobPartitions) == null ? void 0 : _b.call(_a, this.getJobNames(config.jobs), config.nPartitions);
1273
1565
  const workersInstance = this.createWorkersInstanceDefinition(config);
1274
1566
  logger4.debug("Starting job scheduler", config);
1275
1567
  workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
1276
1568
  config,
1277
1569
  workersInstance
1278
1570
  );
1571
+ const initialReconcilePromise = this.reconcilePartitionBatch(config);
1572
+ workersInstance.partitionReconcilerPromise = this.runPartitionReconcilerLoop(
1573
+ config,
1574
+ workersInstance,
1575
+ initialReconcilePromise
1576
+ );
1279
1577
  workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
1280
- var _a;
1578
+ var _a2, _b2;
1281
1579
  workersInstance.running = false;
1282
1580
  workersInstance.signalStop();
1283
- (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
1581
+ (_a2 = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a2.call(workersInstance);
1582
+ (_b2 = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b2.call(workersInstance);
1284
1583
  logger4.error("Job scheduler stopped after an unexpected error.", { error });
1285
1584
  });
1286
1585
  return workersInstance;
@@ -1291,15 +1590,13 @@ __decorateElement(_init5, 5, "jobsRepo", _jobsRepo_dec3, WorkerService);
1291
1590
  __decorateElement(_init5, 5, "executor", _executor_dec, WorkerService);
1292
1591
  WorkerService = __decorateElement(_init5, 0, "WorkerService", _WorkerService_decorators, WorkerService);
1293
1592
  __runInitializers(_init5, 1, WorkerService);
1294
- function setNameToJobs(jobs) {
1593
+ function setNameToJobs(jobs, nPartitions) {
1295
1594
  for (const name of Object.keys(jobs)) {
1296
1595
  jobs[name].jobName = name;
1596
+ jobs[name].nPartitions = nPartitions;
1297
1597
  }
1298
1598
  }
1299
1599
 
1300
- // src/service/index.ts
1301
- import { getInstance, Service as Service4 } from "@orion-js/services";
1302
-
1303
1600
  // src/defineJob/index.ts
1304
1601
  import { cleanAndValidate } from "@orion-js/schema";
1305
1602
  import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
@@ -1316,11 +1613,14 @@ function createEventJob(options) {
1316
1613
  throw new Error("This job has not been registered in the workers");
1317
1614
  }
1318
1615
  const params = jobDefinition.params ? await cleanAndValidate(jobDefinition.params, scheduleOptions.params) : scheduleOptions.params;
1319
- return await scheduleJob({
1320
- ...scheduleOptions,
1321
- name: jobDefinition.jobName,
1322
- params
1323
- });
1616
+ return await scheduleJobInternal(
1617
+ {
1618
+ ...scheduleOptions,
1619
+ name: jobDefinition.jobName,
1620
+ params
1621
+ },
1622
+ jobDefinition.nPartitions
1623
+ );
1324
1624
  };
1325
1625
  jobDefinition.scheduleJobs = async (jobs) => {
1326
1626
  if (!jobDefinition.jobName) {
@@ -1336,7 +1636,7 @@ function createEventJob(options) {
1336
1636
  };
1337
1637
  })
1338
1638
  );
1339
- return await scheduleJobs(processedJobs);
1639
+ return await scheduleJobsInternal(processedJobs, jobDefinition.nPartitions);
1340
1640
  };
1341
1641
  return jobDefinition;
1342
1642
  }
@@ -1365,6 +1665,7 @@ var defineJob = (options) => {
1365
1665
  };
1366
1666
 
1367
1667
  // src/service/index.ts
1668
+ import { getInstance as getInstance2, Service as Service4 } from "@orion-js/services";
1368
1669
  var serviceMetadata = /* @__PURE__ */ new WeakMap();
1369
1670
  var jobsMetadata = /* @__PURE__ */ new Map();
1370
1671
  var jobEntriesByClass = /* @__PURE__ */ new Map();
@@ -1420,7 +1721,7 @@ function initializeJobsIfNeeded(instance) {
1420
1721
  jobsMetadata.set(instance, jobs);
1421
1722
  }
1422
1723
  function getServiceJobs(target) {
1423
- const instance = getInstance(target);
1724
+ const instance = getInstance2(target);
1424
1725
  if (!serviceMetadata.has(instance.constructor)) {
1425
1726
  throw new Error("You must pass a class decorated with @Jobs to getServiceJobs");
1426
1727
  }
@@ -1434,18 +1735,17 @@ function getServiceJobs(target) {
1434
1735
  }
1435
1736
 
1436
1737
  // src/index.ts
1437
- var workerService = getInstance2(WorkerService);
1438
- var eventsService = getInstance2(EventsService);
1439
- var jobsHistoryRepo = getInstance2(JobsHistoryRepo);
1440
- var jobsRepo = getInstance2(JobsRepo);
1738
+ var workerService = getInstance3(WorkerService);
1739
+ var jobsHistoryRepo = getInstance3(JobsHistoryRepo);
1740
+ var jobsRepo = getInstance3(JobsRepo);
1441
1741
  var startWorkers = (config) => {
1442
1742
  return workerService.startWorkers(config);
1443
1743
  };
1444
1744
  var scheduleJob = (options) => {
1445
- return eventsService.scheduleJob(options);
1745
+ return scheduleJobInternal(options);
1446
1746
  };
1447
1747
  var scheduleJobs = (jobs) => {
1448
- return eventsService.scheduleJobs(jobs);
1748
+ return scheduleJobsInternal(jobs);
1449
1749
  };
1450
1750
  export {
1451
1751
  DEFAULT_MAX_TRIES_REACHED_RETENTION_MS,