@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.
- package/package.json +6 -2
- package/src/application/application.test.ts +325 -8
- package/src/application/application.ts +23 -6
- package/src/decorators/decorators.test.ts +1 -1
- package/src/decorators/decorators.ts +1 -1
- package/src/decorators/metadata.test.ts +86 -0
- package/src/decorators/metadata.ts +199 -0
- package/src/docs-examples.test.ts +2 -1
- package/src/index.ts +2 -2
- package/src/module/module.ts +36 -0
- package/src/queue/adapters/memory.adapter.test.ts +0 -4
- package/src/queue/adapters/memory.adapter.ts +0 -46
- package/src/queue/adapters/redis.adapter.test.ts +0 -64
- package/src/queue/adapters/redis.adapter.ts +0 -41
- package/src/queue/docs-examples.test.ts +220 -9
- package/src/queue/index.ts +8 -1
- package/src/queue/queue-service-proxy.test.ts +12 -3
- package/src/queue/queue-service-proxy.ts +37 -7
- package/src/queue/queue.service.test.ts +138 -16
- package/src/queue/queue.service.ts +48 -11
- package/src/queue/scheduler.test.ts +280 -0
- package/src/queue/scheduler.ts +156 -3
- package/src/queue/types.ts +75 -27
- package/src/testing/test-utils.ts +1 -1
|
@@ -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
|
*/
|
|
@@ -48,6 +48,8 @@ import type {
|
|
|
48
48
|
import type { HttpExecutionContext } from './types';
|
|
49
49
|
import type { ServerWebSocket } from 'bun';
|
|
50
50
|
|
|
51
|
+
import { makeMockLoggerLayer } from './testing';
|
|
52
|
+
|
|
51
53
|
import {
|
|
52
54
|
Controller,
|
|
53
55
|
Get,
|
|
@@ -109,7 +111,6 @@ import {
|
|
|
109
111
|
createWsClient,
|
|
110
112
|
createNativeWsClient,
|
|
111
113
|
matchPattern,
|
|
112
|
-
makeMockLoggerLayer,
|
|
113
114
|
hasOnModuleInit,
|
|
114
115
|
hasOnApplicationInit,
|
|
115
116
|
hasOnModuleDestroy,
|
package/src/index.ts
CHANGED
|
@@ -130,8 +130,8 @@ export * from './queue';
|
|
|
130
130
|
// Validation
|
|
131
131
|
export * from './validation';
|
|
132
132
|
|
|
133
|
-
// Testing Utilities
|
|
134
|
-
|
|
133
|
+
// Testing Utilities are available via '@onebun/core/testing' subpath import
|
|
134
|
+
// to avoid requiring testcontainers as a mandatory dependency
|
|
135
135
|
|
|
136
136
|
// HTTP Guards
|
|
137
137
|
export * from './http-guards';
|
package/src/module/module.ts
CHANGED
|
@@ -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':
|
|
@@ -158,76 +158,12 @@ describe('RedisQueueAdapter', () => {
|
|
|
158
158
|
// current RedisClient.raw() implementation. These features need a proper
|
|
159
159
|
// implementation using Bun's Redis client's sendCommand API.
|
|
160
160
|
|
|
161
|
-
describe('scheduled jobs', () => {
|
|
162
|
-
beforeEach(async () => {
|
|
163
|
-
await adapter.connect();
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should add and get scheduled jobs', async () => {
|
|
167
|
-
await adapter.addScheduledJob('test-job', {
|
|
168
|
-
pattern: 'job:test',
|
|
169
|
-
data: { action: 'process' },
|
|
170
|
-
schedule: { every: 1000 },
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const jobs = await adapter.getScheduledJobs();
|
|
174
|
-
|
|
175
|
-
expect(jobs.find((j) => j.name === 'test-job')).toBeDefined();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should add cron scheduled job', async () => {
|
|
179
|
-
await adapter.addScheduledJob('cron-job', {
|
|
180
|
-
pattern: 'job:cron',
|
|
181
|
-
data: { action: 'cron' },
|
|
182
|
-
schedule: { cron: '0 * * * *' },
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
const jobs = await adapter.getScheduledJobs();
|
|
186
|
-
|
|
187
|
-
expect(jobs.find((j) => j.name === 'cron-job')).toBeDefined();
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it('should remove scheduled job', async () => {
|
|
191
|
-
await adapter.addScheduledJob('removable-job', {
|
|
192
|
-
pattern: 'job:remove',
|
|
193
|
-
data: {},
|
|
194
|
-
schedule: { every: 1000 },
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
const removed = await adapter.removeScheduledJob('removable-job');
|
|
198
|
-
|
|
199
|
-
expect(removed).toBe(true);
|
|
200
|
-
|
|
201
|
-
const jobs = await adapter.getScheduledJobs();
|
|
202
|
-
expect(jobs.find((j) => j.name === 'removable-job')).toBeUndefined();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('should return false when removing non-existent job', async () => {
|
|
206
|
-
const removed = await adapter.removeScheduledJob('non-existent');
|
|
207
|
-
|
|
208
|
-
expect(removed).toBe(false);
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('should throw when adding job while disconnected', async () => {
|
|
212
|
-
await adapter.disconnect();
|
|
213
|
-
|
|
214
|
-
await expect(
|
|
215
|
-
adapter.addScheduledJob('fail-job', {
|
|
216
|
-
pattern: 'job:fail',
|
|
217
|
-
data: {},
|
|
218
|
-
schedule: { every: 1000 },
|
|
219
|
-
}),
|
|
220
|
-
).rejects.toThrow('not connected');
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
|
|
224
161
|
describe('features', () => {
|
|
225
162
|
it('should support all standard queue features', () => {
|
|
226
163
|
expect(adapter.supports('delayed-messages')).toBe(true);
|
|
227
164
|
expect(adapter.supports('priority')).toBe(true);
|
|
228
165
|
expect(adapter.supports('dead-letter-queue')).toBe(true);
|
|
229
166
|
expect(adapter.supports('retry')).toBe(true);
|
|
230
|
-
expect(adapter.supports('scheduled-jobs')).toBe(true);
|
|
231
167
|
expect(adapter.supports('consumer-groups')).toBe(true);
|
|
232
168
|
expect(adapter.supports('pattern-subscriptions')).toBe(true);
|
|
233
169
|
});
|
|
@@ -21,8 +21,6 @@ import type {
|
|
|
21
21
|
PublishOptions,
|
|
22
22
|
SubscribeOptions,
|
|
23
23
|
Subscription,
|
|
24
|
-
ScheduledJobOptions,
|
|
25
|
-
ScheduledJobInfo,
|
|
26
24
|
MessageHandler,
|
|
27
25
|
} from '../types';
|
|
28
26
|
|
|
@@ -409,45 +407,6 @@ export class RedisQueueAdapter implements QueueAdapter {
|
|
|
409
407
|
return subscription;
|
|
410
408
|
}
|
|
411
409
|
|
|
412
|
-
// ============================================================================
|
|
413
|
-
// Scheduled Jobs
|
|
414
|
-
// ============================================================================
|
|
415
|
-
|
|
416
|
-
async addScheduledJob(name: string, options: ScheduledJobOptions): Promise<void> {
|
|
417
|
-
this.ensureConnected();
|
|
418
|
-
|
|
419
|
-
if (!this.scheduler) {
|
|
420
|
-
throw new Error('Scheduler not initialized');
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (options.schedule.cron) {
|
|
424
|
-
this.scheduler.addCronJob(name, options.schedule.cron, options.pattern, () => options.data, {
|
|
425
|
-
metadata: options.metadata,
|
|
426
|
-
overlapStrategy: options.overlapStrategy,
|
|
427
|
-
});
|
|
428
|
-
} else if (options.schedule.every) {
|
|
429
|
-
this.scheduler.addIntervalJob(name, options.schedule.every, options.pattern, () => options.data, {
|
|
430
|
-
metadata: options.metadata,
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
async removeScheduledJob(name: string): Promise<boolean> {
|
|
436
|
-
if (!this.scheduler) {
|
|
437
|
-
return false;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return this.scheduler.removeJob(name);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async getScheduledJobs(): Promise<ScheduledJobInfo[]> {
|
|
444
|
-
if (!this.scheduler) {
|
|
445
|
-
return [];
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
return this.scheduler.getJobs();
|
|
449
|
-
}
|
|
450
|
-
|
|
451
410
|
// ============================================================================
|
|
452
411
|
// Features
|
|
453
412
|
// ============================================================================
|