@lavoro/core 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js ADDED
@@ -0,0 +1,984 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
8
+ var __reflectGet = Reflect.get;
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __spreadValues = (a, b) => {
11
+ for (var prop in b || (b = {}))
12
+ if (__hasOwnProp.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ if (__getOwnPropSymbols)
15
+ for (var prop of __getOwnPropSymbols(b)) {
16
+ if (__propIsEnum.call(b, prop))
17
+ __defNormalProp(a, prop, b[prop]);
18
+ }
19
+ return a;
20
+ };
21
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
23
+ var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
24
+ var __async = (__this, __arguments, generator) => {
25
+ return new Promise((resolve, reject) => {
26
+ var fulfilled = (value) => {
27
+ try {
28
+ step(generator.next(value));
29
+ } catch (e) {
30
+ reject(e);
31
+ }
32
+ };
33
+ var rejected = (value) => {
34
+ try {
35
+ step(generator.throw(value));
36
+ } catch (e) {
37
+ reject(e);
38
+ }
39
+ };
40
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
41
+ step((generator = generator.apply(__this, __arguments)).next());
42
+ });
43
+ };
44
+
45
+ // src/logger.ts
46
+ import pino from "pino";
47
+ var Logger = class _Logger {
48
+ constructor(internalLogger) {
49
+ __publicField(this, "internalLogger");
50
+ this.internalLogger = internalLogger;
51
+ }
52
+ child(obj) {
53
+ return new _Logger(this.internalLogger.child(obj));
54
+ }
55
+ trace(msg, obj) {
56
+ this.internalLogger.trace(msg, obj);
57
+ }
58
+ debug(msg, obj) {
59
+ this.internalLogger.debug(msg, obj);
60
+ }
61
+ warn(msg, obj) {
62
+ this.internalLogger.warn(msg, obj);
63
+ }
64
+ error(msg, obj) {
65
+ this.internalLogger.error(msg, obj);
66
+ }
67
+ fatal(msg, obj) {
68
+ this.internalLogger.fatal(msg, obj);
69
+ }
70
+ info(msg, obj) {
71
+ this.internalLogger.info(msg, obj);
72
+ }
73
+ };
74
+ function createDefaultLogger(name, level = "info") {
75
+ return new Logger(pino({ name, level }));
76
+ }
77
+
78
+ // src/queue/define_config.ts
79
+ import { RuntimeException } from "@poppinss/utils";
80
+ function defineConfig(config) {
81
+ if (!config.connection) {
82
+ throw new RuntimeException(
83
+ 'Missing "connection" property in queue config file'
84
+ );
85
+ }
86
+ if (!config.connections) {
87
+ throw new RuntimeException(
88
+ 'Missing "connections" property in queue config file'
89
+ );
90
+ }
91
+ if (!config.connections[config.connection]) {
92
+ throw new RuntimeException(
93
+ `Missing "connections.${String(config.connection)}". It is referenced by the "connection" property`
94
+ );
95
+ }
96
+ Object.keys(config.connections).forEach((connectionName) => {
97
+ const connection = config.connections[connectionName];
98
+ if (!connection.queues || Object.keys(connection.queues).length === 0) {
99
+ throw new RuntimeException(
100
+ `Connection "${connectionName}" must have at least one queue defined`
101
+ );
102
+ }
103
+ });
104
+ return config;
105
+ }
106
+
107
+ // src/queue/pending_dispatch.ts
108
+ var PendingDispatch = class {
109
+ constructor(job, payload) {
110
+ this.job = job;
111
+ this.payload = payload;
112
+ }
113
+ onConnection(connection) {
114
+ this.job.options.connection = connection;
115
+ return this;
116
+ }
117
+ onQueue(queue) {
118
+ this.job.options.queue = queue;
119
+ return this;
120
+ }
121
+ // public withQueueServiceResolver(
122
+ // queueServiceResolver: () => Promise<Queue>,
123
+ // ): this {
124
+ // this.job.setQueueServiceResolver(queueServiceResolver)
125
+ // return this
126
+ // }
127
+ execute() {
128
+ return __async(this, null, function* () {
129
+ if (!this.job.getQueueServiceResolver) {
130
+ throw new Error(
131
+ "Queue service resolver is not set.\nDid you forget to call Job.setDefaultQueueServiceResolver()?"
132
+ );
133
+ }
134
+ const queue = yield this.job.getQueueServiceResolver();
135
+ yield queue.enqueue(this.job, this.payload);
136
+ });
137
+ }
138
+ /**
139
+ * By defining the "then" method, PendingDispatch becomes "thenable",
140
+ * allowing it to trigger automatically when await is called.
141
+ */
142
+ then(onfulfilled, onrejected) {
143
+ return __async(this, null, function* () {
144
+ return this.execute().then(onfulfilled, onrejected);
145
+ });
146
+ }
147
+ };
148
+
149
+ // src/queue/contracts/job.ts
150
+ import { randomUUID } from "crypto";
151
+ var _Job = class _Job {
152
+ constructor() {
153
+ __publicField(this, "_id", randomUUID());
154
+ __publicField(this, "options", {
155
+ retries: 3,
156
+ delay: 0
157
+ });
158
+ __publicField(this, "queueServiceResolver");
159
+ }
160
+ static compileName(queue, name) {
161
+ return `${queue}_${name}`;
162
+ }
163
+ static parseName(name) {
164
+ const [q, n] = name.split(/_(.+)/);
165
+ return { queue: q, name: n };
166
+ }
167
+ get connection() {
168
+ return this.options.connection;
169
+ }
170
+ set connection(connection) {
171
+ this.options.connection = connection;
172
+ }
173
+ get queue() {
174
+ return this.options.queue;
175
+ }
176
+ set queue(queue) {
177
+ this.options.queue = queue;
178
+ }
179
+ get name() {
180
+ return this.constructor.name;
181
+ }
182
+ get id() {
183
+ return this._id;
184
+ }
185
+ set id(id) {
186
+ this._id = id;
187
+ }
188
+ get fullyQualifiedName() {
189
+ if (!this.options.queue) {
190
+ throw new Error("Queue is not set.");
191
+ }
192
+ return _Job.compileName(this.options.queue, this.name);
193
+ }
194
+ static setDefaultQueueServiceResolver(queueServiceResolver) {
195
+ this.defaultQueueServiceResolver = queueServiceResolver;
196
+ }
197
+ setQueueServiceResolver(queueServiceResolver) {
198
+ this.queueServiceResolver = queueServiceResolver;
199
+ }
200
+ get getQueueServiceResolver() {
201
+ return this.queueServiceResolver || _Job.defaultQueueServiceResolver;
202
+ }
203
+ /**
204
+ * Dispatch a job of type with a typed payload.
205
+ */
206
+ static dispatch(payload) {
207
+ const job = new this();
208
+ return new PendingDispatch(job, payload);
209
+ }
210
+ };
211
+ __publicField(_Job, "defaultQueueServiceResolver");
212
+ var Job = _Job;
213
+
214
+ // src/queue/contracts/queue_driver.ts
215
+ var QueueDriver = class {
216
+ constructor(config, options, driverConfig = {}) {
217
+ this.config = config;
218
+ this.options = options;
219
+ this.driverConfig = driverConfig;
220
+ __publicField(this, "logger");
221
+ __publicField(this, "registeredQueues", /* @__PURE__ */ new Set());
222
+ __publicField(this, "registeredJobs", /* @__PURE__ */ new Map());
223
+ __publicField(this, "connection");
224
+ this.logger = createDefaultLogger("queue");
225
+ }
226
+ setLogger(logger) {
227
+ this.logger = logger;
228
+ }
229
+ getMergedWorkerOptions(queue, options) {
230
+ const base = this.options[queue] || {};
231
+ return __spreadValues(__spreadValues({}, base), options || {});
232
+ }
233
+ listen(queue, options) {
234
+ return __async(this, null, function* () {
235
+ if (this.registeredQueues.has(queue)) {
236
+ throw new Error(`Queue '${queue}' already registered`);
237
+ }
238
+ this.registeredQueues.add(queue);
239
+ const workerOptions = this.getMergedWorkerOptions(queue, options);
240
+ this.logger.trace(
241
+ { connection: this.connection, queue, options: workerOptions },
242
+ "Listening queue"
243
+ );
244
+ });
245
+ }
246
+ register(job) {
247
+ return __async(this, null, function* () {
248
+ if (this.registeredJobs.has(job.name)) {
249
+ return;
250
+ }
251
+ this.registeredJobs.set(job.name, job);
252
+ this.logger.trace(
253
+ { connection: this.connection, job: job.name },
254
+ "Registered job"
255
+ );
256
+ });
257
+ }
258
+ unregister(job) {
259
+ return __async(this, null, function* () {
260
+ if (!this.registeredJobs.has(job.name)) {
261
+ return;
262
+ }
263
+ this.registeredJobs.delete(job.name);
264
+ this.logger.trace(
265
+ { connection: this.connection, job: job.name },
266
+ "Unregistered job"
267
+ );
268
+ });
269
+ }
270
+ start() {
271
+ return __async(this, null, function* () {
272
+ for (const [queue, options] of Object.entries(this.options)) {
273
+ yield this.listen(queue, options);
274
+ }
275
+ });
276
+ }
277
+ stop(_options) {
278
+ return __async(this, null, function* () {
279
+ this.registeredQueues.clear();
280
+ this.registeredJobs.clear();
281
+ });
282
+ }
283
+ checkIfQueueIsRegistered(queue) {
284
+ if (!this.registeredQueues.has(queue)) {
285
+ throw new Error(`Queue '${queue}' is not registered.`);
286
+ }
287
+ }
288
+ checkIfJobIsRegistered(job) {
289
+ if (!this.registeredJobs.has(job)) {
290
+ throw new Error(`Job '${job}' is not registered.`);
291
+ }
292
+ }
293
+ getDefaultQueue() {
294
+ if (this.registeredQueues.size === 0) {
295
+ throw new Error(
296
+ `No queues registered for connection: ${this.connection}.`
297
+ );
298
+ }
299
+ return this.registeredQueues.values().next().value;
300
+ }
301
+ /**
302
+ * Parent method to be extended by the driver.
303
+ * It implements checks for the job and queue being registered.
304
+ */
305
+ enqueue(job, payload) {
306
+ return __async(this, null, function* () {
307
+ if (!job.options.queue) {
308
+ job.options.queue = this.getDefaultQueue();
309
+ }
310
+ this.checkIfQueueIsRegistered(job.options.queue);
311
+ this.checkIfJobIsRegistered(job.name);
312
+ return Promise.resolve();
313
+ });
314
+ }
315
+ /**
316
+ * Clean up resources associated with a lock factory created by this driver.
317
+ * This is called when the queue is stopped to ensure proper resource cleanup.
318
+ *
319
+ * @param lockFactory - The lock factory instance to clean up
320
+ */
321
+ destroyLockProvider(_lockFactory) {
322
+ return __async(this, null, function* () {
323
+ });
324
+ }
325
+ };
326
+
327
+ // src/schedule/schedule_interval.ts
328
+ function parseTime(time) {
329
+ const [hour, minute] = time.split(":");
330
+ if (!hour || !minute || hour.length !== 2 || minute.length !== 2) {
331
+ throw new Error("Invalid time format");
332
+ }
333
+ return [Number(hour), Number(minute)];
334
+ }
335
+ function dayOfWeekToNumber(dayOfWeek) {
336
+ switch (dayOfWeek) {
337
+ case "sunday":
338
+ return 0;
339
+ case "monday":
340
+ return 1;
341
+ case "tuesday":
342
+ return 2;
343
+ case "wednesday":
344
+ return 3;
345
+ case "thursday":
346
+ return 4;
347
+ case "friday":
348
+ return 5;
349
+ case "saturday":
350
+ return 6;
351
+ }
352
+ }
353
+ function intervalToCron(interval, options = {}) {
354
+ const { minute = 0, hour = 0, dayOfMonth = 1, dayOfWeek } = options;
355
+ const dayOfWeekNumber = dayOfWeekToNumber(dayOfWeek != null ? dayOfWeek : "sunday");
356
+ const patterns = {
357
+ second: "* * * * * *",
358
+ "two seconds": "*/2 * * * * *",
359
+ "three seconds": "*/3 * * * * *",
360
+ "four seconds": "*/4 * * * * *",
361
+ "five seconds": "*/5 * * * * *",
362
+ "ten seconds": "*/10 * * * * *",
363
+ "fifteen seconds": "*/15 * * * * *",
364
+ "twenty seconds": "*/20 * * * * *",
365
+ "thirty seconds": "*/30 * * * * *",
366
+ minute: "* * * * *",
367
+ "two minutes": "*/2 * * * *",
368
+ "three minutes": "*/3 * * * *",
369
+ "four minutes": "*/4 * * * *",
370
+ "five minutes": "*/5 * * * *",
371
+ "ten minutes": "*/10 * * * *",
372
+ "fifteen minutes": "*/15 * * * *",
373
+ "twenty minutes": "*/20 * * * *",
374
+ "thirty minutes": "*/30 * * * *",
375
+ hour: `${minute} * * * *`,
376
+ "two hours": `${minute} */2 * * *`,
377
+ "three hours": `${minute} */3 * * *`,
378
+ "four hours": `${minute} */4 * * *`,
379
+ "five hours": `${minute} */5 * * *`,
380
+ "six hours": `${minute} */6 * * *`,
381
+ "seven hours": `${minute} */7 * * *`,
382
+ "eight hours": `${minute} */8 * * *`,
383
+ "nine hours": `${minute} */9 * * *`,
384
+ "ten hours": `${minute} */10 * * *`,
385
+ "eleven hours": `${minute} */11 * * *`,
386
+ "twelve hours": `${minute} */12 * * *`,
387
+ day: `${minute} ${hour} * * *`,
388
+ week: `${minute} ${hour} * * ${dayOfWeekNumber}`,
389
+ sunday: `${minute} ${hour} * * 0`,
390
+ monday: `${minute} ${hour} * * 1`,
391
+ tuesday: `${minute} ${hour} * * 2`,
392
+ wednesday: `${minute} ${hour} * * 3`,
393
+ thursday: `${minute} ${hour} * * 4`,
394
+ friday: `${minute} ${hour} * * 5`,
395
+ saturday: `${minute} ${hour} * * 6`,
396
+ month: `${minute} ${hour} ${dayOfWeek ? "*" : dayOfMonth} * ${dayOfWeek ? dayOfWeekNumber + "#1" : "*"}`,
397
+ "last day of month": `${minute} ${hour} L * *`
398
+ // quarter: `${minute} ${hour} ${dayOfWeek ? '*' : dayOfMonth} */3 ${dayOfWeek ? dayOfWeekNumber : '*'}`,
399
+ // year: `${minute} ${hour} ${dayOfMonth} 1 ${dayOfWeek ? dayOfWeekNumber : '*'}`,
400
+ };
401
+ return patterns[interval];
402
+ }
403
+
404
+ // src/schedule/schedule_registry.ts
405
+ var ScheduleRegistry = class {
406
+ static add(name, cron) {
407
+ if (this.instances[name]) {
408
+ throw new Error(`Cron instance with name '${name}' already exists`);
409
+ }
410
+ this.instances[name] = cron;
411
+ }
412
+ static all() {
413
+ return this.instances;
414
+ }
415
+ static get(name) {
416
+ return this.instances[name];
417
+ }
418
+ static clear(name) {
419
+ var _a;
420
+ if (name) {
421
+ (_a = this.instances[name]) == null ? void 0 : _a.stop();
422
+ delete this.instances[name];
423
+ } else {
424
+ Object.values(this.instances).forEach((cron) => cron.stop());
425
+ this.instances = {};
426
+ }
427
+ }
428
+ };
429
+ __publicField(ScheduleRegistry, "instances", {});
430
+
431
+ // src/schedule/pending_schedule.ts
432
+ import { Cron } from "croner";
433
+ import { createHash } from "crypto";
434
+ var getDistributedLockKey = (name) => {
435
+ const hash = createHash("sha1").update(name).digest("hex").slice(0, 8);
436
+ return `lavoro:schedule:${hash}`;
437
+ };
438
+ var PendingSchedule = class {
439
+ constructor(name, cb, lockProviderResolver) {
440
+ this.name = name;
441
+ this.cb = cb;
442
+ this.lockProviderResolver = lockProviderResolver;
443
+ __publicField(this, "cronPattern");
444
+ __publicField(this, "interval", "day");
445
+ __publicField(this, "intervalOptions");
446
+ __publicField(this, "distributedLockOptions", {
447
+ key: getDistributedLockKey,
448
+ ttl: "1m",
449
+ // TODO: Decide on a default TTL
450
+ overlap: false,
451
+ handOff: false
452
+ });
453
+ }
454
+ /**
455
+ * Schedule using a cron pattern.
456
+ * You can use https://crontab.guru to generate a cron pattern.
457
+ *
458
+ * @param pattern - Cron pattern string
459
+ *
460
+ * @example
461
+ * Schedule.call('my-task', () => {}).cron('0 0 * * *') // Daily at midnight
462
+ * Schedule.call('my-task', () => {}).cron('*\/5 * * * *') // Every 5 minutes
463
+ * Schedule.call('my-task', () => {}).cron('0 *\/2 * * *') // Every 2 hours
464
+ */
465
+ cron(pattern) {
466
+ this.cronPattern = pattern;
467
+ return this;
468
+ }
469
+ /**
470
+ * Schedule to run at a specific interval.
471
+ * For longer intervals (hour, day, week, etc.), you can customize when they run.
472
+ *
473
+ * @param interval - The schedule interval
474
+ * @param options - Options to customize the cron pattern
475
+ *
476
+ * @example
477
+ * Schedule.call('my-task', () => {}).every('minute')
478
+ * Schedule.call('my-task', () => {}).every('hour', { minute: 30 })
479
+ * Schedule.call('my-task', () => {}).every('day', { hour: 14, minute: 30 })
480
+ * Schedule.call('my-task', () => {}).every('week', { dayOfWeek: 1, hour: 9 })
481
+ */
482
+ every(interval, options) {
483
+ this.interval = interval;
484
+ this.intervalOptions = options;
485
+ this.cronPattern = intervalToCron(interval, options);
486
+ return this;
487
+ }
488
+ on(dayOfWeek) {
489
+ var _a;
490
+ const valid = [
491
+ //
492
+ "week",
493
+ "month"
494
+ ];
495
+ if (!valid.includes(this.interval)) {
496
+ throw new Error(
497
+ `.on() can only be used for weekly intervals or larger. Current interval: '${this.interval}'`
498
+ );
499
+ }
500
+ this.intervalOptions = __spreadProps(__spreadValues({}, (_a = this.intervalOptions) != null ? _a : {}), { dayOfWeek });
501
+ this.cronPattern = intervalToCron(this.interval, this.intervalOptions);
502
+ return this;
503
+ }
504
+ at(time) {
505
+ var _a;
506
+ const valid = [
507
+ "day",
508
+ "week",
509
+ "sunday",
510
+ "monday",
511
+ "tuesday",
512
+ "wednesday",
513
+ "thursday",
514
+ "friday",
515
+ "saturday",
516
+ "month",
517
+ "last day of month"
518
+ ];
519
+ if (!valid.includes(this.interval)) {
520
+ throw new Error(
521
+ `.at() can only be used for daily intervals or larger. Current interval: '${this.interval}'`
522
+ );
523
+ }
524
+ const [hour, minute] = parseTime(time);
525
+ this.intervalOptions = __spreadProps(__spreadValues({}, (_a = this.intervalOptions) != null ? _a : {}), { hour, minute });
526
+ this.cronPattern = intervalToCron(this.interval, this.intervalOptions);
527
+ return this;
528
+ }
529
+ /**
530
+ * Set the duration during which other instances
531
+ * of the same task will be prevented from running.
532
+ *
533
+ * This should be roughly the duration of the task execution or longer
534
+ * since the lock will be released automatically after the task execution.
535
+ *
536
+ * @param ttl - The lock duration
537
+ *
538
+ * @example
539
+ * Schedule.call('my-task', () => {}).lockFor('10s')
540
+ */
541
+ lockFor(ttl) {
542
+ this.distributedLockOptions.ttl = ttl;
543
+ return this;
544
+ }
545
+ /**
546
+ * Allow overlapping executions of the same task.
547
+ *
548
+ * @example
549
+ * Schedule.call('my-task', () => {}).overlapping()
550
+ */
551
+ overlapping() {
552
+ this.distributedLockOptions.overlap = true;
553
+ return this;
554
+ }
555
+ execute() {
556
+ return __async(this, null, function* () {
557
+ if (!this.cronPattern) {
558
+ throw new Error(
559
+ "No schedule pattern defined. To schedule a task, set interval explicitly."
560
+ );
561
+ }
562
+ ScheduleRegistry.add(
563
+ this.name,
564
+ new Cron(this.cronPattern, () => __async(this, null, function* () {
565
+ if (this.distributedLockOptions.overlap) {
566
+ yield this.cb();
567
+ return;
568
+ }
569
+ const key = this.distributedLockOptions.key(this.name);
570
+ const ttl = this.distributedLockOptions.ttl;
571
+ const lockProvider = this.lockProviderResolver();
572
+ const lock = lockProvider.createLock(key, ttl);
573
+ const acquired = yield lock.acquireImmediately();
574
+ if (!acquired) {
575
+ return;
576
+ }
577
+ try {
578
+ if (this.distributedLockOptions.handOff) {
579
+ yield this.cb(lock.serialize());
580
+ } else {
581
+ yield this.cb();
582
+ yield lock.forceRelease();
583
+ }
584
+ } catch (error) {
585
+ yield lock.forceRelease();
586
+ throw error;
587
+ } finally {
588
+ }
589
+ }))
590
+ );
591
+ });
592
+ }
593
+ /**
594
+ * By defining the "then" method, PendingDispatch becomes "thenable",
595
+ * allowing it to trigger automatically when await is called.
596
+ */
597
+ then(onfulfilled, onrejected) {
598
+ return __async(this, null, function* () {
599
+ return this.execute().then(onfulfilled, onrejected);
600
+ });
601
+ }
602
+ };
603
+
604
+ // src/schedule/pending_job_schedule.ts
605
+ var PendingJobSchedule = class _PendingJobSchedule extends PendingSchedule {
606
+ constructor(job, payload, lockProviderResolver) {
607
+ super(
608
+ job.name,
609
+ (serializedLock) => __async(this, null, function* () {
610
+ if (serializedLock) {
611
+ this.payload._lock = serializedLock;
612
+ }
613
+ yield this.dispatch.then(void 0, (error) => {
614
+ throw error;
615
+ });
616
+ }),
617
+ lockProviderResolver
618
+ );
619
+ this.job = job;
620
+ this.payload = payload;
621
+ this.lockProviderResolver = lockProviderResolver;
622
+ /**
623
+ * Use composition pattern and store pending
624
+ * job dispatch inside a pending schedule.
625
+ */
626
+ __publicField(this, "dispatch");
627
+ __publicField(this, "_connection");
628
+ __publicField(this, "_queue");
629
+ this.job = job;
630
+ this.payload = payload;
631
+ this.dispatch = new PendingDispatch(job, payload);
632
+ this.distributedLockOptions.handOff = true;
633
+ }
634
+ get jobName() {
635
+ return `${this._connection}_${this._queue}_${this.job.name}`;
636
+ }
637
+ onConnection(connection) {
638
+ this._connection = connection;
639
+ this.dispatch.onConnection(connection);
640
+ return this;
641
+ }
642
+ onQueue(queue) {
643
+ this._queue = queue;
644
+ this.dispatch.onQueue(queue);
645
+ return this;
646
+ }
647
+ // public withQueueServiceResolver(queueServiceResolver: () => Promise<Queue>) {
648
+ // this.dispatch.withQueueServiceResolver(queueServiceResolver)
649
+ // return this
650
+ // }
651
+ execute() {
652
+ return __async(this, null, function* () {
653
+ var _a, _b, _c;
654
+ const queueServiceResolver = this.job.getQueueServiceResolver;
655
+ if (queueServiceResolver) {
656
+ const queue = yield queueServiceResolver();
657
+ const connection = (_a = this.job.options.connection) != null ? _a : queue.getDefaultConnection();
658
+ const connectionLockService = queue.getLockProvider(connection);
659
+ if (connectionLockService) {
660
+ this.lockProviderResolver = () => connectionLockService;
661
+ }
662
+ if (this.job.id) {
663
+ queue.registerScheduledJob(this.job.id);
664
+ }
665
+ this._connection = (_b = this._connection) != null ? _b : queue.getDefaultConnection();
666
+ this._queue = (_c = this._queue) != null ? _c : queue.getDefaultQueue();
667
+ }
668
+ this.name = this.jobName;
669
+ yield __superGet(_PendingJobSchedule.prototype, this, "execute").call(this);
670
+ });
671
+ }
672
+ };
673
+
674
+ // src/schedule/schedule.ts
675
+ import { LockFactory } from "@verrou/core";
676
+ import { memoryStore } from "@verrou/core/drivers/memory";
677
+ var defaultScheduleLockProvider = new LockFactory(
678
+ memoryStore().factory()
679
+ );
680
+ var Schedule = class {
681
+ /**
682
+ * Set a custom lock provider resolver for distributed locking.
683
+ *
684
+ * This allows dynamic resolution of lock provider instances, useful when
685
+ * integrating with Queue or other services that manage lock instances.
686
+ *
687
+ * @param resolver - Function that returns a LockFactory instance
688
+ *
689
+ * @example
690
+ * import { LockFactory } from '@verrou/core'
691
+ * import { redisStore } from '@verrou/core/drivers/redis'
692
+ *
693
+ * const customLockProvider = new LockFactory(
694
+ * redisStore({ connection: redisClient })
695
+ * )
696
+ *
697
+ * Schedule.setLockProviderResolver(() => customLockProvider)
698
+ */
699
+ static setLockProviderResolver(resolver) {
700
+ this.defaultLockProviderResolver = resolver;
701
+ }
702
+ /**
703
+ * Get the current lock provider instance from the resolver.
704
+ * @internal
705
+ */
706
+ static getLockProvider() {
707
+ return this.defaultLockProviderResolver();
708
+ }
709
+ /**
710
+ * Clear all scheduled tasks (or specific one if name is specified).
711
+ *
712
+ * @param name - The name of the task to clear
713
+ *
714
+ * @example
715
+ * Schedule.clear() // Clear all tasks
716
+ * Schedule.clear('my-task') // Clear specific task
717
+ */
718
+ static clear(name) {
719
+ if (name) {
720
+ ScheduleRegistry.clear(name);
721
+ } else {
722
+ Object.keys(ScheduleRegistry.all()).forEach(
723
+ (name2) => ScheduleRegistry.clear(name2)
724
+ );
725
+ }
726
+ }
727
+ /**
728
+ * Schedule a callback to run at specified intervals.
729
+ *
730
+ * @param name - Unique identifier for the task (required for distributed systems)
731
+ * @param cb - The callback function to execute
732
+ *
733
+ * @example
734
+ * // Using cron pattern
735
+ * const cleanupUsers = async () => { ... }
736
+ * Schedule.call('cleanup-users', cleanupUsers).cron('0 0 * * *')
737
+ *
738
+ * // Using convenience methods
739
+ * Schedule.call('hourly-task', () => { ... }).hourly()
740
+ * Schedule.call('daily-task', () => { ... }).daily()
741
+ */
742
+ static call(name, cb) {
743
+ return new PendingSchedule(name, cb, this.defaultLockProviderResolver);
744
+ }
745
+ /**
746
+ * Schedule a job to run at specified intervals.
747
+ *
748
+ * You can specify the connection and queue to use for the job
749
+ * the same way you would dispatch a job.
750
+ *
751
+ * @param job - The job class to schedule
752
+ * @param payload - The payload to pass to the job
753
+ *
754
+ * @example
755
+ * Schedule.job(TestJob, { arg1: 'hello', arg2: 1 }).every('minute')
756
+ * Schedule.job(TestJob, { arg1: 'hello', arg2: 1 })
757
+ * .onConnection('main')
758
+ * .onQueue('default')
759
+ * .every('minute')
760
+ */
761
+ static job(job, payload) {
762
+ return new PendingJobSchedule(
763
+ new job(),
764
+ payload,
765
+ this.defaultLockProviderResolver
766
+ );
767
+ }
768
+ };
769
+ /**
770
+ * Default lock provider resolver for distributed locking.
771
+ * Returns the default memory-based instance if not overridden.
772
+ */
773
+ __publicField(Schedule, "defaultLockProviderResolver", () => defaultScheduleLockProvider);
774
+
775
+ // src/queue/queue.ts
776
+ var Queue = class {
777
+ constructor(config) {
778
+ this.config = config;
779
+ __publicField(this, "drivers", /* @__PURE__ */ new Map());
780
+ __publicField(this, "started", false);
781
+ __publicField(this, "logger");
782
+ __publicField(this, "scheduledJobs", /* @__PURE__ */ new Set());
783
+ __publicField(this, "lockFactories", /* @__PURE__ */ new Map());
784
+ this.logger = (config == null ? void 0 : config.logger) || createDefaultLogger("queue");
785
+ for (const [connection, driverConfig] of Object.entries(
786
+ this.config.connections
787
+ )) {
788
+ this.logger.trace(
789
+ { connection, driver: driverConfig.driver },
790
+ "Creating queue driver"
791
+ );
792
+ const driver = this.createDriver(driverConfig);
793
+ driver.connection = connection;
794
+ this.drivers.set(driver.connection, driver);
795
+ }
796
+ }
797
+ createDriver(config) {
798
+ const { constructor: driverConstructor, config: driverConfig } = config.driver;
799
+ const driver = new driverConstructor(
800
+ this.config,
801
+ config.queues,
802
+ driverConfig
803
+ );
804
+ driver.setLogger(this.logger);
805
+ return driver;
806
+ }
807
+ start() {
808
+ return __async(this, null, function* () {
809
+ if (this.started) {
810
+ this.logger.warn("Queue service already started");
811
+ return;
812
+ }
813
+ for (const [connection, driverConfig] of Object.entries(
814
+ this.config.connections
815
+ )) {
816
+ const driver = this.drivers.get(connection);
817
+ if (!driver) {
818
+ throw new Error(`Driver not found for connection: ${connection}.`);
819
+ }
820
+ if (!driverConfig.lockProvider) {
821
+ const lockFactory = driver.createLockProvider();
822
+ if (lockFactory) {
823
+ this.lockFactories.set(connection, lockFactory);
824
+ this.logger.trace(
825
+ { connection, driver: driverConfig.driver },
826
+ "Created lock factory for driver"
827
+ );
828
+ }
829
+ } else {
830
+ this.lockFactories.set(
831
+ connection,
832
+ driverConfig.lockProvider
833
+ );
834
+ this.logger.trace(
835
+ { connection, driver: driverConfig.driver },
836
+ "Using explicitly configured lock provider for driver"
837
+ );
838
+ }
839
+ }
840
+ for (const job of this.config.jobs) {
841
+ yield this.register(job);
842
+ }
843
+ this.started = true;
844
+ for (const [connection, driver] of this.drivers) {
845
+ this.logger.trace({ connection }, "Starting queue connection");
846
+ yield driver.start();
847
+ }
848
+ this.logger.trace(
849
+ { connections: Array.from(this.drivers.keys()) },
850
+ "Queue service started"
851
+ );
852
+ });
853
+ }
854
+ stop() {
855
+ return __async(this, arguments, function* (options = {
856
+ graceful: true,
857
+ timeout: 3e4
858
+ }) {
859
+ if (!this.started) {
860
+ this.logger.warn("Queue service not started");
861
+ return;
862
+ }
863
+ this.logger.trace(
864
+ { count: this.scheduledJobs.size },
865
+ "Cleared scheduled jobs for the queue"
866
+ );
867
+ for (const id of this.scheduledJobs) {
868
+ Schedule.clear(id);
869
+ }
870
+ this.scheduledJobs.clear();
871
+ for (const [connection, driver] of this.drivers) {
872
+ this.logger.trace({ connection }, "Stopping queue connection");
873
+ yield driver.stop(options);
874
+ }
875
+ for (const job of this.config.jobs) {
876
+ yield this.unregister(job);
877
+ }
878
+ for (const [connection, lockFactory] of this.lockFactories) {
879
+ const driver = this.drivers.get(connection);
880
+ if (!driver) {
881
+ throw new Error(`Driver not found for connection: ${connection}.`);
882
+ }
883
+ yield driver.destroyLockProvider(lockFactory);
884
+ this.lockFactories.delete(connection);
885
+ }
886
+ this.started = false;
887
+ this.logger.trace("Queue service stopped");
888
+ });
889
+ }
890
+ register(job) {
891
+ return __async(this, null, function* () {
892
+ for (const [_, driver] of this.drivers) {
893
+ yield driver.register(job);
894
+ }
895
+ });
896
+ }
897
+ unregister(job) {
898
+ return __async(this, null, function* () {
899
+ for (const [_, driver] of this.drivers) {
900
+ yield driver.unregister(job);
901
+ }
902
+ });
903
+ }
904
+ enqueue(job, payload) {
905
+ return __async(this, null, function* () {
906
+ if (this.drivers.size === 0) {
907
+ throw new Error("No queue drivers available.");
908
+ }
909
+ const driver = job.options.connection ? this.drivers.get(job.options.connection) : this.drivers.values().next().value;
910
+ if (!driver) {
911
+ throw new Error(
912
+ `No driver found for connection: ${job.options.connection}.`
913
+ );
914
+ }
915
+ if (!job.options.queue) {
916
+ job.options.queue = driver.getDefaultQueue();
917
+ }
918
+ yield driver.enqueue(job, payload);
919
+ });
920
+ }
921
+ /**
922
+ * Get the default connection name.
923
+ *
924
+ * Returns the name of the first available connection.
925
+ * Throws an error if no connections are available.
926
+ */
927
+ getDefaultConnection() {
928
+ if (this.drivers.size === 0) {
929
+ throw new Error(`No queue drivers available.`);
930
+ }
931
+ return this.drivers.keys().next().value;
932
+ }
933
+ getDefaultQueue() {
934
+ var _a;
935
+ if (this.drivers.size === 0) {
936
+ throw new Error(`No queue drivers available.`);
937
+ }
938
+ return (_a = this.drivers.values().next().value) == null ? void 0 : _a.getDefaultQueue();
939
+ }
940
+ /**
941
+ * Get the lock factory instance for a specific connection.
942
+ * Returns the explicitly configured lock provider or the auto-created lock factory.
943
+ * Throws an error if no lock factory is available.
944
+ *
945
+ * @param connection - The connection name
946
+ * @returns The lock factory instance
947
+ */
948
+ getLockProvider(connection) {
949
+ const connectionConfig = this.config.connections[connection];
950
+ if (connectionConfig == null ? void 0 : connectionConfig.lockProvider) {
951
+ return connectionConfig.lockProvider;
952
+ }
953
+ const lockFactory = this.lockFactories.get(connection);
954
+ if (!lockFactory) {
955
+ throw new Error(`No lock provider found for connection: ${connection}.`);
956
+ }
957
+ return lockFactory;
958
+ }
959
+ /**
960
+ * Register a scheduled job ID with this Queue instance.
961
+ * This allows the Queue to clean up scheduled jobs when it stops.
962
+ */
963
+ registerScheduledJob(id) {
964
+ this.scheduledJobs.add(id);
965
+ }
966
+ };
967
+ export {
968
+ Job,
969
+ Logger,
970
+ PendingDispatch,
971
+ PendingJobSchedule,
972
+ PendingSchedule,
973
+ Queue,
974
+ QueueDriver,
975
+ Schedule,
976
+ ScheduleRegistry,
977
+ createDefaultLogger,
978
+ defaultScheduleLockProvider,
979
+ defineConfig,
980
+ getDistributedLockKey,
981
+ intervalToCron,
982
+ parseTime
983
+ };
984
+ //# sourceMappingURL=index.js.map