@avleon/core 0.0.44 → 0.0.45
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/params.js +0 -5
- package/dist/queue.d.ts +27 -36
- package/dist/queue.js +67 -99
- package/package.json +2 -1
- package/dist/queue.test.d.ts +0 -1
- package/dist/queue.test.js +0 -79
package/dist/params.js
CHANGED
|
@@ -14,7 +14,6 @@ function createParamDecorator(type) {
|
|
|
14
14
|
return function (key, options = {}) {
|
|
15
15
|
return function (target, propertyKey, parameterIndex) {
|
|
16
16
|
var _a, _b, _c, _d;
|
|
17
|
-
// Determine correct meta key
|
|
18
17
|
let metaKey;
|
|
19
18
|
switch (type) {
|
|
20
19
|
case "route:param":
|
|
@@ -35,15 +34,12 @@ function createParamDecorator(type) {
|
|
|
35
34
|
default:
|
|
36
35
|
throw new Error(`Unknown param decorator type: ${String(type)}`);
|
|
37
36
|
}
|
|
38
|
-
// Retrieve and preserve existing metadata
|
|
39
37
|
const existingParams = Reflect.getMetadata(metaKey, target, propertyKey) || [];
|
|
40
38
|
// Get parameter names (fallback safe)
|
|
41
39
|
const functionSource = target[propertyKey].toString();
|
|
42
40
|
const paramNames = ((_b = (_a = functionSource.match(/\(([^)]*)\)/)) === null || _a === void 0 ? void 0 : _a[1]) === null || _b === void 0 ? void 0 : _b.split(",").map((n) => n.trim())) || [];
|
|
43
|
-
// Determine the param type
|
|
44
41
|
const parameterTypes = Reflect.getMetadata("design:paramtypes", target, propertyKey) || [];
|
|
45
42
|
const paramDataType = parameterTypes[parameterIndex];
|
|
46
|
-
// Append new parameter
|
|
47
43
|
existingParams.push({
|
|
48
44
|
index: parameterIndex,
|
|
49
45
|
key: typeof key === "string" ? key : "all",
|
|
@@ -57,7 +53,6 @@ function createParamDecorator(type) {
|
|
|
57
53
|
: null,
|
|
58
54
|
type,
|
|
59
55
|
});
|
|
60
|
-
// Save back using the correct meta key
|
|
61
56
|
Reflect.defineMetadata(metaKey, existingParams, target, propertyKey);
|
|
62
57
|
};
|
|
63
58
|
};
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,38 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
interface
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import Bull, { Queue as BullQueue, Job, JobOptions } from 'bull';
|
|
2
|
+
export interface QueueConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
adapter?: any;
|
|
5
|
+
handler?: (job: Job) => Promise<any>;
|
|
6
|
+
options?: Bull.QueueOptions;
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
export declare class AvleonQueue<T = any> {
|
|
9
|
+
protected name?: string | undefined;
|
|
10
|
+
protected adapter?: any | undefined;
|
|
11
|
+
protected queue: BullQueue<T>;
|
|
12
|
+
protected handlerFn?: (job: Job<T>) => Promise<any>;
|
|
13
|
+
constructor(name?: string | undefined, adapter?: any | undefined, handler?: (job: Job<T>) => Promise<any>);
|
|
14
|
+
handler?(job: Job<T>): Promise<any> | any;
|
|
15
|
+
add(data: T, options?: JobOptions): Promise<Bull.Job<T>>;
|
|
16
|
+
delay(data: T, delayMs: number, options?: JobOptions): Promise<Bull.Job<T>>;
|
|
17
|
+
process(handler: (job: Job<T>) => Promise<any>): void;
|
|
18
|
+
processConcurrent(concurrency: number, handler: (job: Job<T>) => Promise<any>): void;
|
|
19
|
+
getQueue(): BullQueue<T>;
|
|
20
|
+
clean(grace: number, status?: 'completed' | 'wait' | 'active' | 'delayed' | 'failed'): Promise<Job[]>;
|
|
21
|
+
close(): Promise<void>;
|
|
22
|
+
pause(): Promise<void>;
|
|
23
|
+
resume(): Promise<void>;
|
|
24
|
+
getJob(jobId: string): Promise<Job<T> | null>;
|
|
25
|
+
getJobs(types: Array<'completed' | 'waiting' | 'active' | 'delayed' | 'failed' | 'paused'>, start?: number, end?: number): Promise<Job<T>[]>;
|
|
11
26
|
}
|
|
12
|
-
export declare
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
loadJobs(): Promise<Job[]>;
|
|
16
|
-
saveJobs(jobs: Job[]): Promise<void>;
|
|
17
|
-
}
|
|
18
|
-
export declare class AvleonQueue extends EventEmitter {
|
|
19
|
-
private name;
|
|
20
|
-
private processing;
|
|
21
|
-
private stopped;
|
|
22
|
-
private jobHandler;
|
|
23
|
-
private adapter;
|
|
24
|
-
constructor(name: string, adapter?: QueueAdapter, jobHandler?: (job: Job) => Promise<void>);
|
|
25
|
-
private defaultHandler;
|
|
26
|
-
addJob(data: any, options?: {
|
|
27
|
-
delay?: number;
|
|
28
|
-
}): Promise<void>;
|
|
29
|
-
private processNext;
|
|
30
|
-
onDone(cb: (job: Job) => void): Promise<void>;
|
|
31
|
-
onFailed(cb: (error: any, job: Job) => void): Promise<void>;
|
|
32
|
-
getJobs(): Promise<Job[]>;
|
|
33
|
-
stop(): Promise<void>;
|
|
34
|
-
}
|
|
35
|
-
export declare class QueueManager {
|
|
36
|
-
from(name: string, jobHandler?: (job: Job) => Promise<void>): Promise<AvleonQueue>;
|
|
37
|
-
}
|
|
38
|
-
export {};
|
|
27
|
+
export declare function Queue(config: QueueConfig): <T extends {
|
|
28
|
+
new (...args: any[]): AvleonQueue;
|
|
29
|
+
}>(target: T) => T;
|
package/dist/queue.js
CHANGED
|
@@ -1,116 +1,84 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var
|
|
3
|
-
|
|
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;
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
4
|
};
|
|
8
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
6
|
+
exports.AvleonQueue = void 0;
|
|
7
|
+
exports.Queue = Queue;
|
|
8
|
+
const bull_1 = __importDefault(require("bull"));
|
|
9
|
+
const typedi_1 = require("typedi");
|
|
10
|
+
class AvleonQueue {
|
|
11
|
+
constructor(name, adapter, handler) {
|
|
12
|
+
this.name = name;
|
|
13
|
+
this.adapter = adapter;
|
|
14
|
+
// Initialize queue with adapter or default Redis connection
|
|
15
|
+
this.queue = new bull_1.default(name || 'default', adapter);
|
|
16
|
+
this.handlerFn = handler;
|
|
17
|
+
// Check if the instance has a handler method defined
|
|
18
|
+
// This allows subclasses to define handler as a method
|
|
19
|
+
if (typeof this.handler === 'function' && !this.handlerFn) {
|
|
20
|
+
this.handlerFn = (job) => this.handler(job);
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// If handler is provided (from decorator or class method), set up processing
|
|
23
|
+
if (this.handlerFn) {
|
|
24
|
+
this.queue.process(this.handlerFn);
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
// Add job to queue
|
|
28
|
+
add(data, options) {
|
|
29
|
+
return this.queue.add(data, options);
|
|
30
30
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
constructor(name, adapter, jobHandler) {
|
|
35
|
-
super();
|
|
36
|
-
this.processing = false;
|
|
37
|
-
this.stopped = false;
|
|
38
|
-
this.name = name;
|
|
39
|
-
this.adapter = adapter ? adapter : new FileQueueAdapter(name);
|
|
40
|
-
this.jobHandler = jobHandler || this.defaultHandler.bind(this);
|
|
41
|
-
this.setMaxListeners(10);
|
|
31
|
+
// Add job with delay
|
|
32
|
+
delay(data, delayMs, options) {
|
|
33
|
+
return this.queue.add(data, { ...options, delay: delayMs });
|
|
42
34
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
// Process jobs (can be called manually if not using handler)
|
|
36
|
+
process(handler) {
|
|
37
|
+
this.handlerFn = handler;
|
|
38
|
+
this.queue.process(handler);
|
|
47
39
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
runAt: Date.now() + ((options === null || options === void 0 ? void 0 : options.delay) || 0),
|
|
53
|
-
status: "pending",
|
|
54
|
-
};
|
|
55
|
-
const jobs = await this.adapter.loadJobs();
|
|
56
|
-
jobs.push(job);
|
|
57
|
-
await this.adapter.saveJobs(jobs);
|
|
58
|
-
if (!this.processing)
|
|
59
|
-
this.processNext();
|
|
40
|
+
// Process with concurrency
|
|
41
|
+
processConcurrent(concurrency, handler) {
|
|
42
|
+
this.handlerFn = handler;
|
|
43
|
+
this.queue.process(concurrency, handler);
|
|
60
44
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const nextJob = jobs.find((j) => j.status === "pending");
|
|
68
|
-
if (!nextJob) {
|
|
69
|
-
this.processing = false;
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const now = Date.now();
|
|
73
|
-
if (nextJob.runAt && nextJob.runAt > now) {
|
|
74
|
-
const delay = nextJob.runAt - now;
|
|
75
|
-
await new Promise((res) => setTimeout(res, delay));
|
|
76
|
-
}
|
|
77
|
-
nextJob.status = "running";
|
|
78
|
-
await this.adapter.saveJobs(jobs);
|
|
79
|
-
this.emit("start", nextJob);
|
|
80
|
-
try {
|
|
81
|
-
await this.jobHandler(nextJob);
|
|
82
|
-
nextJob.status = "completed";
|
|
83
|
-
this.emit("done", nextJob);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
nextJob.status = "failed";
|
|
87
|
-
this.emit("failed", err, nextJob);
|
|
88
|
-
}
|
|
89
|
-
await this.adapter.saveJobs(jobs.filter((j) => j.id !== nextJob.id));
|
|
90
|
-
}
|
|
91
|
-
this.processing = false;
|
|
45
|
+
// Get the underlying Bull queue
|
|
46
|
+
getQueue() {
|
|
47
|
+
return this.queue;
|
|
48
|
+
}
|
|
49
|
+
async clean(grace, status) {
|
|
50
|
+
return this.queue.clean(grace, status);
|
|
92
51
|
}
|
|
93
|
-
async
|
|
94
|
-
this.
|
|
52
|
+
async close() {
|
|
53
|
+
await this.queue.close();
|
|
95
54
|
}
|
|
96
|
-
async
|
|
97
|
-
this.
|
|
55
|
+
async pause() {
|
|
56
|
+
await this.queue.pause();
|
|
98
57
|
}
|
|
99
|
-
async
|
|
100
|
-
|
|
58
|
+
async resume() {
|
|
59
|
+
await this.queue.resume();
|
|
101
60
|
}
|
|
102
|
-
async
|
|
103
|
-
this.
|
|
61
|
+
async getJob(jobId) {
|
|
62
|
+
return this.queue.getJob(jobId);
|
|
63
|
+
}
|
|
64
|
+
async getJobs(types, start, end) {
|
|
65
|
+
return this.queue.getJobs(types, start, end);
|
|
104
66
|
}
|
|
105
67
|
}
|
|
106
68
|
exports.AvleonQueue = AvleonQueue;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
69
|
+
function Queue(config) {
|
|
70
|
+
return function (target) {
|
|
71
|
+
// Create a new class that extends the target
|
|
72
|
+
const DecoratedClass = class extends target {
|
|
73
|
+
constructor(...args) {
|
|
74
|
+
super(config.name, config.adapter, config.handler);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
Object.defineProperty(DecoratedClass, 'name', {
|
|
78
|
+
value: target.name,
|
|
79
|
+
writable: false
|
|
80
|
+
});
|
|
81
|
+
(0, typedi_1.Service)()(DecoratedClass);
|
|
82
|
+
return DecoratedClass;
|
|
83
|
+
};
|
|
84
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.45",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"keywords": [
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"test": "."
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
+
"bull": "^4.16.5",
|
|
26
27
|
"class-transformer": "^0.5.1",
|
|
27
28
|
"class-validator": "^0.14.2",
|
|
28
29
|
"fastify": "^5.1.0",
|
package/dist/queue.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/queue.test.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const queue_1 = require("./queue");
|
|
4
|
-
const fs_1 = require("fs");
|
|
5
|
-
const path_1 = require("path");
|
|
6
|
-
jest.mock("fs", () => ({
|
|
7
|
-
promises: {
|
|
8
|
-
readFile: jest.fn(),
|
|
9
|
-
writeFile: jest.fn(),
|
|
10
|
-
},
|
|
11
|
-
}));
|
|
12
|
-
const mockQueueFile = (0, path_1.join)(__dirname, "testqueue.json");
|
|
13
|
-
describe("FileQueueAdapter", () => {
|
|
14
|
-
const jobs = [{ id: "1", data: "foo" }, { id: "2", data: "bar" }];
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
jest.clearAllMocks();
|
|
17
|
-
});
|
|
18
|
-
it("should load jobs from file", async () => {
|
|
19
|
-
fs_1.promises.readFile.mockResolvedValue(JSON.stringify(jobs));
|
|
20
|
-
const adapter = new queue_1.FileQueueAdapter("testqueue");
|
|
21
|
-
const loaded = await adapter.loadJobs();
|
|
22
|
-
expect(loaded).toEqual(jobs);
|
|
23
|
-
expect(fs_1.promises.readFile).toHaveBeenCalledWith(mockQueueFile, "utf-8");
|
|
24
|
-
});
|
|
25
|
-
it("should return empty array if file does not exist", async () => {
|
|
26
|
-
fs_1.promises.readFile.mockRejectedValue(new Error("not found"));
|
|
27
|
-
const adapter = new queue_1.FileQueueAdapter("testqueue");
|
|
28
|
-
const loaded = await adapter.loadJobs();
|
|
29
|
-
expect(loaded).toEqual([]);
|
|
30
|
-
});
|
|
31
|
-
it("should save jobs to file", async () => {
|
|
32
|
-
const adapter = new queue_1.FileQueueAdapter("testqueue");
|
|
33
|
-
await adapter.saveJobs(jobs);
|
|
34
|
-
expect(fs_1.promises.writeFile).toHaveBeenCalledWith(mockQueueFile, JSON.stringify(jobs, null, 2), "utf-8");
|
|
35
|
-
});
|
|
36
|
-
});
|
|
37
|
-
describe("QueueManager and SimpleQueue", () => {
|
|
38
|
-
let adapter;
|
|
39
|
-
// let queueManager: QueueManager;
|
|
40
|
-
let handler;
|
|
41
|
-
// beforeEach(() => {
|
|
42
|
-
// jest.clearAllMocks();
|
|
43
|
-
// adapter = new FileQueueAdapter("testqueue");
|
|
44
|
-
// queueManager = QueueManager.getInstance(adapter);
|
|
45
|
-
// handler = jest.fn().mockResolvedValue(undefined);
|
|
46
|
-
// (fs.readFile as jest.Mock).mockResolvedValue("[]");
|
|
47
|
-
// (fs.writeFile as jest.Mock).mockResolvedValue(undefined);
|
|
48
|
-
// });
|
|
49
|
-
// it("should create a queue and add a job", async () => {
|
|
50
|
-
// const queue = queueManager.createQueue(handler);
|
|
51
|
-
// await queue.addJob({ foo: "bar" });
|
|
52
|
-
// expect(fs.readFile).toHaveBeenCalled();
|
|
53
|
-
// expect(fs.writeFile).toHaveBeenCalled();
|
|
54
|
-
// });
|
|
55
|
-
// it("should process jobs using handler", async () => {
|
|
56
|
-
// (fs.readFile as jest.Mock)
|
|
57
|
-
// .mockResolvedValueOnce("[]")
|
|
58
|
-
// .mockResolvedValueOnce(JSON.stringify([{ id: "1", data: "baz" }]))
|
|
59
|
-
// .mockResolvedValueOnce("[]");
|
|
60
|
-
// const queue = queueManager.createQueue(handler);
|
|
61
|
-
// await queue.addJob("baz");
|
|
62
|
-
// expect(handler).toHaveBeenCalled();
|
|
63
|
-
// });
|
|
64
|
-
// it("should requeue job if handler throws", async () => {
|
|
65
|
-
// handler.mockRejectedValueOnce(new Error("fail"));
|
|
66
|
-
// (fs.readFile as jest.Mock)
|
|
67
|
-
// .mockResolvedValueOnce("[]")
|
|
68
|
-
// .mockResolvedValueOnce(JSON.stringify([{ id: "1", data: "baz" }]))
|
|
69
|
-
// .mockResolvedValueOnce(JSON.stringify([{ id: "1", data: "baz" }]));
|
|
70
|
-
// const queue = queueManager.createQueue(handler);
|
|
71
|
-
// await queue.addJob("baz");
|
|
72
|
-
// expect(handler).toHaveBeenCalled();
|
|
73
|
-
// expect(fs.writeFile).toHaveBeenCalledTimes(2);
|
|
74
|
-
// });
|
|
75
|
-
// it("QueueManager should be singleton", () => {
|
|
76
|
-
// const another = QueueManager.getInstance(adapter);
|
|
77
|
-
// expect(another).toBe(queueManager);
|
|
78
|
-
// });
|
|
79
|
-
});
|