@onebun/core 0.2.11 → 0.2.13

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.
@@ -239,6 +239,286 @@ describe('QueueScheduler', () => {
239
239
  });
240
240
  });
241
241
 
242
+ describe('addJob', () => {
243
+ it('should add a cron job visible via getJob', () => {
244
+ scheduler.addJob({
245
+ type: 'cron',
246
+ name: 'my-cron',
247
+ expression: '0 0 * * * *',
248
+ pattern: 'test.cron',
249
+ });
250
+
251
+ const job = scheduler.getJob('my-cron');
252
+ expect(job).toBeDefined();
253
+ expect(job!.type).toBe('cron');
254
+ expect(job!.paused).toBe(false);
255
+ expect(job!.declarative).toBe(false);
256
+ expect(job!.schedule.cron).toBe('0 0 * * * *');
257
+ });
258
+
259
+ it('should add an interval job visible via getJob', () => {
260
+ scheduler.addJob({
261
+ type: 'interval',
262
+ name: 'my-interval',
263
+ intervalMs: 5000,
264
+ pattern: 'test.interval',
265
+ });
266
+
267
+ const job = scheduler.getJob('my-interval');
268
+ expect(job).toBeDefined();
269
+ expect(job!.type).toBe('interval');
270
+ expect(job!.paused).toBe(false);
271
+ expect(job!.declarative).toBe(false);
272
+ expect(job!.schedule.every).toBe(5000);
273
+ });
274
+
275
+ it('should add a timeout job visible via getJob', () => {
276
+ scheduler.addJob({
277
+ type: 'timeout',
278
+ name: 'my-timeout',
279
+ timeoutMs: 3000,
280
+ pattern: 'test.timeout',
281
+ });
282
+
283
+ const job = scheduler.getJob('my-timeout');
284
+ expect(job).toBeDefined();
285
+ expect(job!.type).toBe('timeout');
286
+ expect(job!.paused).toBe(false);
287
+ expect(job!.declarative).toBe(false);
288
+ expect(job!.schedule.timeout).toBe(3000);
289
+ });
290
+ });
291
+
292
+ describe('getJobs returns type, paused, and timeout', () => {
293
+ it('should return correct type, paused, and schedule for all job types', () => {
294
+ scheduler.addJob({
295
+ type: 'cron', name: 'c1', expression: '* * * * *', pattern: 'p',
296
+ });
297
+ scheduler.addJob({
298
+ type: 'interval', name: 'i1', intervalMs: 1000, pattern: 'p',
299
+ });
300
+ scheduler.addJob({
301
+ type: 'timeout', name: 't1', timeoutMs: 2000, pattern: 'p',
302
+ });
303
+
304
+ const jobs = scheduler.getJobs();
305
+ expect(jobs.length).toBe(3);
306
+
307
+ const cronJob = jobs.find((j) => j.name === 'c1');
308
+ expect(cronJob!.type).toBe('cron');
309
+ expect(cronJob!.paused).toBe(false);
310
+
311
+ const intervalJob = jobs.find((j) => j.name === 'i1');
312
+ expect(intervalJob!.type).toBe('interval');
313
+ expect(intervalJob!.paused).toBe(false);
314
+ expect(intervalJob!.schedule.every).toBe(1000);
315
+
316
+ const timeoutJob = jobs.find((j) => j.name === 't1');
317
+ expect(timeoutJob!.type).toBe('timeout');
318
+ expect(timeoutJob!.paused).toBe(false);
319
+ expect(timeoutJob!.schedule.timeout).toBe(2000);
320
+ });
321
+ });
322
+
323
+ describe('declarative field', () => {
324
+ it('should mark jobs added via addCronJob with declarative option as declarative', () => {
325
+ scheduler.addCronJob('dec-cron', '* * * * *', 'p', undefined, { declarative: true });
326
+
327
+ const job = scheduler.getJob('dec-cron');
328
+ expect(job).toBeDefined();
329
+ expect(job!.declarative).toBe(true);
330
+ });
331
+
332
+ it('should mark jobs added via addIntervalJob with declarative option as declarative', () => {
333
+ scheduler.addIntervalJob('dec-interval', 1000, 'p', undefined, { declarative: true });
334
+
335
+ const job = scheduler.getJob('dec-interval');
336
+ expect(job).toBeDefined();
337
+ expect(job!.declarative).toBe(true);
338
+ });
339
+
340
+ it('should mark jobs added via addTimeoutJob with declarative option as declarative', () => {
341
+ scheduler.addTimeoutJob('dec-timeout', 1000, 'p', undefined, { declarative: true });
342
+
343
+ const job = scheduler.getJob('dec-timeout');
344
+ expect(job).toBeDefined();
345
+ expect(job!.declarative).toBe(true);
346
+ });
347
+
348
+ it('should mark jobs added via addJob as not declarative', () => {
349
+ scheduler.addJob({
350
+ type: 'cron', name: 'dyn-cron', expression: '* * * * *', pattern: 'p',
351
+ });
352
+
353
+ const job = scheduler.getJob('dyn-cron');
354
+ expect(job).toBeDefined();
355
+ expect(job!.declarative).toBe(false);
356
+ });
357
+ });
358
+
359
+ describe('pauseJob', () => {
360
+ it('should pause an existing job and return true', () => {
361
+ scheduler.addJob({
362
+ type: 'interval', name: 'j1', intervalMs: 1000, pattern: 'p',
363
+ });
364
+
365
+ const result = scheduler.pauseJob('j1');
366
+ expect(result).toBe(true);
367
+
368
+ const job = scheduler.getJob('j1');
369
+ expect(job!.paused).toBe(true);
370
+ });
371
+
372
+ it('should return false for nonexistent job', () => {
373
+ const result = scheduler.pauseJob('nonexistent');
374
+ expect(result).toBe(false);
375
+ });
376
+ });
377
+
378
+ describe('resumeJob', () => {
379
+ it('should resume a paused job and return true', () => {
380
+ scheduler.addJob({
381
+ type: 'interval', name: 'j1', intervalMs: 1000, pattern: 'p',
382
+ });
383
+ scheduler.pauseJob('j1');
384
+
385
+ const result = scheduler.resumeJob('j1');
386
+ expect(result).toBe(true);
387
+
388
+ const job = scheduler.getJob('j1');
389
+ expect(job!.paused).toBe(false);
390
+ });
391
+
392
+ it('should return false for nonexistent job', () => {
393
+ const result = scheduler.resumeJob('nonexistent');
394
+ expect(result).toBe(false);
395
+ });
396
+
397
+ it('should return false for non-paused job', () => {
398
+ scheduler.addJob({
399
+ type: 'interval', name: 'j1', intervalMs: 1000, pattern: 'p',
400
+ });
401
+
402
+ const result = scheduler.resumeJob('j1');
403
+ expect(result).toBe(false);
404
+ });
405
+ });
406
+
407
+ describe('updateJob', () => {
408
+ it('should update cron expression', () => {
409
+ scheduler.addJob({
410
+ type: 'cron',
411
+ name: 'c1',
412
+ expression: '0 0 * * * *',
413
+ pattern: 'p',
414
+ });
415
+
416
+ const result = scheduler.updateJob({ type: 'cron', name: 'c1', expression: '*/5 * * * *' });
417
+ expect(result).toBe(true);
418
+
419
+ const job = scheduler.getJob('c1');
420
+ expect(job!.schedule.cron).toBe('*/5 * * * *');
421
+ });
422
+
423
+ it('should update interval', () => {
424
+ scheduler.addJob({
425
+ type: 'interval', name: 'i1', intervalMs: 1000, pattern: 'p',
426
+ });
427
+
428
+ const result = scheduler.updateJob({ type: 'interval', name: 'i1', intervalMs: 5000 });
429
+ expect(result).toBe(true);
430
+
431
+ const job = scheduler.getJob('i1');
432
+ expect(job!.schedule.every).toBe(5000);
433
+ });
434
+
435
+ it('should update timeout', () => {
436
+ scheduler.addJob({
437
+ type: 'timeout', name: 't1', timeoutMs: 1000, pattern: 'p',
438
+ });
439
+
440
+ const result = scheduler.updateJob({ type: 'timeout', name: 't1', timeoutMs: 9000 });
441
+ expect(result).toBe(true);
442
+
443
+ const job = scheduler.getJob('t1');
444
+ expect(job!.schedule.timeout).toBe(9000);
445
+ });
446
+
447
+ it('should return false when type does not match', () => {
448
+ scheduler.addJob({
449
+ type: 'interval', name: 'i1', intervalMs: 1000, pattern: 'p',
450
+ });
451
+
452
+ const result = scheduler.updateJob({ type: 'cron', name: 'i1', expression: '* * * * *' });
453
+ expect(result).toBe(false);
454
+ });
455
+
456
+ it('should return false for nonexistent job', () => {
457
+ const result = scheduler.updateJob({ type: 'cron', name: 'nope', expression: '* * * * *' });
458
+ expect(result).toBe(false);
459
+ });
460
+ });
461
+
462
+ describe('paused jobs do not fire', () => {
463
+ it('should not fire paused cron job', async () => {
464
+ const received: Message[] = [];
465
+ await adapter.subscribe('test.cron', async (message) => {
466
+ received.push(message);
467
+ });
468
+
469
+ // Every second cron
470
+ scheduler.addJob({
471
+ type: 'cron',
472
+ name: 'cron-paused',
473
+ expression: '* * * * * *',
474
+ pattern: 'test.cron',
475
+ });
476
+ scheduler.pauseJob('cron-paused');
477
+ scheduler.start();
478
+
479
+ // Advance time well past when cron would fire
480
+ advanceTime(5000);
481
+ await Promise.resolve();
482
+
483
+ expect(received.length).toBe(0);
484
+ });
485
+
486
+ it('should not fire paused interval job, and resume restarts it', async () => {
487
+ const received: Message[] = [];
488
+ await adapter.subscribe('test.interval', async (message) => {
489
+ received.push(message);
490
+ });
491
+
492
+ scheduler.addJob({
493
+ type: 'interval',
494
+ name: 'int-paused',
495
+ intervalMs: 100,
496
+ pattern: 'test.interval',
497
+ });
498
+ scheduler.start();
499
+
500
+ // Should fire immediately on start
501
+ advanceTime(10);
502
+ await Promise.resolve();
503
+ expect(received.length).toBe(1);
504
+
505
+ // Pause the job
506
+ scheduler.pauseJob('int-paused');
507
+
508
+ // Advance time — no new messages
509
+ advanceTime(500);
510
+ await Promise.resolve();
511
+ expect(received.length).toBe(1);
512
+
513
+ // Resume — should restart interval and fire immediately
514
+ scheduler.resumeJob('int-paused');
515
+
516
+ advanceTime(10);
517
+ await Promise.resolve();
518
+ expect(received.length).toBe(2);
519
+ });
520
+ });
521
+
242
522
  describe('createQueueScheduler', () => {
243
523
  it('should create scheduler instance', () => {
244
524
  const created = createQueueScheduler(adapter);
@@ -6,6 +6,8 @@
6
6
  */
7
7
 
8
8
  import type {
9
+ AddJobOptions,
10
+ UpdateJobOptions,
9
11
  QueueAdapter,
10
12
  ScheduledJobInfo,
11
13
  OverlapStrategy,
@@ -42,6 +44,12 @@ interface ScheduledJob {
42
44
  // Timeout-specific
43
45
  timeoutMs?: number;
44
46
 
47
+ // Pause state
48
+ paused?: boolean;
49
+
50
+ // Whether created via decorator
51
+ declarative?: boolean;
52
+
45
53
  // Runtime state
46
54
  timer?: ReturnType<typeof setTimeout> | ReturnType<typeof setInterval>;
47
55
  isRunning?: boolean;
@@ -64,9 +72,17 @@ export class QueueScheduler {
64
72
  private running = false;
65
73
  private cronCheckInterval?: ReturnType<typeof setInterval>;
66
74
  private readonly cronCheckIntervalMs = 1000; // Check cron jobs every second
75
+ private onJobError?: (jobName: string, error: unknown) => void;
67
76
 
68
77
  constructor(private readonly adapter: QueueAdapter) {}
69
78
 
79
+ /**
80
+ * Set error handler for scheduled job failures
81
+ */
82
+ setErrorHandler(handler: (jobName: string, error: unknown) => void): void {
83
+ this.onJobError = handler;
84
+ }
85
+
70
86
  /**
71
87
  * Start the scheduler
72
88
  */
@@ -82,8 +98,12 @@ export class QueueScheduler {
82
98
  this.checkCronJobs();
83
99
  }, this.cronCheckIntervalMs);
84
100
 
85
- // Start all interval and timeout jobs
101
+ // Start all interval and timeout jobs (skip paused)
86
102
  for (const job of this.jobs.values()) {
103
+ if (job.paused) {
104
+ continue;
105
+ }
106
+
87
107
  if (job.type === 'interval' && job.intervalMs) {
88
108
  this.startIntervalJob(job);
89
109
  } else if (job.type === 'timeout' && job.timeoutMs) {
@@ -129,6 +149,7 @@ export class QueueScheduler {
129
149
  options?: {
130
150
  metadata?: Partial<MessageMetadata>;
131
151
  overlapStrategy?: OverlapStrategy;
152
+ declarative?: boolean;
132
153
  },
133
154
  ): void {
134
155
  const schedule = parseCronExpression(expression);
@@ -144,6 +165,7 @@ export class QueueScheduler {
144
165
  getDataFn,
145
166
  metadata: options?.metadata,
146
167
  overlapStrategy: options?.overlapStrategy ?? 'skip',
168
+ declarative: options?.declarative,
147
169
  };
148
170
 
149
171
  this.jobs.set(name, job);
@@ -159,6 +181,7 @@ export class QueueScheduler {
159
181
  getDataFn?: () => unknown | Promise<unknown>,
160
182
  options?: {
161
183
  metadata?: Partial<MessageMetadata>;
184
+ declarative?: boolean;
162
185
  },
163
186
  ): void {
164
187
  const job: ScheduledJob = {
@@ -168,6 +191,7 @@ export class QueueScheduler {
168
191
  intervalMs,
169
192
  getDataFn,
170
193
  metadata: options?.metadata,
194
+ declarative: options?.declarative,
171
195
  };
172
196
 
173
197
  this.jobs.set(name, job);
@@ -188,6 +212,7 @@ export class QueueScheduler {
188
212
  getDataFn?: () => unknown | Promise<unknown>,
189
213
  options?: {
190
214
  metadata?: Partial<MessageMetadata>;
215
+ declarative?: boolean;
191
216
  },
192
217
  ): void {
193
218
  const job: ScheduledJob = {
@@ -197,6 +222,7 @@ export class QueueScheduler {
197
222
  timeoutMs,
198
223
  getDataFn,
199
224
  metadata: options?.metadata,
225
+ declarative: options?.declarative,
200
226
  };
201
227
 
202
228
  this.jobs.set(name, job);
@@ -207,6 +233,118 @@ export class QueueScheduler {
207
233
  }
208
234
  }
209
235
 
236
+ /**
237
+ * Add a job using the unified API
238
+ */
239
+ addJob(options: AddJobOptions): void {
240
+ switch (options.type) {
241
+ case 'cron':
242
+ this.addCronJob(options.name, options.expression, options.pattern, options.getDataFn, {
243
+ metadata: options.metadata,
244
+ overlapStrategy: options.overlapStrategy,
245
+ });
246
+ break;
247
+ case 'interval':
248
+ this.addIntervalJob(options.name, options.intervalMs, options.pattern, options.getDataFn, {
249
+ metadata: options.metadata,
250
+ });
251
+ break;
252
+ case 'timeout':
253
+ this.addTimeoutJob(options.name, options.timeoutMs, options.pattern, options.getDataFn, {
254
+ metadata: options.metadata,
255
+ });
256
+ break;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Pause a scheduled job
262
+ */
263
+ pauseJob(name: string): boolean {
264
+ const job = this.jobs.get(name);
265
+ if (!job) {
266
+ return false;
267
+ }
268
+
269
+ job.paused = true;
270
+
271
+ if (job.timer) {
272
+ clearTimeout(job.timer);
273
+ clearInterval(job.timer);
274
+ job.timer = undefined;
275
+ }
276
+
277
+ return true;
278
+ }
279
+
280
+ /**
281
+ * Resume a paused job
282
+ */
283
+ resumeJob(name: string): boolean {
284
+ const job = this.jobs.get(name);
285
+ if (!job || !job.paused) {
286
+ return false;
287
+ }
288
+
289
+ job.paused = false;
290
+
291
+ if (this.running) {
292
+ if (job.type === 'interval' && job.intervalMs) {
293
+ this.startIntervalJob(job);
294
+ } else if (job.type === 'timeout' && job.timeoutMs) {
295
+ this.startTimeoutJob(job);
296
+ } else if (job.type === 'cron' && job.cronSchedule) {
297
+ job.nextRun = getNextRun(job.cronSchedule) ?? undefined;
298
+ }
299
+ }
300
+
301
+ return true;
302
+ }
303
+
304
+ /**
305
+ * Update a scheduled job's timing configuration
306
+ */
307
+ updateJob(options: UpdateJobOptions): boolean {
308
+ const job = this.jobs.get(options.name);
309
+ if (!job || job.type !== options.type) {
310
+ return false;
311
+ }
312
+
313
+ switch (options.type) {
314
+ case 'cron': {
315
+ const schedule = parseCronExpression(options.expression);
316
+ job.cronExpression = options.expression;
317
+ job.cronSchedule = schedule;
318
+ job.nextRun = getNextRun(schedule) ?? undefined;
319
+ break;
320
+ }
321
+ case 'interval': {
322
+ if (job.timer) {
323
+ clearInterval(job.timer);
324
+ job.timer = undefined;
325
+ }
326
+ job.intervalMs = options.intervalMs;
327
+ if (this.running && !job.paused) {
328
+ this.startIntervalJob(job);
329
+ }
330
+ break;
331
+ }
332
+ case 'timeout': {
333
+ if (job.timer) {
334
+ clearTimeout(job.timer);
335
+ job.timer = undefined;
336
+ }
337
+ job.timeoutMs = options.timeoutMs;
338
+ if (this.running && !job.paused) {
339
+ this.startTimeoutJob(job);
340
+ }
341
+ break;
342
+ }
343
+ }
344
+
345
+ return true;
346
+ }
347
+
210
348
  /**
211
349
  * Remove a job
212
350
  */
@@ -236,10 +374,14 @@ export class QueueScheduler {
236
374
  for (const job of this.jobs.values()) {
237
375
  result.push({
238
376
  name: job.name,
377
+ type: job.type,
378
+ paused: job.paused ?? false,
379
+ declarative: job.declarative ?? false,
239
380
  pattern: job.pattern,
240
381
  schedule: {
241
382
  cron: job.cronExpression,
242
383
  every: job.intervalMs,
384
+ timeout: job.timeoutMs,
243
385
  },
244
386
  nextRun: job.nextRun,
245
387
  lastRun: job.lastRun,
@@ -261,10 +403,14 @@ export class QueueScheduler {
261
403
 
262
404
  return {
263
405
  name: job.name,
406
+ type: job.type,
407
+ paused: job.paused ?? false,
408
+ declarative: job.declarative ?? false,
264
409
  pattern: job.pattern,
265
410
  schedule: {
266
411
  cron: job.cronExpression,
267
412
  every: job.intervalMs,
413
+ timeout: job.timeoutMs,
268
414
  },
269
415
  nextRun: job.nextRun,
270
416
  lastRun: job.lastRun,
@@ -294,6 +440,10 @@ export class QueueScheduler {
294
440
  continue;
295
441
  }
296
442
 
443
+ if (job.paused) {
444
+ continue;
445
+ }
446
+
297
447
  // Check if it's time to run
298
448
  if (job.nextRun && now >= job.nextRun) {
299
449
  // Handle overlap strategy
@@ -363,8 +513,11 @@ export class QueueScheduler {
363
513
  await this.adapter.publish(job.pattern, data, {
364
514
  metadata: job.metadata,
365
515
  });
366
- } catch {
367
- // Error executing job - silently continue (error handling should be done via events)
516
+ } catch (error) {
517
+ // Report error via handler if set, otherwise silently continue
518
+ if (this.onJobError) {
519
+ this.onJobError(job.name, error);
520
+ }
368
521
  } finally {
369
522
  job.isRunning = false;
370
523
  }
@@ -224,30 +224,79 @@ export interface Subscription {
224
224
  export type OverlapStrategy = 'skip' | 'queue';
225
225
 
226
226
  /**
227
- * Options for scheduled jobs
227
+ * Add a cron job
228
228
  */
229
- export interface ScheduledJobOptions {
230
- /** Pattern to publish to */
229
+ export interface AddCronJob {
230
+ type: 'cron';
231
+ name: string;
232
+ expression: string;
231
233
  pattern: string;
234
+ getDataFn?: () => unknown | Promise<unknown>;
235
+ metadata?: Partial<MessageMetadata>;
236
+ overlapStrategy?: OverlapStrategy;
237
+ }
232
238
 
233
- /** Data to include in the message */
234
- data?: unknown;
235
-
236
- /** Schedule configuration */
237
- schedule: {
238
- /** Cron expression */
239
- cron?: string;
240
- /** Interval in milliseconds */
241
- every?: number;
242
- };
239
+ /**
240
+ * Add an interval job
241
+ */
242
+ export interface AddIntervalJob {
243
+ type: 'interval';
244
+ name: string;
245
+ intervalMs: number;
246
+ pattern: string;
247
+ getDataFn?: () => unknown | Promise<unknown>;
248
+ metadata?: Partial<MessageMetadata>;
249
+ }
243
250
 
244
- /** Metadata to include in messages */
251
+ /**
252
+ * Add a timeout job
253
+ */
254
+ export interface AddTimeoutJob {
255
+ type: 'timeout';
256
+ name: string;
257
+ timeoutMs: number;
258
+ pattern: string;
259
+ getDataFn?: () => unknown | Promise<unknown>;
245
260
  metadata?: Partial<MessageMetadata>;
261
+ }
246
262
 
247
- /** What to do if previous job is still running */
248
- overlapStrategy?: OverlapStrategy;
263
+ /**
264
+ * Discriminated union for adding any scheduled job type
265
+ */
266
+ export type AddJobOptions = AddCronJob | AddIntervalJob | AddTimeoutJob;
267
+
268
+ /**
269
+ * Update a cron job's expression
270
+ */
271
+ export interface UpdateCronJob {
272
+ type: 'cron';
273
+ name: string;
274
+ expression: string;
249
275
  }
250
276
 
277
+ /**
278
+ * Update an interval job's interval
279
+ */
280
+ export interface UpdateIntervalJob {
281
+ type: 'interval';
282
+ name: string;
283
+ intervalMs: number;
284
+ }
285
+
286
+ /**
287
+ * Update a timeout job's delay
288
+ */
289
+ export interface UpdateTimeoutJob {
290
+ type: 'timeout';
291
+ name: string;
292
+ timeoutMs: number;
293
+ }
294
+
295
+ /**
296
+ * Discriminated union for updating any scheduled job type
297
+ */
298
+ export type UpdateJobOptions = UpdateCronJob | UpdateIntervalJob | UpdateTimeoutJob;
299
+
251
300
  /**
252
301
  * Information about a scheduled job
253
302
  */
@@ -255,13 +304,23 @@ export interface ScheduledJobInfo {
255
304
  /** Job name */
256
305
  name: string;
257
306
 
307
+ /** Job type */
308
+ type: 'cron' | 'interval' | 'timeout';
309
+
258
310
  /** Pattern to publish to */
259
311
  pattern: string;
260
312
 
313
+ /** Whether the job is paused */
314
+ paused: boolean;
315
+
316
+ /** Whether the job was created via decorator (@Cron, @Interval, @Timeout) */
317
+ declarative: boolean;
318
+
261
319
  /** Schedule configuration */
262
320
  schedule: {
263
321
  cron?: string;
264
322
  every?: number;
323
+ timeout?: number;
265
324
  };
266
325
 
267
326
  /** Next scheduled run time */
@@ -286,7 +345,6 @@ export type QueueFeature =
286
345
  | 'priority'
287
346
  | 'dead-letter-queue'
288
347
  | 'retry'
289
- | 'scheduled-jobs'
290
348
  | 'consumer-groups'
291
349
  | 'pattern-subscriptions';
292
350
 
@@ -353,16 +411,6 @@ export interface QueueAdapter {
353
411
  options?: SubscribeOptions
354
412
  ): Promise<Subscription>;
355
413
 
356
- // Scheduled Jobs
357
- /** Add a scheduled job */
358
- addScheduledJob(name: string, options: ScheduledJobOptions): Promise<void>;
359
-
360
- /** Remove a scheduled job */
361
- removeScheduledJob(name: string): Promise<boolean>;
362
-
363
- /** Get all scheduled jobs */
364
- getScheduledJobs(): Promise<ScheduledJobInfo[]>;
365
-
366
414
  // Feature Support
367
415
  /** Check if a feature is supported by this adapter */
368
416
  supports(feature: QueueFeature): boolean;
@@ -232,7 +232,7 @@ export const fakeTimers = new FakeTimers();
232
232
  *
233
233
  * @example
234
234
  * ```typescript
235
- * import { useFakeTimers } from '@onebun/core';
235
+ * import { useFakeTimers } from '@onebun/core/testing';
236
236
  *
237
237
  * describe('My tests', () => {
238
238
  * const { advanceTime, restore } = useFakeTimers();