@avleon/core 0.0.42-rc0.1 → 0.0.43

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/icore.d.ts CHANGED
@@ -148,6 +148,7 @@ export declare class AvleonApplication {
148
148
  private _isConfigClass;
149
149
  useCors<T = FastifyCorsOptions>(corsOptions?: ConfigInput<T>): void;
150
150
  useWebSocket<T = Partial<ServerOptions>>(socketOptions: ConfigInput<T>): void;
151
+ useSocketIO<T = Partial<ServerOptions>>(socketOptions: ConfigInput<T>): void;
151
152
  private _initWebSocket;
152
153
  useOpenApi<T = OpenApiUiOptions>(configOrClass: OpenApiConfigInput<T>): void;
153
154
  useMultipart<T extends MultipartOptions>(options: ConfigInput<T>): void;
package/dist/icore.js CHANGED
@@ -59,6 +59,7 @@ const multipart_1 = __importDefault(require("@fastify/multipart"));
59
59
  const validation_1 = require("./validation");
60
60
  const utils_1 = require("./utils");
61
61
  const socket_io_1 = require("socket.io");
62
+ const event_dispatcher_1 = require("./event-dispatcher");
62
63
  const event_subscriber_1 = require("./event-subscriber");
63
64
  const stream_1 = __importDefault(require("stream"));
64
65
  const kenx_provider_1 = require("./kenx-provider");
@@ -191,14 +192,16 @@ class AvleonApplication {
191
192
  this._hasWebsocket = true;
192
193
  this._initWebSocket(socketOptions);
193
194
  }
195
+ useSocketIO(socketOptions) {
196
+ this._hasWebsocket = true;
197
+ this._initWebSocket(socketOptions);
198
+ }
194
199
  async _initWebSocket(options) {
195
200
  const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
196
201
  failOnMissing: true,
197
202
  customMessage: 'Install "fastify-socket.io" to enable socket.io.\n\n run pnpm install fastify-socket.io',
198
203
  });
199
- await this.app.register(fsSocketIO, options);
200
- const socketIO = await this.app.io;
201
- typedi_1.default.set(socket_io_1.Server, socketIO);
204
+ this.app.register(fsSocketIO, options);
202
205
  }
203
206
  useOpenApi(configOrClass) {
204
207
  let openApiConfig;
@@ -773,7 +776,15 @@ class AvleonApplication {
773
776
  }
774
777
  }
775
778
  handleSocket(socket) {
779
+ const contextService = typedi_1.default.get(event_dispatcher_1.SocketContextService);
776
780
  subscriberRegistry.register(socket);
781
+ // Wrap all future event handlers with context
782
+ const originalOn = socket.on.bind(socket);
783
+ socket.on = (event, handler) => {
784
+ return originalOn(event, (...args) => {
785
+ contextService.run(socket, () => handler(...args));
786
+ });
787
+ };
777
788
  }
778
789
  async run(port = 4000, fn) {
779
790
  if (this.alreadyRun)
@@ -803,20 +814,6 @@ class AvleonApplication {
803
814
  },
804
815
  });
805
816
  });
806
- if (this._hasWebsocket) {
807
- await this.app.io.on("connection", this.handleSocket);
808
- await this.app.io.use((socket, next) => {
809
- const token = socket.handshake.auth.token;
810
- try {
811
- const user = { id: 1, name: "tareq" };
812
- socket.data.user = user; // this powers @AuthUser()
813
- next();
814
- }
815
- catch (_a) {
816
- next(new Error("Unauthorized"));
817
- }
818
- });
819
- }
820
817
  this.app.setErrorHandler((error, request, reply) => {
821
818
  if (error instanceof exceptions_1.BaseHttpException) {
822
819
  const response = {
@@ -836,6 +833,31 @@ class AvleonApplication {
836
833
  });
837
834
  });
838
835
  await this.app.ready();
836
+ if (this._hasWebsocket) {
837
+ if (!this.app.io) {
838
+ throw new Error("Socket.IO not initialized. Make sure fastify-socket.io is registered correctly.");
839
+ }
840
+ // Register the io instance in Container
841
+ typedi_1.default.set(socket_io_1.Server, this.app.io);
842
+ // Register middleware first
843
+ // await this.app.io.use(
844
+ // (
845
+ // socket: { handshake: { auth: { token: any } }; data: { user: any } },
846
+ // next: any,
847
+ // ) => {
848
+ // const token = socket.handshake.auth.token;
849
+ // try {
850
+ // const user = { id: 1, name: "tareq" };
851
+ // socket.data.user = user; // this powers @AuthUser()
852
+ // next();
853
+ // } catch {
854
+ // next(new Error("Unauthorized"));
855
+ // }
856
+ // },
857
+ // );
858
+ // Then register connection handler
859
+ await this.app.io.on("connection", this.handleSocket.bind(this));
860
+ }
839
861
  await this.app.listen({ port });
840
862
  console.log(`Application running on http://127.0.0.1:${port}`);
841
863
  }
