@onebun/core 0.2.12 → 0.2.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onebun/core",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Core package for OneBun framework - decorators, DI, modules, controllers",
5
5
  "license": "LGPL-3.0",
6
6
  "author": "RemRyahirev",
@@ -46,7 +46,7 @@
46
46
  "effect": "^3.13.10",
47
47
  "arktype": "^2.0.0",
48
48
  "@onebun/logger": "^0.2.1",
49
- "@onebun/envs": "^0.2.1",
49
+ "@onebun/envs": "^0.2.2",
50
50
  "@onebun/metrics": "^0.2.2",
51
51
  "@onebun/requests": "^0.2.1",
52
52
  "@onebun/trace": "^0.2.1"
@@ -3964,13 +3964,6 @@ describe('OneBunApplication', () => {
3964
3964
  };
3965
3965
  }
3966
3966
 
3967
- async addScheduledJob(): Promise<void> {}
3968
- async removeScheduledJob(): Promise<boolean> {
3969
- return false;
3970
- }
3971
- async getScheduledJobs(): Promise<import('../queue/types').ScheduledJobInfo[]> {
3972
- return [];
3973
- }
3974
3967
  supports(): boolean {
3975
3968
  return false;
3976
3969
  }
@@ -246,7 +246,8 @@ function resolvePathUnderRoot(rootDir: string, relativePath: string): string | n
246
246
  /**
247
247
  * OneBun Application
248
248
  */
