@hazeljs/queue 0.2.0-beta.54 → 0.2.0-beta.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/queue.decorator.test.d.ts +2 -0
- package/dist/__tests__/queue.decorator.test.d.ts.map +1 -0
- package/dist/__tests__/queue.decorator.test.js +127 -0
- package/dist/__tests__/queue.module.test.d.ts +2 -0
- package/dist/__tests__/queue.module.test.d.ts.map +1 -0
- package/dist/__tests__/queue.module.test.js +94 -0
- package/dist/__tests__/queue.service.test.d.ts +2 -0
- package/dist/__tests__/queue.service.test.d.ts.map +1 -0
- package/dist/__tests__/queue.service.test.js +101 -0
- package/dist/queue.service.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.decorator.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/queue.decorator.test.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
require("reflect-metadata");
|
|
13
|
+
const queue_decorator_1 = require("../queue.decorator");
|
|
14
|
+
describe('Queue decorator', () => {
|
|
15
|
+
describe('@Queue', () => {
|
|
16
|
+
it('should attach metadata to a method', () => {
|
|
17
|
+
class TestProcessor {
|
|
18
|
+
handleJob() { }
|
|
19
|
+
}
|
|
20
|
+
__decorate([
|
|
21
|
+
(0, queue_decorator_1.Queue)('test-queue'),
|
|
22
|
+
__metadata("design:type", Function),
|
|
23
|
+
__metadata("design:paramtypes", []),
|
|
24
|
+
__metadata("design:returntype", void 0)
|
|
25
|
+
], TestProcessor.prototype, "handleJob", null);
|
|
26
|
+
const metadata = (0, queue_decorator_1.getQueueProcessorMetadata)(new TestProcessor());
|
|
27
|
+
expect(metadata).toHaveLength(1);
|
|
28
|
+
expect(metadata[0].queueName).toBe('test-queue');
|
|
29
|
+
expect(metadata[0].methodName).toBe('handleJob');
|
|
30
|
+
expect(metadata[0].options).toEqual({});
|
|
31
|
+
});
|
|
32
|
+
it('should attach metadata with options', () => {
|
|
33
|
+
class TestProcessor {
|
|
34
|
+
handleWelcome() { }
|
|
35
|
+
}
|
|
36
|
+
__decorate([
|
|
37
|
+
(0, queue_decorator_1.Queue)('emails', { name: 'welcome-handler' }),
|
|
38
|
+
__metadata("design:type", Function),
|
|
39
|
+
__metadata("design:paramtypes", []),
|
|
40
|
+
__metadata("design:returntype", void 0)
|
|
41
|
+
], TestProcessor.prototype, "handleWelcome", null);
|
|
42
|
+
const metadata = (0, queue_decorator_1.getQueueProcessorMetadata)(new TestProcessor());
|
|
43
|
+
expect(metadata).toHaveLength(1);
|
|
44
|
+
expect(metadata[0].queueName).toBe('emails');
|
|
45
|
+
expect(metadata[0].options).toEqual({ name: 'welcome-handler' });
|
|
46
|
+
});
|
|
47
|
+
it('should support multiple processors on the same class', () => {
|
|
48
|
+
class MultiQueueProcessor {
|
|
49
|
+
handleEmail() { }
|
|
50
|
+
handleOrder() { }
|
|
51
|
+
handleNotification() { }
|
|
52
|
+
}
|
|
53
|
+
__decorate([
|
|
54
|
+
(0, queue_decorator_1.Queue)('emails'),
|
|
55
|
+
__metadata("design:type", Function),
|
|
56
|
+
__metadata("design:paramtypes", []),
|
|
57
|
+
__metadata("design:returntype", void 0)
|
|
58
|
+
], MultiQueueProcessor.prototype, "handleEmail", null);
|
|
59
|
+
__decorate([
|
|
60
|
+
(0, queue_decorator_1.Queue)('orders'),
|
|
61
|
+
__metadata("design:type", Function),
|
|
62
|
+
__metadata("design:paramtypes", []),
|
|
63
|
+
__metadata("design:returntype", void 0)
|
|
64
|
+
], MultiQueueProcessor.prototype, "handleOrder", null);
|
|
65
|
+
__decorate([
|
|
66
|
+
(0, queue_decorator_1.Queue)('notifications'),
|
|
67
|
+
__metadata("design:type", Function),
|
|
68
|
+
__metadata("design:paramtypes", []),
|
|
69
|
+
__metadata("design:returntype", void 0)
|
|
70
|
+
], MultiQueueProcessor.prototype, "handleNotification", null);
|
|
71
|
+
const metadata = (0, queue_decorator_1.getQueueProcessorMetadata)(new MultiQueueProcessor());
|
|
72
|
+
expect(metadata).toHaveLength(3);
|
|
73
|
+
const queueNames = metadata.map((m) => m.queueName);
|
|
74
|
+
expect(queueNames).toContain('emails');
|
|
75
|
+
expect(queueNames).toContain('orders');
|
|
76
|
+
expect(queueNames).toContain('notifications');
|
|
77
|
+
const methodNames = metadata.map((m) => m.methodName);
|
|
78
|
+
expect(methodNames).toContain('handleEmail');
|
|
79
|
+
expect(methodNames).toContain('handleOrder');
|
|
80
|
+
expect(methodNames).toContain('handleNotification');
|
|
81
|
+
});
|
|
82
|
+
it('should support multiple methods for the same queue', () => {
|
|
83
|
+
class SameQueueProcessor {
|
|
84
|
+
handleWelcome() { }
|
|
85
|
+
handleReminder() { }
|
|
86
|
+
}
|
|
87
|
+
__decorate([
|
|
88
|
+
(0, queue_decorator_1.Queue)('emails'),
|
|
89
|
+
__metadata("design:type", Function),
|
|
90
|
+
__metadata("design:paramtypes", []),
|
|
91
|
+
__metadata("design:returntype", void 0)
|
|
92
|
+
], SameQueueProcessor.prototype, "handleWelcome", null);
|
|
93
|
+
__decorate([
|
|
94
|
+
(0, queue_decorator_1.Queue)('emails'),
|
|
95
|
+
__metadata("design:type", Function),
|
|
96
|
+
__metadata("design:paramtypes", []),
|
|
97
|
+
__metadata("design:returntype", void 0)
|
|
98
|
+
], SameQueueProcessor.prototype, "handleReminder", null);
|
|
99
|
+
const metadata = (0, queue_decorator_1.getQueueProcessorMetadata)(new SameQueueProcessor());
|
|
100
|
+
expect(metadata).toHaveLength(2);
|
|
101
|
+
expect(metadata.every((m) => m.queueName === 'emails')).toBe(true);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('getQueueProcessorMetadata', () => {
|
|
105
|
+
it('should return empty array for class without @Queue decorators', () => {
|
|
106
|
+
class PlainClass {
|
|
107
|
+
plainMethod() { }
|
|
108
|
+
}
|
|
109
|
+
const metadata = (0, queue_decorator_1.getQueueProcessorMetadata)(new PlainClass());
|
|
110
|
+
expect(metadata).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
it('should preserve target reference in metadata', () => {
|
|
113
|
+
class ProcessorWithTarget {
|
|
114
|
+
handle() { }
|
|
115
|
+
}
|
|
116
|
+
__decorate([
|
|
117
|
+
(0, queue_decorator_1.Queue)('test'),
|
|
118
|
+
__metadata("design:type", Function),
|
|
119
|
+
__metadata("design:paramtypes", []),
|
|
120
|
+
__metadata("design:returntype", void 0)
|
|
121
|
+
], ProcessorWithTarget.prototype, "handle", null);
|
|
122
|
+
const instance = new ProcessorWithTarget();
|
|
123
|
+
const metadata = (0, queue_decorator_1.getQueueProcessorMetadata)(instance);
|
|
124
|
+
expect(metadata[0].target).toBeDefined();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.module.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/queue.module.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const core_1 = require("@hazeljs/core");
|
|
13
|
+
const queue_decorator_1 = require("../queue.decorator");
|
|
14
|
+
const queue_module_1 = require("../queue.module");
|
|
15
|
+
const queue_service_1 = require("../queue.service");
|
|
16
|
+
// Mock bullmq
|
|
17
|
+
jest.mock('bullmq', () => ({
|
|
18
|
+
Queue: jest.fn().mockImplementation(() => ({
|
|
19
|
+
add: jest.fn().mockResolvedValue({ id: 'job-1' }),
|
|
20
|
+
close: jest.fn().mockResolvedValue(undefined),
|
|
21
|
+
})),
|
|
22
|
+
}));
|
|
23
|
+
describe('QueueModule', () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
core_1.Container.getInstance().clear?.();
|
|
26
|
+
});
|
|
27
|
+
describe('forRoot', () => {
|
|
28
|
+
it('should return module config with providers and exports', () => {
|
|
29
|
+
const config = queue_module_1.QueueModule.forRoot({
|
|
30
|
+
connection: { host: 'localhost', port: 6379 },
|
|
31
|
+
});
|
|
32
|
+
expect(config.module).toBe(queue_module_1.QueueModule);
|
|
33
|
+
expect(config.providers).toHaveLength(1);
|
|
34
|
+
expect(config.providers[0].provide).toBe(queue_service_1.QueueService);
|
|
35
|
+
expect(config.providers[0].useFactory).toBeDefined();
|
|
36
|
+
expect(config.exports).toContain(queue_service_1.QueueService);
|
|
37
|
+
expect(config.global).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
it('should use isGlobal option', () => {
|
|
40
|
+
const config = queue_module_1.QueueModule.forRoot({
|
|
41
|
+
connection: { host: 'localhost' },
|
|
42
|
+
isGlobal: false,
|
|
43
|
+
});
|
|
44
|
+
expect(config.global).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
it('should create QueueService with connection when useFactory is called', () => {
|
|
47
|
+
const config = queue_module_1.QueueModule.forRoot({
|
|
48
|
+
connection: { host: 'redis.example.com', port: 6380 },
|
|
49
|
+
});
|
|
50
|
+
const factory = config.providers[0].useFactory;
|
|
51
|
+
const service = factory();
|
|
52
|
+
expect(service).toBeInstanceOf(queue_service_1.QueueService);
|
|
53
|
+
// Service should be usable (connection was set)
|
|
54
|
+
expect(() => service.getQueue('test')).not.toThrow();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('getProcessorMetadata', () => {
|
|
58
|
+
it('should return empty array for object without @Queue decorators', () => {
|
|
59
|
+
const metadata = queue_module_1.QueueModule.getProcessorMetadata({ foo: 'bar' });
|
|
60
|
+
expect(metadata).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
it('should return processor metadata for class with @Queue decorators', () => {
|
|
63
|
+
class TestProcessor {
|
|
64
|
+
handleEmail() { }
|
|
65
|
+
}
|
|
66
|
+
__decorate([
|
|
67
|
+
(0, queue_decorator_1.Queue)('emails'),
|
|
68
|
+
__metadata("design:type", Function),
|
|
69
|
+
__metadata("design:paramtypes", []),
|
|
70
|
+
__metadata("design:returntype", void 0)
|
|
71
|
+
], TestProcessor.prototype, "handleEmail", null);
|
|
72
|
+
const instance = new TestProcessor();
|
|
73
|
+
const metadata = queue_module_1.QueueModule.getProcessorMetadata(instance);
|
|
74
|
+
expect(metadata).toHaveLength(1);
|
|
75
|
+
expect(metadata[0].queueName).toBe('emails');
|
|
76
|
+
expect(metadata[0].methodName).toBe('handleEmail');
|
|
77
|
+
expect(metadata[0].options).toEqual({});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('getQueueService', () => {
|
|
81
|
+
it('should throw when QueueService is not in container', () => {
|
|
82
|
+
// Register undefined so resolve returns undefined (container auto-resolves classes otherwise)
|
|
83
|
+
core_1.Container.getInstance().register(queue_service_1.QueueService, undefined);
|
|
84
|
+
expect(() => queue_module_1.QueueModule.getQueueService()).toThrow('QueueService not found. Ensure QueueModule is imported.');
|
|
85
|
+
});
|
|
86
|
+
it('should return QueueService when registered in container', () => {
|
|
87
|
+
const service = new queue_service_1.QueueService();
|
|
88
|
+
service.setConnection({ host: 'localhost' });
|
|
89
|
+
core_1.Container.getInstance().register(queue_service_1.QueueService, service);
|
|
90
|
+
const resolved = queue_module_1.QueueModule.getQueueService();
|
|
91
|
+
expect(resolved).toBe(service);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.service.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/queue.service.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const queue_service_1 = require("../queue.service");
|
|
4
|
+
// Mock bullmq
|
|
5
|
+
const mockAdd = jest.fn();
|
|
6
|
+
const mockClose = jest.fn();
|
|
7
|
+
jest.mock('bullmq', () => ({
|
|
8
|
+
Queue: jest.fn().mockImplementation(() => ({
|
|
9
|
+
add: mockAdd,
|
|
10
|
+
close: mockClose,
|
|
11
|
+
})),
|
|
12
|
+
}));
|
|
13
|
+
describe('QueueService', () => {
|
|
14
|
+
let service;
|
|
15
|
+
const connection = { host: 'localhost', port: 6379 };
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
mockAdd.mockResolvedValue({ id: 'job-123' });
|
|
19
|
+
mockClose.mockResolvedValue(undefined);
|
|
20
|
+
service = new queue_service_1.QueueService();
|
|
21
|
+
service.setConnection(connection);
|
|
22
|
+
});
|
|
23
|
+
describe('setConnection', () => {
|
|
24
|
+
it('should store connection options', () => {
|
|
25
|
+
const svc = new queue_service_1.QueueService();
|
|
26
|
+
svc.setConnection(connection);
|
|
27
|
+
expect(() => svc.getQueue('test')).not.toThrow();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('getQueue', () => {
|
|
31
|
+
it('should throw when connection is not configured', () => {
|
|
32
|
+
const svc = new queue_service_1.QueueService();
|
|
33
|
+
expect(() => svc.getQueue('test')).toThrow('QueueService not configured. Call setConnection() or use QueueModule.forRoot()');
|
|
34
|
+
});
|
|
35
|
+
it('should create and return a queue for the given name', () => {
|
|
36
|
+
const queue = service.getQueue('my-queue');
|
|
37
|
+
expect(queue).toBeDefined();
|
|
38
|
+
expect(queue.add).toBeDefined();
|
|
39
|
+
});
|
|
40
|
+
it('should reuse existing queue for same name', () => {
|
|
41
|
+
const queue1 = service.getQueue('reused');
|
|
42
|
+
const queue2 = service.getQueue('reused');
|
|
43
|
+
expect(queue1).toBe(queue2);
|
|
44
|
+
});
|
|
45
|
+
it('should create different queues for different names', () => {
|
|
46
|
+
const queue1 = service.getQueue('queue-a');
|
|
47
|
+
const queue2 = service.getQueue('queue-b');
|
|
48
|
+
expect(queue1).not.toBe(queue2);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('add', () => {
|
|
52
|
+
it('should add a job and return id', async () => {
|
|
53
|
+
const result = await service.add('emails', 'welcome', { userId: '1', email: 'a@b.com' });
|
|
54
|
+
expect(result).toEqual({ id: 'job-123' });
|
|
55
|
+
expect(mockAdd).toHaveBeenCalledWith('welcome', { userId: '1', email: 'a@b.com' }, undefined);
|
|
56
|
+
});
|
|
57
|
+
it('should add job with empty object when data is undefined', async () => {
|
|
58
|
+
await service.add('tasks', 'run');
|
|
59
|
+
expect(mockAdd).toHaveBeenCalledWith('run', {}, undefined);
|
|
60
|
+
});
|
|
61
|
+
it('should pass job options to BullMQ', async () => {
|
|
62
|
+
await service.add('tasks', 'process', { id: 1 }, { delay: 5000, priority: 10 });
|
|
63
|
+
expect(mockAdd).toHaveBeenCalledWith('process', { id: 1 }, {
|
|
64
|
+
delay: 5000,
|
|
65
|
+
priority: 10,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
describe('addDelayed', () => {
|
|
70
|
+
it('should add job with delay', async () => {
|
|
71
|
+
await service.addDelayed('emails', 'reminder', { userId: '1' }, 3000);
|
|
72
|
+
expect(mockAdd).toHaveBeenCalledWith('reminder', { userId: '1' }, { delay: 3000 });
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('addWithRetry', () => {
|
|
76
|
+
it('should add job with retry options', async () => {
|
|
77
|
+
await service.addWithRetry('orders', 'process', { orderId: 'o1' }, {
|
|
78
|
+
attempts: 3,
|
|
79
|
+
backoff: { type: 'exponential', delay: 1000 },
|
|
80
|
+
});
|
|
81
|
+
expect(mockAdd).toHaveBeenCalledWith('process', { orderId: 'o1' }, {
|
|
82
|
+
attempts: 3,
|
|
83
|
+
backoff: { type: 'exponential', delay: 1000 },
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
describe('close', () => {
|
|
88
|
+
it('should close all queues', async () => {
|
|
89
|
+
service.getQueue('q1');
|
|
90
|
+
service.getQueue('q2');
|
|
91
|
+
await service.close();
|
|
92
|
+
expect(mockClose).toHaveBeenCalledTimes(2);
|
|
93
|
+
});
|
|
94
|
+
it('should clear queues map after close', async () => {
|
|
95
|
+
service.getQueue('q1');
|
|
96
|
+
await service.close();
|
|
97
|
+
// After close, getQueue would create new instance - we verify close was called
|
|
98
|
+
expect(mockClose).toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
package/dist/queue.service.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hazeljs/queue",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.56",
|
|
4
4
|
"description": "Redis-backed job queue module for HazelJS framework using BullMQ",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -58,5 +58,5 @@
|
|
|
58
58
|
"description": "Required for Redis connection. BullMQ uses ioredis for Redis connectivity."
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "c2737e90974458a8438eee623726f0a453b66b8b"
|
|
62
62
|
}
|