@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 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 { EventEmitter } from "events";
2
- interface Job {
3
- id: string;
4
- data: any;
5
- runAt?: number;
6
- status?: "pending" | "running" | "failed" | "completed";
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
- interface QueueAdapter {
9
- loadJobs(): Promise<Job[]>;
10
- saveJobs(jobs: Job[]): Promise<void>;
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 class FileQueueAdapter implements QueueAdapter {
13
- private queueFile;
14
- constructor(queueName: string);
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 __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;
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.QueueManager = exports.AvleonQueue = exports.FileQueueAdapter = void 0;
10
- const fs_1 = require("fs");
11
- const path_1 = require("path");
12
- const crypto_1 = require("crypto");
13
- const events_1 = require("events");
14
- const decorators_1 = require("./decorators");
15
- class FileQueueAdapter {
16
- constructor(queueName) {
17
- this.queueFile = (0, path_1.join)(__dirname, `${queueName}.json`);
18
- }
19
- async loadJobs() {
20
- try {
21
- const data = await fs_1.promises.readFile(this.queueFile, "utf-8");
22
- return JSON.parse(data);
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
- catch (_a) {
25
- return [];
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
- async saveJobs(jobs) {
29
- await fs_1.promises.writeFile(this.queueFile, JSON.stringify(jobs, null, 2), "utf-8");
27
+ // Add job to queue
28
+ add(data, options) {
29
+ return this.queue.add(data, options);
30
30
  }
31
- }
32
- exports.FileQueueAdapter = FileQueueAdapter;
33
- class AvleonQueue extends events_1.EventEmitter {
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
- async defaultHandler(job) {
44
- if (typeof job.data === "function") {
45
- await job.data();
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
- async addJob(data, options) {
49
- const job = {
50
- id: (0, crypto_1.randomUUID)(),
51
- data,
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
- async processNext() {
62
- if (this.processing || this.stopped)
63
- return;
64
- this.processing = true;
65
- while (!this.stopped) {
66
- const jobs = await this.adapter.loadJobs();
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 onDone(cb) {
94
- this.on("done", cb);
52
+ async close() {
53
+ await this.queue.close();
95
54
  }
96
- async onFailed(cb) {
97
- this.on("failed", cb);
55
+ async pause() {
56
+ await this.queue.pause();
98
57
  }
99
- async getJobs() {
100
- return this.adapter.loadJobs();
58
+ async resume() {
59
+ await this.queue.resume();
101
60
  }
102
- async stop() {
103
- this.stopped = true;
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
- let QueueManager = class QueueManager {
108
- async from(name, jobHandler) {
109
- const q = new AvleonQueue(name, new FileQueueAdapter(name), jobHandler);
110
- return q;
111
- }
112
- };
113
- exports.QueueManager = QueueManager;
114
- exports.QueueManager = QueueManager = __decorate([
115
- decorators_1.AppService
116
- ], QueueManager);
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.44",
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",
@@ -1 +0,0 @@
1
- export {};
@@ -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
- });