249
- export class OneBunApplication {
249
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
250
+ export class OneBunApplication<QA extends import('../queue/types').QueueAdapterConstructor<any> = import('../queue/types').QueueAdapterConstructor> {
250
251
  private rootModule: ModuleInstance | null = null;
251
252
  private server: ReturnType<typeof Bun.serve> | null = null;
252
253
  private options: ApplicationOptions;
@@ -272,7 +273,7 @@ export class OneBunApplication {
272
273
  */
273
274
  constructor(
274
275
  moduleClass: new (...args: unknown[]) => object,
275
- options?: Partial<ApplicationOptions>,
276
+ options?: Partial<ApplicationOptions<QA>>,
276
277
  ) {
277
278
  this.moduleClass = moduleClass;
278
279
 
@@ -14,6 +14,7 @@ import {
14
14
 
15
15
  import {
16
16
  defineMetadata,
17
+ diagnoseDecoratorMetadata,
17
18
  getMetadata,
18
19
  getConstructorParamTypes,
19
20
  setConstructorParamTypes,
@@ -731,6 +732,91 @@ describe('Metadata System', () => {
731
732
  });
732
733
  });
733
734
 
735
+ describe('diagnoseDecoratorMetadata', () => {
736
+ test('should return ok when no classes have constructor params', () => {
737
+ class NoParams {}
738
+
739
+ const result = diagnoseDecoratorMetadata([NoParams]);
740
+
741
+ expect(result.ok).toBe(true);
742
+ expect(result.classesWithParams).toBe(0);
743
+ expect(result.classesWithMetadata).toBe(0);
744
+ });
745
+
746
+ test('should return ok when classes with params have metadata', () => {
747
+ class DepA {}
748
+ class WithMeta {
749
+ constructor(_dep: DepA) {}
750
+ }
751
+
752
+ // Simulate Bun emitting design:paramtypes via Reflect polyfill
753
+ (globalThis as any).Reflect.defineMetadata('design:paramtypes', [DepA], WithMeta);
754
+
755
+ const result = diagnoseDecoratorMetadata([WithMeta]);
756
+
757
+ expect(result.ok).toBe(true);
758
+ expect(result.classesWithParams).toBe(1);
759
+ expect(result.classesWithMetadata).toBe(1);
760
+ });
761
+
762
+ test('should return not ok when classes with params have NO metadata', () => {
763
+ // Use a fresh class without any metadata set
764
+ class NoDep {}
765
+ class BrokenService {
766
+ constructor(_dep: NoDep) {}
767
+ }
768
+
769
+ const result = diagnoseDecoratorMetadata([BrokenService]);
770
+
771
+ expect(result.ok).toBe(false);
772
+ expect(result.classesWithParams).toBe(1);
773
+ expect(result.classesWithMetadata).toBe(0);
774
+ });
775
+
776
+ test('should return ok when at least one class has metadata', () => {
777
+ class DepA {}
778
+ class DepB {}
779
+
780
+ class ServiceA {
781
+ constructor(_dep: DepA) {}
782
+ }
783
+ class ServiceB {
784
+ constructor(_dep: DepB) {}
785
+ }
786
+
787
+ // Only one has metadata — still ok (metadata emission works in principle)
788
+ (globalThis as any).Reflect.defineMetadata('design:paramtypes', [DepA], ServiceA);
789
+
790
+ const result = diagnoseDecoratorMetadata([ServiceA, ServiceB]);
791
+
792
+ expect(result.ok).toBe(true);
793
+ expect(result.classesWithParams).toBe(2);
794
+ expect(result.classesWithMetadata).toBe(1);
795
+ });
796
+
797
+ test('should return ok for empty class list', () => {
798
+ const result = diagnoseDecoratorMetadata([]);
799
+
800
+ expect(result.ok).toBe(true);
801
+ expect(result.classesWithParams).toBe(0);
802
+ });
803
+
804
+ test('should handle mixed classes with and without params', () => {
805
+ class Dep {}
806
+ class NoParamsService {}
807
+ class WithParamsService {
808
+ constructor(_dep: Dep) {}
809
+ }
810
+
811
+ // No metadata for WithParamsService
812
+ const result = diagnoseDecoratorMetadata([NoParamsService, WithParamsService]);
813
+
814
+ expect(result.ok).toBe(false);
815
+ expect(result.classesWithParams).toBe(1);
816
+ expect(result.classesWithMetadata).toBe(0);
817
+ });
818
+ });
819
+
734
820
  describe('Metadata storage edge cases', () => {
735
821
  test('should handle metadata on object values', () => {
736
822
  const objectValue = { value: 42 };
@@ -286,6 +286,205 @@ export function getConstructorParamTypes(target: Function): Function[] | undefin
286
286
  return undefined;
287
287
  }
288
288
 
289
+ /**
290
+ * Diagnostic: check whether Bun is emitting decorator metadata (design:paramtypes).
291
+ *
292
+ * Scans an array of decorated classes. If at least one class has constructor
293
+ * parameters (target.length > 0) but NONE of them have design:paramtypes,
294
+ * then emitDecoratorMetadata is not working — typically because the setting
295
+ * is missing from the root tsconfig.json that Bun actually reads.
296
+ *
297
+ * @param decoratedClasses - Classes registered via @Service / @Controller / @Middleware
298
+ * @returns Object with diagnostic result and details
299
+ */
300
+ export function diagnoseDecoratorMetadata(decoratedClasses: Function[]): {
301
+ ok: boolean;
302
+ classesWithParams: number;
303
+ classesWithMetadata: number;
304
+ } {
305
+ let classesWithParams = 0;
306
+ let classesWithMetadata = 0;
307
+
308
+ for (const cls of decoratedClasses) {
309
+ if (cls.length > 0) {
310
+ classesWithParams++;
311
+ const types = (globalThis as any).Reflect?.getMetadata?.('design:paramtypes', cls);
312
+ if (types && Array.isArray(types) && types.length > 0) {
313
+ classesWithMetadata++;
314
+ }
315
+ }
316
+ }
317
+
318
+ return {
319
+ ok: classesWithParams === 0 || classesWithMetadata > 0,
320
+ classesWithParams,
321
+ classesWithMetadata,
322
+ };
323
+ }
324
+
325
+ /**
326
+ * Build a detailed diagnostic message by inspecting the project's tsconfig files.
327
+ *
328
+ * Walks up from `process.cwd()` looking for tsconfig.json files, reads them,
329
+ * and tells the user exactly which file to edit and what to add.
330
+ */
331
+ export function buildDecoratorMetadataDiagnosticMessage(
332
+ classesWithParams: number,
333
+ ): string {
334
+ const fs = require('node:fs');
335
+ const pathModule = require('node:path');
336
+
337
+ const header =
338
+ `[OneBun] Dependency injection is broken: none of the ${classesWithParams} ` +
339
+ 'service(s) with constructor parameters have design:paramtypes metadata.\n' +
340
+ 'Bun is NOT emitting decorator metadata.\n';
341
+
342
+ type TsconfigInfo = { path: string; content: any; hasEmit: boolean; hasExperimental: boolean };
343
+
344
+ const readTsconfig = (tsconfigPath: string): TsconfigInfo | null => {
345
+ try {
346
+ if (!fs.existsSync(tsconfigPath)) {
347
+ return null;
348
+ }
349
+ const raw = fs.readFileSync(tsconfigPath, 'utf-8');
350
+ // Strip comments (single-line // and multi-line /* */) for JSON.parse
351
+ const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
352
+ const parsed = JSON.parse(stripped);
353
+ const compilerOptions = parsed.compilerOptions || {};
354
+
355
+ return {
356
+ path: tsconfigPath,
357
+ content: parsed,
358
+ hasEmit: compilerOptions.emitDecoratorMetadata === true,
359
+ hasExperimental: compilerOptions.experimentalDecorators === true,
360
+ };
361
+ } catch {
362
+ return null;
363
+ }
364
+ };
365
+
366
+ // 1. Find tsconfig.json files walking UP from cwd to filesystem root
367
+ const tsconfigFiles: TsconfigInfo[] = [];
368
+ let dir = process.cwd();
369
+ const root = pathModule.parse(dir).root;
370
+
371
+ while (dir !== root) {
372
+ const info = readTsconfig(pathModule.join(dir, 'tsconfig.json'));
373
+ if (info) {
374
+ tsconfigFiles.push(info);
375
+ }
376
+ dir = pathModule.dirname(dir);
377
+ }
378
+
379
+ // 2. Also scan immediate subdirectories of cwd for child tsconfig.json files
380
+ // (e.g. packages/backend/tsconfig.json in a monorepo)
381
+ const childTsconfigFiles: TsconfigInfo[] = [];
382
+ try {
383
+ const scanDirs = [process.cwd()];
384
+ // Scan up to 2 levels deep to find packages/*/tsconfig.json and similar
385
+ for (const scanDir of scanDirs) {
386
+ const entries = fs.readdirSync(scanDir, { withFileTypes: true });
387
+ for (const entry of entries) {
388
+ if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
389
+ const subdir = pathModule.join(scanDir, entry.name);
390
+ const info = readTsconfig(pathModule.join(subdir, 'tsconfig.json'));
391
+ if (info) {
392
+ childTsconfigFiles.push(info);
393
+ }
394
+ // One level deeper (e.g. packages/backend/)
395
+ try {
396
+ const subEntries = fs.readdirSync(subdir, { withFileTypes: true });
397
+ for (const subEntry of subEntries) {
398
+ if (subEntry.isDirectory() && !subEntry.name.startsWith('.') && subEntry.name !== 'node_modules') {
399
+ const info2 = readTsconfig(pathModule.join(subdir, subEntry.name, 'tsconfig.json'));
400
+ if (info2) {
401
+ childTsconfigFiles.push(info2);
402
+ }
403
+ }
404
+ }
405
+ } catch {
406
+ // Skip unreadable directories
407
+ }
408
+ }
409
+ }
410
+ }
411
+ } catch {
412
+ // Skip if directory scanning fails
413
+ }
414
+
415
+ if (tsconfigFiles.length === 0) {
416
+ return (
417
+ header +
418
+ '\nNo tsconfig.json found. Create one in your project root with:\n\n' +
419
+ ' {\n' +
420
+ ' "compilerOptions": {\n' +
421
+ ' "experimentalDecorators": true,\n' +
422
+ ' "emitDecoratorMetadata": true\n' +
423
+ ' }\n' +
424
+ ' }\n'
425
+ );
426
+ }
427
+
428
+ // The first (deepest / closest to cwd) tsconfig is what the user likely expects to work.
429
+ // The last (shallowest / closest to root) is what Bun actually reads.
430
+ const rootTsconfig = tsconfigFiles[tsconfigFiles.length - 1];
431
+
432
+ // Check if any child tsconfig has the settings but root does not
433
+ // Look in both parent chain and scanned subdirectories
434
+ const allConfigs = [...tsconfigFiles, ...childTsconfigFiles];
435
+ const childWithSettings = allConfigs.find(
436
+ (t) => t.path !== rootTsconfig.path && (t.hasEmit || t.hasExperimental),
437
+ );
438
+
439
+ const lines: string[] = [header];
440
+
441
+ if (rootTsconfig.hasEmit && rootTsconfig.hasExperimental) {
442
+ // Root has both settings — unusual, might be a different issue
443
+ lines.push(`\nRoot tsconfig (${rootTsconfig.path}) already has both settings.`);
444
+ lines.push('If DI is still broken, check that Bun is reading this file for your entry point.');
445
+
446
+ return lines.join('\n');
447
+ }
448
+
449
+ // Report what's missing from the root tsconfig
450
+ const missing: string[] = [];
451
+ if (!rootTsconfig.hasExperimental) {
452
+ missing.push('"experimentalDecorators": true');
453
+ }
454
+ if (!rootTsconfig.hasEmit) {
455
+ missing.push('"emitDecoratorMetadata": true');
456
+ }
457
+
458
+ lines.push(`\nRoot tsconfig: ${rootTsconfig.path}`);
459
+ lines.push(`Missing in "compilerOptions": ${missing.join(', ')}`);
460
+
461
+ if (childWithSettings) {
462
+ const childHas: string[] = [];
463
+ if (childWithSettings.hasExperimental) {
464
+ childHas.push('"experimentalDecorators": true');
465
+ }
466
+ if (childWithSettings.hasEmit) {
467
+ childHas.push('"emitDecoratorMetadata": true');
468
+ }
469
+ lines.push(
470
+ `\nNote: ${childWithSettings.path} has ${childHas.join(' and ')},` +
471
+ ' but Bun ignores these when they are only in a child tsconfig.',
472
+ );
473
+ }
474
+
475
+ lines.push(`\nFix: add the missing option(s) to ${rootTsconfig.path}:\n`);
476
+ lines.push(' "compilerOptions": {');
477
+ if (!rootTsconfig.hasExperimental) {
478
+ lines.push(' "experimentalDecorators": true,');
479
+ }
480
+ if (!rootTsconfig.hasEmit) {
481
+ lines.push(' "emitDecoratorMetadata": true');
482
+ }
483
+ lines.push(' }');
484
+
485
+ return lines.join('\n');
486
+ }
487
+
289
488
  /**
290
489
  * Namespace for metadata functions to mimic Reflect API
291
490
  */
@@ -37,6 +37,7 @@ import type {
37
37
  OnApplicationDestroy,
38
38
  } from './';
39
39
  import type { ExceptionFilter } from './exception-filters/exception-filters';
40
+ import type { QueueAdapter, QueueAdapterConstructor } from './queue/types';
40
41
  import type {
41
42
  SseEvent,
42
43
  SseGenerator,
@@ -44,6 +45,7 @@ import type {
44
45
  OneBunResponse,
45
46
  MiddlewareClass,
46
47
  OnModuleConfigure,
48
+ QueueApplicationOptions,
47
49
  } from './types';
48
50
  import type { HttpExecutionContext } from './types';
49
51
  import type { ServerWebSocket } from 'bun';
@@ -5706,3 +5708,96 @@ describe('docs/api/security.md', () => {
5706
5708
  expect(res.headers.get('Strict-Transport-Security')).toBeNull();
5707
5709
  });
5708
5710
  });
5711
+
5712
+ // ============================================================================
5713
+ // docs/api/queue.md — Type-safe queue adapter configuration
5714
+ // ============================================================================
5715
+ describe('docs/api/queue.md — type-safe adapter options', () => {
5716
+ // Minimal custom adapter for type-checking purposes
5717
+ interface CustomAdapterOptions {
5718
+ servers: string;
5719
+ streams?: Array<{ name: string; subjects: string[] }>;
5720
+ }
5721
+
5722
+ class CustomAdapter implements QueueAdapter {
5723
+ readonly name = 'custom';
5724
+ readonly type = 'jetstream' as const;
5725
+ constructor(private opts: CustomAdapterOptions) {}
5726
+ async connect() { /* noop */ }
5727
+ async disconnect() { /* noop */ }
5728
+ isConnected() {
5729
+ return true;
5730
+ }
5731
+ async publish() {
5732
+ return '';
5733
+ }
5734
+ async publishBatch() {
5735
+ return [];
5736
+ }
5737
+ async subscribe() {
5738
+ return {
5739
+ async unsubscribe() { /* noop */ },
5740
+ pause() { /* noop */ },
5741
+ resume() { /* noop */ },
5742
+ pattern: '',
5743
+ isActive: true,
5744
+ };
5745
+ }
5746
+ supports() {
5747
+ return false;
5748
+ }
5749
+ on() { /* noop */ }
5750
+ off() { /* noop */ }
5751
+ }
5752
+
5753
+ it('QueueApplicationOptions infers options type from adapter constructor', () => {
5754
+ // When adapter is a specific class, options should be typed as its constructor argument
5755
+ const queueOpts: QueueApplicationOptions<typeof CustomAdapter> = {
5756
+ adapter: CustomAdapter,
5757
+ options: {
5758
+ servers: 'nats://localhost:4222',
5759
+ streams: [{ name: 'EVENTS', subjects: ['events.>'] }],
5760
+ },
5761
+ };
5762
+
5763
+ expect(queueOpts.adapter).toBe(CustomAdapter);
5764
+ expect(queueOpts.options?.servers).toBe('nats://localhost:4222');
5765
+ });
5766
+
5767
+ it('QueueApplicationOptions works with string adapter type (backward compatibility)', () => {
5768
+ const queueOpts: QueueApplicationOptions = {
5769
+ adapter: 'memory',
5770
+ enabled: true,
5771
+ };
5772
+
5773
+ expect(queueOpts.adapter).toBe('memory');
5774
+ });
5775
+
5776
+ it('QueueAdapterConstructor is compatible with custom adapter classes', () => {
5777
+ // Verify that a custom adapter class satisfies QueueAdapterConstructor
5778
+ const ctor: QueueAdapterConstructor<CustomAdapterOptions> = CustomAdapter;
5779
+ const instance = new ctor({ servers: 'nats://localhost:4222' });
5780
+
5781
+ expect(instance.name).toBe('custom');
5782
+ expect(instance.type).toBe('jetstream');
5783
+ });
5784
+
5785
+ it('OneBunApplication accepts typed adapter options without type assertion', () => {
5786
+ // This is the main desired usage — no `as SomeOptions` needed
5787
+ @Module({ controllers: [] })
5788
+ class TestModule {}
5789
+
5790
+ const app = new OneBunApplication(TestModule, {
5791
+ loggerLayer: makeMockLoggerLayer(),
5792
+ queue: {
5793
+ adapter: CustomAdapter,
5794
+ options: {
5795
+ servers: 'nats://localhost:4222',
5796
+ streams: [{ name: 'EVENTS', subjects: ['events.>'] }],
5797
+ },
5798
+ },
5799
+ });
5800
+
5801
+ expect(app).toBeDefined();
5802
+ });
5803
+ });
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export {
3
3
  Env,
4
4
  type EnvSchema,
5
5
  EnvValidationError,
6
+ getConfig,
6
7
  type InferConfigType,
7
8
  type EnvVariableConfig,
8
9
  } from '@onebun/envs';
@@ -22,6 +22,7 @@ import {
22
22
  isGlobalModule,
23
23
  registerControllerDependencies,
24
24
  } from '../decorators/decorators';
25
+ import { buildDecoratorMetadataDiagnosticMessage, diagnoseDecoratorMetadata } from '../decorators/metadata';
25
26
  import { QueueService, QueueServiceTag } from '../queue';
26
27
  import { BaseWebSocketGateway } from '../websocket/ws-base-gateway';
27
28
  import { isWebSocketGateway } from '../websocket/ws-decorators';
@@ -70,6 +71,7 @@ const processedGlobalModules: Set<Function> = ((globalThis as any)[Symbol.for('o
70
71
  export function clearGlobalServicesRegistry(): void {
71
72
  globalServicesRegistry.clear();
72
73
  processedGlobalModules.clear();
74
+ OneBunModule.resetDecoratorMetadataDiagnosis();
73
75
  }
74
76
 
75
77
  /**
@@ -274,6 +276,20 @@ export class OneBunModule implements ModuleInstance {
274
276
  * Create services with automatic dependency injection
275
277
  * Services can depend on other services (including imported ones)
276
278
  */
279
+ /**
280
+ * Whether the decorator metadata diagnostic has already run (once per process).
281
+ * Prevents repeated checks across multiple module initializations.
282
+ */
283
+ private static decoratorMetadataDiagnosed = false;
284
+
285
+ /**
286
+ * Reset the diagnostic flag (for testing).
287
+ * @internal
288
+ */
289
+ static resetDecoratorMetadataDiagnosis(): void {
290
+ OneBunModule.decoratorMetadataDiagnosed = false;
291
+ }
292
+
277
293
  private createServicesWithDI(metadata: ReturnType<typeof getModuleMetadata>): void {
278
294
  if (!metadata?.providers) {
279
295
  return;
@@ -295,6 +311,26 @@ export class OneBunModule implements ModuleInstance {
295
311
  }
296
312
  }
297
313
 
314
+ // Run decorator metadata diagnostic once: check that Bun emits design:paramtypes.
315
+ // If emitDecoratorMetadata is missing from the root tsconfig.json, Bun silently
316
+ // skips metadata emission and ALL constructor-based DI breaks.
317
+ // Only mark as diagnosed when we actually found classes with constructor params
318
+ // (modules with only zero-arg services can't tell us anything).
319
+ if (!OneBunModule.decoratorMetadataDiagnosed) {
320
+ const providerClasses = metadata.providers.filter(
321
+ (p): p is Function => typeof p === 'function',
322
+ );
323
+ const diagnosis = diagnoseDecoratorMetadata(providerClasses);
324
+ if (diagnosis.classesWithParams > 0) {
325
+ OneBunModule.decoratorMetadataDiagnosed = true;
326
+ if (!diagnosis.ok) {
327
+ throw new Error(
328
+ buildDecoratorMetadataDiagnosticMessage(diagnosis.classesWithParams),
329
+ );
330
+ }
331
+ }
332
+ }
333
+
298
334
  // Create services in dependency order
299
335
  const pendingProviders = [...metadata.providers.filter((p) => typeof p === 'function')];
300
336
  const createdServices = new Set<string>();
@@ -384,10 +384,6 @@ describe('InMemoryQueueAdapter', () => {
384
384
  expect(adapter.supports('priority')).toBe(true);
385
385
  });
386
386
 
387
- it('should support scheduled-jobs', () => {
388
- expect(adapter.supports('scheduled-jobs')).toBe(true);
389
- });
390
-
391
387
  it('should not support consumer-groups', () => {
392
388
  expect(adapter.supports('consumer-groups')).toBe(false);
393
389
  });
@@ -15,8 +15,6 @@ import type {
15
15
  PublishOptions,
16
16
  SubscribeOptions,
17
17
  Subscription,
18
- ScheduledJobOptions,
19
- ScheduledJobInfo,
20
18
  MessageHandler,
21
19
  } from '../types';
22
20
 
@@ -322,49 +320,6 @@ export class InMemoryQueueAdapter implements QueueAdapter {
322
320
  return subscription;
323
321
  }
324
322
 
325
- // ============================================================================
326
- // Scheduled Jobs
327
- // ============================================================================
328
-
329
- async addScheduledJob(name: string, options: ScheduledJobOptions): Promise<void> {
330
- this.ensureConnected();
331
-
332
- if (!this.scheduler) {
333
- throw new Error('Scheduler not initialized');
334
- }
335
-
336
- if (options.schedule.cron) {
337
- this.scheduler.addCronJob(name, options.schedule.cron, options.pattern, () => options.data, {
338
- metadata: options.metadata,
339
- overlapStrategy: options.overlapStrategy,
340
- });
341
- } else if (options.schedule.every) {
342
- this.scheduler.addIntervalJob(name, options.schedule.every, options.pattern, () => options.data, {
343
- metadata: options.metadata,
344
- });
345
- }
346
- }
347
-
348
- async removeScheduledJob(name: string): Promise<boolean> {
349
- this.ensureConnected();
350
-
351
- if (!this.scheduler) {
352
- return false;
353
- }
354
-
355
- return this.scheduler.removeJob(name);
356
- }
357
-
358
- async getScheduledJobs(): Promise<ScheduledJobInfo[]> {
359
- this.ensureConnected();
360
-
361
- if (!this.scheduler) {
362
- return [];
363
- }
364
-
365
- return this.scheduler.getJobs();
366
- }
367
-
368
323
  // ============================================================================
369
324
  // Features
370
325
  // ============================================================================
@@ -373,7 +328,6 @@ export class InMemoryQueueAdapter implements QueueAdapter {
373
328
  switch (feature) {
374
329
  case 'delayed-messages':
375
330
  case 'priority':
376
- case 'scheduled-jobs':
377
331
  case 'pattern-subscriptions':
378
332
  return true;
379
333
  case 'consumer-groups':