package/dist/queue.d.ts CHANGED
@@ -1,12 +1,9 @@
1
- /**
2
- * @copyright 2024
3
- * @author Tareq Hossain
4
- * @email xtrinsic96@gmail.com
5
- * @url https://github.com/xtareq
6
- */
1
+ import { EventEmitter } from "events";
7
2
  interface Job {
8
3
  id: string;
9
4
  data: any;
5
+ runAt?: number;
6
+ status?: "pending" | "running" | "failed" | "completed";
10
7
  }
11
8
  interface QueueAdapter {
12
9
  loadJobs(): Promise<Job[]>;
@@ -18,19 +15,24 @@ export declare class FileQueueAdapter implements QueueAdapter {
18
15
  loadJobs(): Promise<Job[]>;
19
16
  saveJobs(jobs: Job[]): Promise<void>;
20
17
  }
21
- declare class SimpleQueue {
18
+ export declare class AvleonQueue extends EventEmitter {
19
+ private name;
22
20
  private processing;
21
+ private stopped;
23
22
  private jobHandler;
24
23
  private adapter;
25
- constructor(adapter: QueueAdapter, jobHandler: (job: Job) => Promise<void>);
26
- addJob(data: any): Promise<void>;
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>;
27
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>;
28
34
  }
29
35
  export declare class QueueManager {
30
- private static instance;
31
- private adapter;
32
- private constructor();
33
- static getInstance(adapter: QueueAdapter): QueueManager;
34
- createQueue(jobHandler: (job: Job) => Promise<void>): SimpleQueue;
36
+ from(name: string, jobHandler?: (job: Job) => Promise<void>): Promise<AvleonQueue>;
35
37
  }
36
38
  export {};
package/dist/queue.js CHANGED
@@ -1,15 +1,17 @@
1
1
  "use strict";
2
- /**
3
- * @copyright 2024
4
- * @author Tareq Hossain
5
- * @email xtrinsic96@gmail.com
6
- * @url https://github.com/xtareq
7
- */
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
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.QueueManager = exports.FileQueueAdapter = void 0;
9
+ exports.QueueManager = exports.AvleonQueue = exports.FileQueueAdapter = void 0;
10
10
  const fs_1 = require("fs");
11
11
  const path_1 = require("path");
12
- const helpers_1 = require("./helpers");
12
+ const crypto_1 = require("crypto");
13
+ const events_1 = require("events");
14
+ const decorators_1 = require("./decorators");
13
15
  class FileQueueAdapter {
14
16
  constructor(queueName) {
15
17
  this.queueFile = (0, path_1.join)(__dirname, `${queueName}.json`);
@@ -19,7 +21,7 @@ class FileQueueAdapter {
19
21
  const data = await fs_1.promises.readFile(this.queueFile, "utf-8");
20
22
  return JSON.parse(data);
21
23
  }
22
- catch (error) {
24
+ catch (_a) {
23
25
  return [];
24
26
  }
25
27
  }
@@ -28,76 +30,87 @@ class FileQueueAdapter {
28
30
  }
29
31
  }
30
32
  exports.FileQueueAdapter = FileQueueAdapter;
31
- // class RedisQueueAdapter implements QueueAdapter {
32
- // private client = createClient();
33
- // private queueKey: string;
34
- //
35
- // constructor(queueName: string) {
36
- // this.queueKey = `queue:${queueName}`;
37
- // this.client.connect();
38
- // }
39
- //
40
- // async loadJobs(): Promise<Job[]> {
41
- // const jobs = await this.client.lRange(this.queueKey, 0, -1);
42
- // return jobs.map((job) => JSON.parse(job));
43
- // }
44
- //
45
- // async saveJobs(jobs: Job[]) {
46
- // await this.client.del(this.queueKey);
47
- // if (jobs.length > 0) {
48
- // await this.client.rPush(this.queueKey, ...jobs.map(job => JSON.stringify(job)));
49
- // }
50
- // }
51
- // }
52
- class SimpleQueue {
53
- constructor(adapter, jobHandler) {
33
+ class AvleonQueue extends events_1.EventEmitter {
34
+ constructor(name, adapter, jobHandler) {
35
+ super();
54
36
  this.processing = false;
55
- this.adapter = adapter;
56
- this.jobHandler = jobHandler;
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);
57
42
  }
58
- async addJob(data) {
59
- const job = { id: helpers_1.uuid, data };
43
+ async defaultHandler(job) {
44
+ if (typeof job.data === "function") {
45
+ await job.data();
46
+ }
47
+ }
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
+ };
60
55
  const jobs = await this.adapter.loadJobs();
61
56
  jobs.push(job);
62
57
  await this.adapter.saveJobs(jobs);
63
- this.processNext();
58
+ if (!this.processing)
59
+ this.processNext();
64
60
  }
65
61
  async processNext() {
66
- if (this.processing)
62
+ if (this.processing || this.stopped)
67
63
  return;
68
64
  this.processing = true;
69
- const jobs = await this.adapter.loadJobs();
70
- if (jobs.length === 0) {
71
- this.processing = false;
72
- return;
73
- }
74
- const job = jobs.shift();
75
- if (job) {
76
- try {
77
- await this.jobHandler(job);
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;
78
71
  }
79
- catch (error) {
80
- console.error(`Error processing job ${job.id}:`, error);
81
- jobs.unshift(job);
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));
82
76
  }
77
+ nextJob.status = "running";
83
78
  await this.adapter.saveJobs(jobs);
84
- this.processing = false;
85
- this.processNext();
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));
86
90
  }
91
+ this.processing = false;
87
92
  }
