@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.cjs CHANGED
@@ -92,27 +92,170 @@ __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
+ var import_schema3 = require("@orion-js/schema");
226
+
227
+ // src/services/partitions.ts
228
+ function validatePartitionsCount(nPartitions) {
229
+ if (!Number.isInteger(nPartitions) || nPartitions <= 0) {
230
+ throw new Error("nPartitions must be a positive integer");
231
+ }
232
+ }
233
+ function getRandomPartition(nPartitions) {
234
+ return Math.floor(Math.random() * nPartitions);
235
+ }
236
+ function createShuffledPartitions(nPartitions) {
237
+ const partitions = Array.from({ length: nPartitions }, (_, partition) => partition);
238
+ for (let index = partitions.length - 1; index > 0; index--) {
239
+ const swapIndex = Math.floor(Math.random() * (index + 1));
240
+ [partitions[index], partitions[swapIndex]] = [partitions[swapIndex], partitions[index]];
241
+ }
242
+ return partitions;
243
+ }
105
244
 
106
245
  // 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", {
246
+ var import_schema2 = require("@orion-js/schema");
247
+ var JobStatusEnum = (0, import_schema2.createEnum)("JobStatus", ["pending", "maxTriesReached"]);
248
+ var JobRecordSchema = (0, import_schema2.schemaWithName)("JobRecord", {
110
249
  _id: { type: "string" },
111
250
  jobName: { type: "string" },
112
- type: { type: (0, import_schema.createEnum)("JobType", ["recurrent", "event"]) },
251
+ type: { type: (0, import_schema2.createEnum)("JobType", ["recurrent", "event"]) },
113
252
  priority: { type: "number" },
114
253
  uniqueIdentifier: { type: "string", optional: true },
115
254
  nextRunAt: { type: "date" },
255
+ /**
256
+ * Acquisition partition. Optional so records created by previous versions can be reconciled.
257
+ */
258
+ partition: { type: "number", optional: true },
116
259
  lastRunAt: { type: "date", optional: true },
117
260
  lockedUntil: { type: "date", optional: true },
118
261
  /**
@@ -120,6 +263,11 @@ var JobRecordSchema = (0, import_schema.schemaWithName)("JobRecord", {
120
263
  * this value so a stale execution cannot modify a newer claim.
121
264
  */
122
265
  lockId: { type: "string", optional: true },
266
+ /**
267
+ * Internal acquisition metadata used when MongoDB supports sorted updateOne. It preserves the
268
+ * pre-update stale state because updateOne intentionally does not return the previous document.
269
+ */
270
+ claimWasStale: { type: "boolean", optional: true },
123
271
  tries: { type: "number", optional: true },
124
272
  params: { type: "blackbox", optional: true },
125
273
  /**
@@ -144,23 +292,30 @@ var DEFAULT_MAX_TRIES_REACHED_RETENTION_MS = 1e3 * 60 * 60 * 24 * 7;
144
292
  // src/repos/JobsRepo.ts
145
293
  var JOB_ACQUISITION_HINTS = {
146
294
  byJobName: {
295
+ partition: 1,
147
296
  jobName: 1,
148
297
  priority: -1,
149
298
  nextRunAt: 1
150
299
  },
151
300
  globalPriority: {
301
+ partition: 1,
152
302
  priority: -1,
153
303
  nextRunAt: 1
154
304
  }
155
305
  };
156
306
  var INITIAL_JOB_ACQUISITION_HINT = "byJobName";
157
307
  var CANDIDATE_JOB_ACQUISITION_HINT = "globalPriority";
308
+ var SORTED_UPDATE_ONE_MIN_WIRE_VERSION = 25;
309
+ var LOCK_ID_INDEX_NAME = "jobs_dogs_records_lock_id";
310
+ var PARTITION_JOB_NAME_INDEX_NAME = "jobs_dogs_records_partition_job_name_priority_next_run";
311
+ var PARTITION_PRIORITY_INDEX_NAME = "jobs_dogs_records_partition_priority_next_run";
158
312
  var JOB_ACQUISITION_SORT = {
159
313
  priority: -1,
160
314
  nextRunAt: 1
161
315
  };
162
- function getJobAcquisitionSelector(jobNames, now) {
316
+ function getJobAcquisitionSelector(jobNames, now, partition) {
163
317
  return {
318
+ partition,
164
319
  jobName: { $in: jobNames },
165
320
  nextRunAt: { $lte: now },
166
321
  $and: [
@@ -171,17 +326,19 @@ function getJobAcquisitionSelector(jobNames, now) {
171
326
  ]
172
327
  };
173
328
  }
174
- var _jobs_dec, _JobsRepo_decorators, _init;
175
- _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, import_mongodb.MongoCollection)({
329
+ var _jobs_dec, _JobsRepo_decorators, _init2;
330
+ _JobsRepo_decorators = [(0, import_mongodb2.Repository)()], _jobs_dec = [(0, import_mongodb2.MongoCollection)({
176
331
  idGeneration: "uuid",
177
332
  name: "orionjs.jobs_dogs_records",
178
333
  schema: JobRecordSchema,
179
334
  indexes: [
180
335
  {
181
- keys: JOB_ACQUISITION_HINTS.byJobName
336
+ keys: JOB_ACQUISITION_HINTS.byJobName,
337
+ options: { name: PARTITION_JOB_NAME_INDEX_NAME }
182
338
  },
183
339
  {
184
- keys: JOB_ACQUISITION_HINTS.globalPriority
340
+ keys: JOB_ACQUISITION_HINTS.globalPriority,
341
+ options: { name: PARTITION_PRIORITY_INDEX_NAME }
185
342
  },
186
343
  {
187
344
  keys: {
@@ -201,6 +358,15 @@ _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, impo
201
358
  sparse: true
202
359
  }
203
360
  },
361
+ {
362
+ keys: {
363
+ lockId: 1
364
+ },
365
+ options: {
366
+ name: LOCK_ID_INDEX_NAME,
367
+ sparse: true
368
+ }
369
+ },
204
370
  {
205
371
  keys: {
206
372
  expiresAt: 1
@@ -212,14 +378,55 @@ _JobsRepo_decorators = [(0, import_mongodb.Repository)()], _jobs_dec = [(0, impo
212
378
  })];
213
379
  var JobsRepo = class {
214
380
  constructor() {
215
- __publicField(this, "jobs", __runInitializers(_init, 8, this)), __runInitializers(_init, 11, this);
381
+ __publicField(this, "jobs", __runInitializers(_init2, 8, this)), __runInitializers(_init2, 11, this);
382
+ __publicField(this, "sortedUpdateOneSupportPromise");
383
+ __publicField(this, "partitionsByJobName", /* @__PURE__ */ new Map());
384
+ }
385
+ configureJobPartitions(jobNames, nPartitions) {
386
+ for (const jobName of jobNames) {
387
+ this.partitionsByJobName.set(jobName, nPartitions);
388
+ }
389
+ }
390
+ getJobPartitionsCount(jobName) {
391
+ return this.partitionsByJobName.get(jobName);
216
392
  }