88
- }
89
- class QueueManager {
90
- constructor(adapter) {
91
- this.adapter = adapter;
93
+ async onDone(cb) {
94
+ this.on("done", cb);
92
95
  }
93
- static getInstance(adapter) {
94
- if (!QueueManager.instance) {
95
- QueueManager.instance = new QueueManager(adapter);
96
- }
97
- return QueueManager.instance;
96
+ async onFailed(cb) {
97
+ this.on("failed", cb);
98
98
  }
99
- createQueue(jobHandler) {
100
- return new SimpleQueue(this.adapter, jobHandler);
99
+ async getJobs() {
100
+ return this.adapter.loadJobs();
101
+ }
102
+ async stop() {
103
+ this.stopped = true;
101
104
  }
102
105
  }
106
+ 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
+ };
103
113
  exports.QueueManager = QueueManager;
114
+ exports.QueueManager = QueueManager = __decorate([
115
+ decorators_1.AppService
116
+ ], QueueManager);
@@ -36,22 +36,22 @@ describe("FileQueueAdapter", () => {
36
36
  });
37
37
  describe("QueueManager and SimpleQueue", () => {
38
38
  let adapter;
39
- let queueManager;
39
+ // let queueManager: QueueManager;
40
40
  let handler;
41
- beforeEach(() => {
42
- jest.clearAllMocks();
43
- adapter = new queue_1.FileQueueAdapter("testqueue");
44
- queueManager = queue_1.QueueManager.getInstance(adapter);
45
- handler = jest.fn().mockResolvedValue(undefined);
46
- fs_1.promises.readFile.mockResolvedValue("[]");
47
- fs_1.promises.writeFile.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_1.promises.readFile).toHaveBeenCalled();
53
- expect(fs_1.promises.writeFile).toHaveBeenCalled();
54
- });
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
55
  // it("should process jobs using handler", async () => {
56
56
  // (fs.readFile as jest.Mock)
57
57
  // .mockResolvedValueOnce("[]")
@@ -72,8 +72,8 @@ describe("QueueManager and SimpleQueue", () => {
72
72
  // expect(handler).toHaveBeenCalled();
73
73
  // expect(fs.writeFile).toHaveBeenCalledTimes(2);
74
74
  // });
75
- it("QueueManager should be singleton", () => {
76
- const another = queue_1.QueueManager.getInstance(adapter);
77
- expect(another).toBe(queueManager);
78
- });
75
+ // it("QueueManager should be singleton", () => {
76
+ // const another = QueueManager.getInstance(adapter);
77
+ // expect(another).toBe(queueManager);
78
+ // });
79
79
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avleon/core",
3
- "version": "0.0.42-rc0.1",
3
+ "version": "0.0.43",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "keywords": [