217
- async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT) {
393
+ async supportsSortedUpdateOne() {
394
+ if (!this.sortedUpdateOneSupportPromise) {
395
+ this.sortedUpdateOneSupportPromise = this.detectSortedUpdateOneSupport();
396
+ }
397
+ return this.sortedUpdateOneSupportPromise;
398
+ }
399
+ async detectSortedUpdateOneSupport() {
400
+ const rawCollection = await this.jobs.getRawCollection();
401
+ await this.jobs.createIndexesPromise;
402
+ const hello = await rawCollection.db.admin().command({ hello: 1 });
403
+ const supportsSortedUpdateOne = typeof hello.maxWireVersion === "number" && hello.maxWireVersion >= SORTED_UPDATE_ONE_MIN_WIRE_VERSION;
404
+ if (!supportsSortedUpdateOne) {
405
+ import_logger.logger.info("MongoDB does not support sorted updateOne; using findOneAndUpdate for jobs.");
406
+ return false;
407
+ }
408
+ const hasLockIdIndex = await rawCollection.indexExists(LOCK_ID_INDEX_NAME);
409
+ if (!hasLockIdIndex) {
410
+ import_logger.logger.warn(
411
+ `MongoDB supports sorted updateOne but index "${LOCK_ID_INDEX_NAME}" is unavailable; using findOneAndUpdate for jobs.`
412
+ );
413
+ return false;
414
+ }
415
+ import_logger.logger.info("MongoDB supports sorted updateOne; using it for job acquisition.");
416
+ return true;
417
+ }
418
+ async getJobAndLock(jobNames, lockTime, hint = INITIAL_JOB_ACQUISITION_HINT, partition = 0) {
419
+ if (await this.supportsSortedUpdateOne()) {
420
+ return this.getJobAndLockWithSortedUpdateOne(jobNames, lockTime, hint, partition);
421
+ }
422
+ return this.getJobAndLockWithFindOneAndUpdate(jobNames, lockTime, hint, partition);
423
+ }
424
+ async getJobAndLockWithFindOneAndUpdate(jobNames, lockTime, hint, partition) {
218
425
  const executionId = (0, import_helpers.generateId)();
219
426
  const now = /* @__PURE__ */ new Date();
220
427
  const lockedUntil = new Date(now.getTime() + lockTime);
221
428
  const job = await this.jobs.findOneAndUpdate(
222
- getJobAcquisitionSelector(jobNames, now),
429
+ getJobAcquisitionSelector(jobNames, now, partition),
223
430
  {
224
431
  $set: { lockId: executionId, lockedUntil, lastRunAt: now },
225
432
  $inc: { tries: 1 }
@@ -235,6 +442,37 @@ var JobsRepo = class {
235
442
  if (!job) return;
236
443
  const tries = (job.tries || 0) + 1;
237
444
  const wasStale = Boolean(job.lockedUntil);
445
+ return this.toJobToRun(job, executionId, lockTime, tries, wasStale);
446
+ }
447
+ async getJobAndLockWithSortedUpdateOne(jobNames, lockTime, hint, partition) {
448
+ const executionId = (0, import_helpers.generateId)();
449
+ const now = /* @__PURE__ */ new Date();
450
+ const lockedUntil = new Date(now.getTime() + lockTime);
451
+ const rawCollection = await this.jobs.getRawCollection();
452
+ const result = await rawCollection.updateOne(
453
+ getJobAcquisitionSelector(jobNames, now, partition),
454
+ [
455
+ {
456
+ $set: {
457
+ lockId: { $literal: executionId },
458
+ lockedUntil: { $literal: lockedUntil },
459
+ lastRunAt: { $literal: now },
460
+ tries: { $add: [{ $ifNull: ["$tries", 0] }, 1] },
461
+ claimWasStale: { $eq: [{ $type: "$lockedUntil" }, "date"] }
462
+ }
463
+ }
464
+ ],
465
+ {
466
+ hint: JOB_ACQUISITION_HINTS[hint],
467
+ sort: JOB_ACQUISITION_SORT
468
+ }
469
+ );
470
+ if (result.matchedCount === 0) return;
471
+ const job = await rawCollection.findOne({ lockId: executionId });
472
+ if (!job) return;
473
+ return this.toJobToRun(job, executionId, lockTime, job.tries || 0, Boolean(job.claimWasStale));
474
+ }
475
+ toJobToRun(job, executionId, lockTime, tries, wasStale) {
238
476
  if (wasStale) {
239
477
  import_logger.logger.info(`Running job "${job.jobName}" that was staled`);
240
478
  }
@@ -252,9 +490,9 @@ var JobsRepo = class {
252
490
  wasStale
253
491
  };
254
492
  }
255
- async getJobAcquisitionExecutionTime(jobNames, hint) {
493
+ async getJobAcquisitionExecutionTime(jobNames, hint, partition = 0) {
256
494
  var _a;
257
- const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date()), {
495
+ const explain = await this.jobs.find(getJobAcquisitionSelector(jobNames, /* @__PURE__ */ new Date(), partition), {
258
496
  hint: JOB_ACQUISITION_HINTS[hint],
259
497
  maxTimeMS: 1e3,
260
498
  readPreference: "primary"
@@ -278,7 +516,7 @@ var JobsRepo = class {
278
516
  priority: options.priority,
279
517
  ...options.resetTries ? { tries: 0 } : {}
280
518
  },
281
- $unset: { lockId: "", lockedUntil: "" }
519
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
282
520
  };
283
521
  const result = await this.jobs.updateOne(
284
522
  options.lockId ? { _id: options.jobId, lockId: options.lockId } : options.jobId,
@@ -312,6 +550,7 @@ var JobsRepo = class {
312
550
  $unset: {
313
551
  lockId: "",
314
552
  lockedUntil: "",
553
+ claimWasStale: "",
315
554
  ...retentionMs === null ? { expiresAt: "" } : {}
316
555
  }
317
556
  }
@@ -368,12 +607,12 @@ var JobsRepo = class {
368
607
  lockedUntil: { $exists: true }
369
608
  },
370
609
  {
371
- $unset: { lockId: "", lockedUntil: "" }
610
+ $unset: { lockId: "", lockedUntil: "", claimWasStale: "" }
372
611
  }
373
612
  );
374
613
  return result.modifiedCount;
375
614
  }
376
- async ensureJobRecord(job) {
615
+ async ensureJobRecord(job, partition = 0) {
377
616
  const result = await this.jobs.upsert(
378
617
  {
379
618
  jobName: job.name
@@ -387,7 +626,8 @@ var JobsRepo = class {
387
626
  status: ""
388
627
  },
389
628
  $setOnInsert: {
390
- nextRunAt: /* @__PURE__ */ new Date()
629
+ nextRunAt: /* @__PURE__ */ new Date(),
630
+ partition
391
631
  }
392
632
  }
393
633
  );
@@ -405,7 +645,8 @@ var JobsRepo = class {
405
645
  params: options.params,
406
646
  nextRunAt: options.nextRunAt,
407
647
  priority: options.priority,
408
- type: "event"
648
+ type: "event",
649
+ ...options.partition === void 0 ? {} : { partition: options.partition }
409
650
  });
410
651
  } catch (error) {
411
652
  if (error.isValidationError && Object.values(error.validationErrors).includes("notUnique") && options.uniqueIdentifier) {
@@ -421,30 +662,75 @@ var JobsRepo = class {
421
662
  if (jobs.length === 0) {
422
663
  return { scheduledCount: 0, skippedCount: 0, errors: [] };
423
664
  }
424
- let scheduledCount = 0;
425
- let skippedCount = 0;
426
665
  const errors = [];
666
+ const preparedJobs = [];
427
667
  for (let i = 0; i < jobs.length; i++) {
428
668
  const job = jobs[i];
429
669
  try {
430
- await this.jobs.insertOne({
670
+ const record = await (0, import_schema3.clean)(JobRecordSchema, {
671
+ _id: this.jobs.generateId(),
431
672
  jobName: job.name,
432
673
  uniqueIdentifier: job.uniqueIdentifier,
433
674
  params: job.params,
434
675
  nextRunAt: job.nextRunAt,
435
676
  priority: job.priority,
436
- type: "event"
677
+ type: "event",
678
+ ...job.partition === void 0 ? {} : { partition: job.partition }
437
679
  });
438
- scheduledCount++;
680
+ await (0, import_schema3.validate)(JobRecordSchema, record);
681
+ preparedJobs.push({ index: i, job, record });
439
682
  } catch (error) {
440
- if (error.isValidationError && Object.values(error.validationErrors).includes("notUnique") && job.uniqueIdentifier) {
441
- import_logger.logger.info(`Job "${job.name}" with identifier "${job.uniqueIdentifier}" already exists`);
442
- skippedCount++;
443
- } else {
683
+ errors.push({
684
+ index: i,
685
+ error: error instanceof Error ? error : new Error(String(error)),
686
+ job
687
+ });
688
+ }
689
+ }
690
+ if (preparedJobs.length === 0) {
691
+ return { scheduledCount: 0, skippedCount: 0, errors };
692
+ }
693
+ const rawCollection = await this.jobs.getRawCollection();
694
+ await this.jobs.createIndexesPromise;
695
+ const operations = preparedJobs.map(
696
+ ({ record }) => record.uniqueIdentifier === void 0 ? { insertOne: { document: record } } : {
697
+ updateOne: {
698
+ filter: { uniqueIdentifier: record.uniqueIdentifier },
699
+ update: { $setOnInsert: record },
700
+ upsert: true
701
+ }
702
+ }
703
+ );
704
+ let scheduledCount = 0;
705
+ let skippedCount = 0;
706
+ try {
707
+ const result = await rawCollection.bulkWrite(operations, { ordered: false });
708
+ scheduledCount = result.insertedCount + result.upsertedCount;
709
+ skippedCount = result.matchedCount;
710
+ } catch (error) {
711
+ const writeErrors = error instanceof import_mongodb2.MongoDB.MongoBulkWriteError ? Array.isArray(error.writeErrors) ? error.writeErrors : [error.writeErrors] : [];
712
+ if (!(error instanceof import_mongodb2.MongoDB.MongoBulkWriteError) || writeErrors.length === 0) {
713
+ for (const preparedJob of preparedJobs) {
444
714
  errors.push({
445
- index: i,
715
+ index: preparedJob.index,
446
716
  error: error instanceof Error ? error : new Error(String(error)),
447
- job
717
+ job: preparedJob.job
718
+ });
719
+ }
720
+ } else {
721
+ scheduledCount = error.result.insertedCount + error.result.upsertedCount;
722
+ skippedCount = error.result.matchedCount;
723
+ for (const writeError of writeErrors) {
724
+ const preparedJob = preparedJobs[writeError.index];
725
+ if (!preparedJob) continue;
726
+ if (writeError.code === 11e3 && preparedJob.record.uniqueIdentifier !== void 0) {
727
+ skippedCount++;
728
+ continue;
729
+ }
730
+ errors.push({
731
+ index: preparedJob.index,
732
+ error: new Error(writeError.errmsg),
733
+ job: preparedJob.job
448
734
  });
449
735
  }
450
736
  }
@@ -458,11 +744,55 @@ var JobsRepo = class {
458
744
  errors
459
745
  };
460
746
  }
747
+ async reconcilePartitions(options) {
748
+ if (options.jobNames.length === 0) return 0;
749
+ const rawCollection = await this.jobs.getRawCollection();
750
+ await this.jobs.createIndexesPromise;
751
+ const now = /* @__PURE__ */ new Date();
752
+ const invalidPartition = {
753
+ $or: [
754
+ { partition: { $exists: false } },
755
+ { partition: { $lt: 0 } },
756
+ { partition: { $gte: options.nPartitions } }
757
+ ]
758
+ };
759
+ const availableLock = {
760
+ $or: [{ lockedUntil: { $exists: false } }, { lockedUntil: { $lte: now } }]
761
+ };
762
+ const runnableJob = {
763
+ $or: [{ type: { $ne: "event" } }, { status: { $ne: "maxTriesReached" } }]
764
+ };
765
+ const jobs = await rawCollection.find({
766
+ jobName: { $in: options.jobNames },
767
+ $and: [invalidPartition, availableLock, runnableJob]
768
+ }).project({ _id: 1 }).limit(options.batchSize).toArray();
769
+ if (jobs.length === 0) return 0;
770
+ const result = await rawCollection.bulkWrite(
771
+ jobs.map((job) => ({
772
+ updateOne: {
773
+ filter: {
774
+ _id: job._id,
775
+ $and: [invalidPartition, availableLock, runnableJob]
776
+ },
777
+ update: { $set: { partition: getRandomPartition(options.nPartitions) } }
778
+ }
779
+ })),
780
+ { ordered: false }
781
+ );
782
+ return result.modifiedCount;
783
+ }
461
784
  };
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);
785
+ _init2 = __decoratorStart(null);
786
+ __decorateElement(_init2, 5, "jobs", _jobs_dec, JobsRepo);
787
+ JobsRepo = __decorateElement(_init2, 0, "JobsRepo", _JobsRepo_decorators, JobsRepo);
788
+ __runInitializers(_init2, 1, JobsRepo);
789
+
790
+ // src/schedule.ts
791
+ var import_services2 = require("@orion-js/services");
792
+
793
+ // src/services/EventsService.ts
794
+ var import_logger2 = require("@orion-js/logger");
795
+ var import_services = require("@orion-js/services");
466
796
 
467
797
  // src/services/getNextRunDate.ts
468
798
  var import_cron_parser = require("cron-parser");
@@ -492,176 +822,65 @@ var getNextRunDate = (options) => {
492
822
  };
493
823
 
494
824
  // src/services/EventsService.ts
495
- var _jobsRepo_dec, _EventsService_decorators, _init2;
825
+ var _jobsRepo_dec, _EventsService_decorators, _init3;
496
826
  _EventsService_decorators = [(0, import_services.Service)()], _jobsRepo_dec = [(0, import_services.Inject)(() => JobsRepo)];
497
827
  var EventsService = class {
498
828
  constructor() {
499
- __publicField(this, "jobsRepo", __runInitializers(_init2, 8, this)), __runInitializers(_init2, 11, this);
829
+ __publicField(this, "jobsRepo", __runInitializers(_init3, 8, this)), __runInitializers(_init3, 11, this);
500
830
  }
501
- async scheduleJob(options) {
831
+ async scheduleJob(options, nPartitions) {
502
832
  import_logger2.logger.debug("Scheduling job...", options);
833
+ const configuredPartitions = nPartitions ?? this.jobsRepo.getJobPartitionsCount(options.name);
503
834
  await this.jobsRepo.scheduleJob({
504
835
  name: options.name,
505
836
  priority: options.priority || 100,
506
837
  nextRunAt: getNextRunDate(options),
507
838
  params: options.params || null,
508
- uniqueIdentifier: options.uniqueIdentifier
839
+ uniqueIdentifier: options.uniqueIdentifier,
840
+ partition: configuredPartitions === void 0 ? void 0 : getRandomPartition(configuredPartitions)
509
841
  });
510
842
  }
511
- async scheduleJobs(jobs) {
843
+ async scheduleJobs(jobs, nPartitions) {
512
844
  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
- }));
845
+ const jobRecords = jobs.map((options) => {
846
+ const configuredPartitions = nPartitions ?? this.jobsRepo.getJobPartitionsCount(options.name);
847
+ return {
848
+ name: options.name,
849
+ priority: options.priority || 100,
850
+ nextRunAt: getNextRunDate(options),
851
+ params: options.params || null,
852
+ uniqueIdentifier: options.uniqueIdentifier,
853
+ partition: configuredPartitions === void 0 ? void 0 : getRandomPartition(configuredPartitions)
854
+ };
855
+ });
520
856
  return await this.jobsRepo.scheduleJobs(jobRecords);
521
857
  }
522
858
  };
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);
859
+ _init3 = __decoratorStart(null);
860
+ __decorateElement(_init3, 5, "jobsRepo", _jobsRepo_dec, EventsService);
861
+ EventsService = __decorateElement(_init3, 0, "EventsService", _EventsService_decorators, EventsService);
862
+ __runInitializers(_init3, 1, EventsService);
863
+
864
+ // src/schedule.ts
865
+ var eventsService = (0, import_services2.getInstance)(EventsService);
866
+ function scheduleJobInternal(options, nPartitions) {
867
+ return eventsService.scheduleJob(options, nPartitions);
868
+ }
869
+ function scheduleJobsInternal(jobs, nPartitions) {
870
+ return eventsService.scheduleJobs(jobs, nPartitions);
871
+ }
527
872
 
528
873
  // src/services/WorkerService.ts
529
874
  var import_helpers2 = require("@orion-js/helpers");
530
875
  var import_logger4 = require("@orion-js/logger");
531
- var import_services3 = require("@orion-js/services");
876
+ var import_services4 = require("@orion-js/services");
532
877
 
533
878
  // src/services/Executor.ts
534
879
  var import_api = require("@opentelemetry/api");
535
880
  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
881
+ var import_services3 = require("@orion-js/services");
663
882
  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)];
883
+ _Executor_decorators = [(0, import_services3.Service)()], _jobsRepo_dec2 = [(0, import_services3.Inject)(() => JobsRepo)], _jobsHistoryRepo_dec = [(0, import_services3.Inject)(() => JobsHistoryRepo)];
665
884
  var Executor = class {
666
885
  constructor() {
667
886
  __publicField(this, "jobsRepo", __runInitializers(_init4, 8, this)), __runInitializers(_init4, 11, this);
@@ -1001,10 +1220,14 @@ __runInitializers(_init4, 1, Executor);
1001
1220
  // src/services/WorkerService.ts
1002
1221
  var FILTERED_JOB_NAMES_ACQUISITION_HINT = "byJobName";
1003
1222
  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)];
1223
+ _WorkerService_decorators = [(0, import_services4.Service)()], _jobsRepo_dec3 = [(0, import_services4.Inject)(() => JobsRepo)], _executor_dec = [(0, import_services4.Inject)(() => Executor)];
1005
1224
  var WorkerService = class {
1006
1225
  constructor() {
1007
1226
  __publicField(this, "jobAcquisitionHintProbeIntervalMs", 30 * 60 * 1e3);
1227
+ __publicField(this, "partitionReconcilerIntervalMs", 5 * 60 * 1e3);
1228
+ __publicField(this, "partitionReconcilerJitterMs", 30 * 1e3);
1229
+ __publicField(this, "partitionReconcilerBatchPauseMs", 250);
1230
+ __publicField(this, "partitionReconcilerBatchSize", 500);
1008
1231
  __publicField(this, "jobsRepo", __runInitializers(_init5, 8, this)), __runInitializers(_init5, 11, this);
1009
1232
  __publicField(this, "executor", __runInitializers(_init5, 12, this)), __runInitializers(_init5, 15, this);
1010
1233
  }
@@ -1089,6 +1312,15 @@ var WorkerService = class {
1089
1312
  if (capacityPromises.length === 0) return;
1090
1313
  await Promise.race([...capacityPromises, workersInstance.stopSignal]);
1091
1314
  }
1315
+ getNextPartition(config, workersInstance) {
1316
+ const partition = workersInstance.partitionOrder[workersInstance.partitionCursor];
1317
+ workersInstance.partitionCursor++;
1318
+ if (workersInstance.partitionCursor >= config.nPartitions) {
1319
+ workersInstance.partitionOrder = createShuffledPartitions(config.nPartitions);
1320
+ workersInstance.partitionCursor = 0;
1321
+ }
1322
+ return partition;
1323
+ }
1092
1324
  async runSchedulerLoop(config, workersInstance) {
1093
1325
  const jobNames = this.getJobNames(config.jobs);
1094
1326
  const executeConfig = {
@@ -1098,6 +1330,7 @@ var WorkerService = class {
1098
1330
  onMaxTriesReached: config.onMaxTriesReached
1099
1331
  };
1100
1332
  import_logger4.logger.debug(`Running scheduler loop for jobs "${jobNames.join(", ")}"...`);
1333
+ let consecutivePartitionMisses = 0;
1101
1334
  while (workersInstance.running) {
1102
1335
  let didMiss = false;
1103
1336
  let allJobNamesAtCapacity = false;
@@ -1109,21 +1342,30 @@ var WorkerService = class {
1109
1342
  break;
1110
1343
  }
1111
1344
  const jobAcquisitionHint = availableJobNames.length < jobNames.length ? FILTERED_JOB_NAMES_ACQUISITION_HINT : workersInstance.jobAcquisitionHint;
1345
+ const partition = this.getNextPartition(config, workersInstance);
1112
1346
  const jobToRun = await this.jobsRepo.getJobAndLock(
1113
1347
  availableJobNames,
1114
1348
  config.defaultLockTime,
1115
- jobAcquisitionHint
1349
+ jobAcquisitionHint,
1350
+ partition
1116
1351
  );
1117
1352
  if (!jobToRun) {
1118
- import_logger4.logger.debug("No job to run");
1119
- didMiss = true;
1120
- break;
1353
+ consecutivePartitionMisses++;
1354
+ import_logger4.logger.debug(`No job to run in partition ${partition}`);
1355
+ if (consecutivePartitionMisses >= config.nPartitions) {
1356
+ consecutivePartitionMisses = 0;
1357
+ didMiss = true;
1358
+ break;
1359
+ }
1360
+ continue;
1121
1361
  }
1362
+ consecutivePartitionMisses = 0;
1122
1363
  this.startExecution(executeConfig, workersInstance, jobToRun);
1123
1364
  if (!workersInstance.running) break;
1124
1365
  }
1125
1366
  } catch (error) {
1126
1367
  import_logger4.logger.error("Error in job scheduler.", { error });
1368
+ consecutivePartitionMisses = 0;
1127
1369
  didMiss = true;
1128
1370
  }
1129
1371
  if (!workersInstance.running) return;
@@ -1159,11 +1401,13 @@ var WorkerService = class {
1159
1401
  workersInstance.jobAcquisitionHintProbeCycle++;
1160
1402
  try {
1161
1403
  for (let sample = 0; sample < 3; sample++) {
1404
+ const partition = getRandomPartition(config.nPartitions);
1162
1405
  for (const hint of [firstHint, secondHint]) {
1163
1406
  if (!workersInstance.running) return;
1164
1407
  const executionTimeMillis = await this.jobsRepo.getJobAcquisitionExecutionTime(
1165
1408
  jobNames,
1166
- hint
1409
+ hint,
1410
+ partition
1167
1411
  );
1168
1412
  if (!workersInstance.running) return;
1169
1413
  byHint[hint].push(executionTimeMillis);
@@ -1235,6 +1479,44 @@ var WorkerService = class {
1235
1479
  await this.waitForNextJobAcquisitionHintProbe(workersInstance);
1236
1480
  }
1237
1481
  }
1482
+ async reconcilePartitionBatch(config) {
1483
+ if (typeof this.jobsRepo.reconcilePartitions !== "function") return 0;
1484
+ try {
1485
+ return await this.jobsRepo.reconcilePartitions({
1486
+ jobNames: this.getJobNames(config.jobs),
1487
+ nPartitions: config.nPartitions,
1488
+ batchSize: this.partitionReconcilerBatchSize
1489
+ });
1490
+ } catch (error) {
1491
+ import_logger4.logger.warn("Job partition reconciliation failed; it will be retried.", { error });
1492
+ return 0;
1493
+ }
1494
+ }
1495
+ async waitForNextPartitionReconcile(workersInstance, delayMs) {
1496
+ await new Promise((resolve) => {
1497
+ let resolved = false;
1498
+ const finishWait = () => {
1499
+ if (resolved) return;
1500
+ resolved = true;
1501
+ clearTimeout(workersInstance.partitionReconcilerTimer);
1502
+ workersInstance.partitionReconcilerTimer = void 0;
1503
+ workersInstance.cancelPartitionReconcilerWait = void 0;
1504
+ resolve();
1505
+ };
1506
+ workersInstance.partitionReconcilerTimer = setTimeout(finishWait, delayMs);
1507
+ workersInstance.cancelPartitionReconcilerWait = finishWait;
1508
+ });
1509
+ }
1510
+ async runPartitionReconcilerLoop(config, workersInstance, initialReconcilePromise) {
1511
+ let modifiedCount = await initialReconcilePromise;
1512
+ while (workersInstance.running) {
1513
+ const didFillBatch = modifiedCount >= this.partitionReconcilerBatchSize;
1514
+ const delayMs = didFillBatch ? this.partitionReconcilerBatchPauseMs : this.partitionReconcilerIntervalMs + Math.floor(Math.random() * (this.partitionReconcilerJitterMs + 1));
1515
+ await this.waitForNextPartitionReconcile(workersInstance, delayMs);
1516
+ if (!workersInstance.running) return;
1517
+ modifiedCount = await this.reconcilePartitionBatch(config);
1518
+ }
1519
+ }
1238
1520
  createWorkersInstanceDefinition(config) {
1239
1521
  let signalStop;
1240
1522
  const stopSignal = new Promise((resolve) => {
@@ -1250,7 +1532,10 @@ var WorkerService = class {
1250
1532
  jobAcquisitionHintProbeCycle: 0,
1251
1533
  hasCompletedInitialJobAcquisitionHintProbe: false,
1252
1534
  pendingJobAcquisitionHintWins: 0,
1535
+ partitionOrder: createShuffledPartitions(config.nPartitions),
1536
+ partitionCursor: 0,
1253
1537
  jobAcquisitionHintProbePromise: Promise.resolve(),
1538
+ partitionReconcilerPromise: Promise.resolve(),
1254
1539
  schedulerPromise: Promise.resolve(),
1255
1540
  stopSignal,
1256
1541
  signalStop,
@@ -1260,14 +1545,16 @@ var WorkerService = class {
1260
1545
  stop: async () => {
1261
1546
  if (workersInstance.stopPromise) return workersInstance.stopPromise;
1262
1547
  workersInstance.stopPromise = (async () => {
1263
- var _a;
1548
+ var _a, _b;
1264
1549
  import_logger4.logger.info("Stopping job scheduler...");
1265
1550
  workersInstance.running = false;
1266
1551
  workersInstance.signalStop();
1267
1552
  (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
1553
+ (_b = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b.call(workersInstance);
1268
1554
  await Promise.all([
1269
1555
  workersInstance.schedulerPromise,
1270
- workersInstance.jobAcquisitionHintProbePromise
1556
+ workersInstance.jobAcquisitionHintProbePromise,
1557
+ workersInstance.partitionReconcilerPromise
1271
1558
  ]);
1272
1559
  const activeCapacity = Array.from(
1273
1560
  workersInstance.activeExecutions.values(),
@@ -1285,7 +1572,7 @@ var WorkerService = class {
1285
1572
  await Promise.all(
1286
1573
  jobs.filter((job) => job.type === "recurrent").map(async (job) => {
1287
1574
  import_logger4.logger.debug(`Ensuring records for job "${job.name}"...`);
1288
- await this.jobsRepo.ensureJobRecord(job);
1575
+ await this.jobsRepo.ensureJobRecord(job, getRandomPartition(config.nPartitions));
1289
1576
  })
1290
1577
  );
1291
1578
  }
@@ -1302,29 +1589,41 @@ var WorkerService = class {
1302
1589
  * @param userConfig - Configuration for the scheduler. Required field: jobs
1303
1590
  */
1304
1591
  startWorkers(userConfig) {
1592
+ var _a, _b;
1305
1593
  const maxTriesReachedRetentionMs = userConfig.maxTriesReachedRetentionMs === void 0 ? DEFAULT_MAX_TRIES_REACHED_RETENTION_MS : userConfig.maxTriesReachedRetentionMs;
1306
1594
  if (maxTriesReachedRetentionMs !== null && (!Number.isFinite(maxTriesReachedRetentionMs) || maxTriesReachedRetentionMs < 0)) {
1307
1595
  throw new Error("maxTriesReachedRetentionMs must be null, zero, or a positive number");
1308
1596
  }
1597
+ const nPartitions = userConfig.nPartitions ?? 1;
1598
+ validatePartitionsCount(nPartitions);
1309
1599
  const config = {
1310
1600
  pollInterval: 3e3,
1311
1601
  workersCount: 4,
1312
1602
  defaultLockTime: 30 * 1e3,
1313
1603
  ...userConfig,
1604
+ nPartitions,
1314
1605
  maxTriesReachedRetentionMs
1315
1606
  };
1316
- setNameToJobs(config.jobs);
1607
+ setNameToJobs(config.jobs, config.nPartitions);
1608
+ (_b = (_a = this.jobsRepo).configureJobPartitions) == null ? void 0 : _b.call(_a, this.getJobNames(config.jobs), config.nPartitions);
1317
1609
  const workersInstance = this.createWorkersInstanceDefinition(config);
1318
1610
  import_logger4.logger.debug("Starting job scheduler", config);
1319
1611
  workersInstance.jobAcquisitionHintProbePromise = this.runJobAcquisitionHintProbeLoop(
1320
1612
  config,
1321
1613
  workersInstance
1322
1614
  );
1615
+ const initialReconcilePromise = this.reconcilePartitionBatch(config);
1616
+ workersInstance.partitionReconcilerPromise = this.runPartitionReconcilerLoop(
1617
+ config,
1618
+ workersInstance,
1619
+ initialReconcilePromise
1620
+ );
1323
1621
  workersInstance.schedulerPromise = this.runScheduler(config, workersInstance).catch((error) => {
1324
- var _a;
1622
+ var _a2, _b2;
1325
1623
  workersInstance.running = false;
1326
1624
  workersInstance.signalStop();
1327
- (_a = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a.call(workersInstance);
1625
+ (_a2 = workersInstance.cancelJobAcquisitionHintProbeWait) == null ? void 0 : _a2.call(workersInstance);
1626
+ (_b2 = workersInstance.cancelPartitionReconcilerWait) == null ? void 0 : _b2.call(workersInstance);
1328
1627
  import_logger4.logger.error("Job scheduler stopped after an unexpected error.", { error });
1329
1628
  });
1330
1629
  return workersInstance;
@@ -1335,17 +1634,15 @@ __decorateElement(_init5, 5, "jobsRepo", _jobsRepo_dec3, WorkerService);
1335
1634
  __decorateElement(_init5, 5, "executor", _executor_dec, WorkerService);
1336
1635
  WorkerService = __decorateElement(_init5, 0, "WorkerService", _WorkerService_decorators, WorkerService);
1337
1636
  __runInitializers(_init5, 1, WorkerService);
1338
- function setNameToJobs(jobs) {
1637
+ function setNameToJobs(jobs, nPartitions) {
1339
1638
  for (const name of Object.keys(jobs)) {
1340
1639
  jobs[name].jobName = name;
1640
+ jobs[name].nPartitions = nPartitions;
1341
1641
  }
1342
1642
  }
1343
1643
 
1344
- // src/service/index.ts
1345
- var import_services4 = require("@orion-js/services");
1346
-
1347
1644
  // src/defineJob/index.ts
1348
- var import_schema3 = require("@orion-js/schema");
1645
+ var import_schema4 = require("@orion-js/schema");
1349
1646
  var import_cron_parser2 = require("cron-parser");
1350
1647
  var import_parse_duration = __toESM(require("parse-duration"), 1);
1351
1648
  function createEventJob(options) {
@@ -1359,12 +1656,15 @@ function createEventJob(options) {
1359
1656
  if (!jobDefinition.jobName) {
1360
1657
  throw new Error("This job has not been registered in the workers");
1361
1658
  }
1362
- 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
- });
1659
+ const params = jobDefinition.params ? await (0, import_schema4.cleanAndValidate)(jobDefinition.params, scheduleOptions.params) : scheduleOptions.params;
1660
+ return await scheduleJobInternal(
1661
+ {
1662
+ ...scheduleOptions,
1663
+ name: jobDefinition.jobName,
1664
+ params
1665
+ },
1666
+ jobDefinition.nPartitions
1667
+ );
1368
1668
  };
1369
1669
  jobDefinition.scheduleJobs = async (jobs) => {
1370
1670
  if (!jobDefinition.jobName) {
@@ -1372,7 +1672,7 @@ function createEventJob(options) {
1372
1672
  }
1373
1673
  const processedJobs = await Promise.all(
1374
1674
  jobs.map(async (scheduleOptions) => {
1375
- const params = jobDefinition.params ? await (0, import_schema3.cleanAndValidate)(jobDefinition.params, scheduleOptions.params) : scheduleOptions.params;
1675
+ const params = jobDefinition.params ? await (0, import_schema4.cleanAndValidate)(jobDefinition.params, scheduleOptions.params) : scheduleOptions.params;
1376
1676
  return {
1377
1677
  ...scheduleOptions,
1378
1678
  name: jobDefinition.jobName,
@@ -1380,7 +1680,7 @@ function createEventJob(options) {
1380
1680
  };
1381
1681
  })
1382
1682
  );
1383
- return await scheduleJobs(processedJobs);
1683
+ return await scheduleJobsInternal(processedJobs, jobDefinition.nPartitions);
1384
1684
  };
1385
1685
  return jobDefinition;
1386
1686
  }
@@ -1409,13 +1709,14 @@ var defineJob = (options) => {
1409
1709
  };
1410
1710
 
1411
1711
  // src/service/index.ts
1712
+ var import_services5 = require("@orion-js/services");
1412
1713
  var serviceMetadata = /* @__PURE__ */ new WeakMap();
1413
1714
  var jobsMetadata = /* @__PURE__ */ new Map();
1414
1715
  var jobEntriesByClass = /* @__PURE__ */ new Map();
1415
1716
  var pendingJobEntries = {};
1416
1717
  function Jobs() {
1417
1718
  return (target, context) => {
1418
- (0, import_services4.Service)()(target, context);
1719
+ (0, import_services5.Service)()(target, context);
1419
1720
  serviceMetadata.set(target, { _serviceType: "jobs" });
1420
1721
  if (Object.keys(pendingJobEntries).length > 0) {
1421
1722
  jobEntriesByClass.set(target, pendingJobEntries);
@@ -1464,7 +1765,7 @@ function initializeJobsIfNeeded(instance) {
1464
1765
  jobsMetadata.set(instance, jobs);
1465
1766
  }
1466
1767
  function getServiceJobs(target) {
1467
- const instance = (0, import_services4.getInstance)(target);
1768
+ const instance = (0, import_services5.getInstance)(target);
1468
1769
  if (!serviceMetadata.has(instance.constructor)) {
1469
1770
  throw new Error("You must pass a class decorated with @Jobs to getServiceJobs");
1470
1771
  }
@@ -1478,18 +1779,17 @@ function getServiceJobs(target) {
1478
1779
  }
1479
1780
 
1480
1781
  // 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);
1782
+ var workerService = (0, import_services6.getInstance)(WorkerService);
1783
+ var jobsHistoryRepo = (0, import_services6.getInstance)(JobsHistoryRepo);
1784
+ var jobsRepo = (0, import_services6.getInstance)(JobsRepo);
1485
1785
  var startWorkers = (config) => {
1486
1786
  return workerService.startWorkers(config);
1487
1787
  };
1488
1788
  var scheduleJob = (options) => {
1489
- return eventsService.scheduleJob(options);
1789
+ return scheduleJobInternal(options);
1490
1790
  };
1491
1791
  var scheduleJobs = (jobs) => {
1492
- return eventsService.scheduleJobs(jobs);
1792
+ return scheduleJobsInternal(jobs);
1493
1793
  };
1494
1794
  // Annotate the CommonJS export names for ESM import in node:
1495
1795
  0 && (module.exports = {