@avleon/core 0.0.28 → 0.0.29

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.
Files changed (42) hide show
  1. package/dist/application.js +1 -1
  2. package/dist/cache.d.ts +1 -1
  3. package/dist/cache.js +2 -2
  4. package/dist/collection.d.ts +25 -32
  5. package/dist/collection.js +50 -6
  6. package/dist/collection.test.d.ts +1 -0
  7. package/dist/collection.test.js +59 -0
  8. package/dist/config.d.ts +2 -0
  9. package/dist/config.js +30 -5
  10. package/dist/config.test.d.ts +1 -0
  11. package/dist/config.test.js +40 -0
  12. package/dist/controller.js +2 -2
  13. package/dist/environment-variables.js +42 -5
  14. package/dist/event-dispatcher.d.ts +23 -0
  15. package/dist/event-dispatcher.js +102 -0
  16. package/dist/event-subscriber.d.ts +15 -0
  17. package/dist/event-subscriber.js +96 -0
  18. package/dist/exceptions/http-exceptions.js +1 -1
  19. package/dist/exceptions/index.d.ts +1 -1
  20. package/dist/exceptions/system-exception.js +3 -1
  21. package/dist/file-storage.js +1 -1
  22. package/dist/helpers.js +1 -1
  23. package/dist/icore.d.ts +5 -0
  24. package/dist/icore.js +53 -18
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +6 -1
  27. package/dist/utils/index.d.ts +2 -2
  28. package/dist/utils/optional-require.js +2 -2
  29. package/dist/validation.d.ts +1 -1
  30. package/dist/websocket.d.ts +7 -0
  31. package/dist/websocket.js +20 -0
  32. package/dist/websocket.test.d.ts +0 -0
  33. package/dist/websocket.test.js +1 -0
  34. package/package.json +3 -1
  35. package/src/config.test.ts +2 -2
  36. package/src/config.ts +2 -2
  37. package/src/event-dispatcher.ts +100 -0
  38. package/src/event-subscriber.ts +79 -0
  39. package/src/icore.ts +1106 -1060
  40. package/src/index.ts +3 -1
  41. package/src/middleware.ts +6 -4
  42. package/src/websocket.ts +47 -0
@@ -0,0 +1,96 @@
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
+ exports.EventSubscriberRegistry = void 0;
13
+ exports.Private = Private;
14
+ exports.isPrivate = isPrivate;
15
+ exports.getPrivateChannelResolver = getPrivateChannelResolver;
16
+ exports.registerSocketSubscriber = registerSocketSubscriber;
17
+ exports.getSocketSubscribers = getSocketSubscribers;
18
+ exports.Subscribe = Subscribe;
19
+ exports.PrivateSubscribe = PrivateSubscribe;
20
+ const typedi_1 = require("typedi");
21
+ const event_dispatcher_1 = require("./event-dispatcher");
22
+ require("reflect-metadata");
23
+ const PRIVATE_META_KEY = 'avleon:private';
24
+ function Private(channelResolver) {
25
+ return function (target, propertyKey) {
26
+ Reflect.defineMetadata(PRIVATE_META_KEY, true, target, propertyKey);
27
+ Reflect.defineMetadata(`private:channel:${propertyKey}`, channelResolver, target);
28
+ };
29
+ }
30
+ function isPrivate(target, propertyKey) {
31
+ return Reflect.getMetadata(PRIVATE_META_KEY, target, propertyKey) || false;
32
+ }
33
+ function getPrivateChannelResolver(target, propertyKey) {
34
+ return Reflect.getMetadata(`private:channel:${propertyKey}`, target);
35
+ }
36
+ const socketSubscriberClasses = new Set();
37
+ function registerSocketSubscriber(target) {
38
+ socketSubscriberClasses.add(target);
39
+ }
40
+ function getSocketSubscribers() {
41
+ return Array.from(socketSubscriberClasses);
42
+ }
43
+ // decorators/Subscribe.ts
44
+ function Subscribe(event) {
45
+ return (target, propertyKey) => {
46
+ Reflect.defineMetadata('socket:event', event, target, propertyKey);
47
+ registerSocketSubscriber(target.constructor);
48
+ };
49
+ }
50
+ // decorators/Private.ts
51
+ function PrivateSubscribe(resolver) {
52
+ return function (target, propertyKey) {
53
+ Reflect.defineMetadata('avleon:private', true, target, propertyKey);
54
+ Reflect.defineMetadata(`private:channel:${propertyKey}`, resolver, target);
55
+ };
56
+ }
57
+ let EventSubscriberRegistry = class EventSubscriberRegistry {
58
+ constructor(socketContext) {
59
+ this.socketContext = socketContext;
60
+ }
61
+ register(socket) {
62
+ const subscriberClasses = getSocketSubscribers();
63
+ for (const SubscriberClass of subscriberClasses) {
64
+ const instance = typedi_1.Container.get(SubscriberClass);
65
+ const prototype = Object.getPrototypeOf(instance);
66
+ const methodNames = Object.getOwnPropertyNames(prototype)
67
+ .filter(name => typeof prototype[name] === 'function');
68
+ for (const methodName of methodNames) {
69
+ const event = Reflect.getMetadata('socket:event', prototype, methodName);
70
+ const isPrivateListener = isPrivate(instance, methodName);
71
+ const channelResolver = getPrivateChannelResolver(instance, methodName);
72
+ //const event = Reflect.getMetadata(`subscribe:event:${method}`, instance);
73
+ if (event) {
74
+ const channel = isPrivateListener && channelResolver ? channelResolver(socket) : event;
75
+ console.log('Channel', channel);
76
+ socket.on(channel, (payload) => {
77
+ this.socketContext.run(socket, async () => {
78
+ if (isPrivateListener) {
79
+ const user = socket.data.user;
80
+ if (!user)
81
+ return; // unauthorized
82
+ // optionally add more validation here
83
+ }
84
+ await instance[methodName](payload, socket.data);
85
+ });
86
+ });
87
+ }
88
+ }
89
+ }
90
+ }
91
+ };
92
+ exports.EventSubscriberRegistry = EventSubscriberRegistry;
93
+ exports.EventSubscriberRegistry = EventSubscriberRegistry = __decorate([
94
+ (0, typedi_1.Service)(),
95
+ __metadata("design:paramtypes", [event_dispatcher_1.SocketContextService])
96
+ ], EventSubscriberRegistry);
@@ -79,5 +79,5 @@ exports.HttpExceptions = {
79
79
  badRequest: (message = "") => new BadRequestException(message),
80
80
  unauthorized: (message = "") => new UnauthorizedException(message),
81
81
  forbidden: (message = "") => new ForbiddenException(message),
82
- internalError: (message = "") => new InternalErrorException(message)
82
+ internalError: (message = "") => new InternalErrorException(message),
83
83
  };
@@ -1 +1 @@
1
- export * from './http-exceptions';
1
+ export * from "./http-exceptions";
@@ -11,7 +11,9 @@ class DuplicateRouteException extends Error {
11
11
  constructor(params) {
12
12
  let sameController = params.controller == params.inverseController;
13
13
  let message = `Duplicate route found for method ${params.method.toUpperCase()}:${params.path == "" ? "'/'" : params.path} `;
14
- message += sameController ? `in ${params.controller}` : `both in ${params.controller} and ${params.inverseController}`;
14
+ message += sameController
15
+ ? `in ${params.controller}`
16
+ : `both in ${params.controller} and ${params.inverseController}`;
15
17
  super(message);
16
18
  }
17
19
  }
@@ -90,7 +90,7 @@ let FileStorage = class FileStorage {
90
90
  overwrite: options && options.overwrite ? options.overwrite : true,
91
91
  };
92
92
  for (let f of files) {
93
- let uploadPath = `public`;
93
+ let uploadPath = "public";
94
94
  if (options === null || options === void 0 ? void 0 : options.to) {
95
95
  uploadPath = `public/${options.to}`;
96
96
  }
package/dist/helpers.js CHANGED
@@ -40,7 +40,7 @@ function inject(cls) {
40
40
  return container_1.default.get(cls);
41
41
  }
42
42
  catch (error) {
43
- throw new system_exception_1.SystemUseError(`Not a project class. Maybe you wanna register it first.`);
43
+ throw new system_exception_1.SystemUseError("Not a project class. Maybe you wanna register it first.");
44
44
  }
45
45
  }
46
46
  function isConstructor(func) {
package/dist/icore.d.ts CHANGED
@@ -15,6 +15,7 @@ import { IConfig } from "./config";
15
15
  import { FastifyCorsOptions } from "@fastify/cors";
16
16
  import { FastifyMultipartOptions } from "@fastify/multipart";
17
17
  import { MultipartFile } from "./multipart";
18
+ import { ServerOptions } from 'socket.io';
18
19
  export type FuncRoute = {
19
20
  handler: any;
20
21
  middlewares?: any[];
@@ -124,6 +125,7 @@ export declare class AvleonApplication {
124
125
  private middlewares;
125
126
  private rMap;
126
127
  private hasSwagger;
128
+ private _hasWebsocket;
127
129
  private globalSwaggerOptions;
128
130
  private dataSourceOptions?;
129
131
  private controllers;
@@ -142,6 +144,8 @@ export declare class AvleonApplication {
142
144
  private initSwagger;
143
145
  private _isConfigClass;
144
146
  useCors<T = FastifyCorsOptions>(corsOptions?: ConfigInput<T>): void;
147
+ useWebSocket<T = Partial<ServerOptions>>(socketOptions: ConfigInput<T>): void;
148
+ private _initWebSocket;
145
149
  useOpenApi<T = OpenApiUiOptions>(configOrClass: OpenApiConfigInput<T>): void;
146
150
  useMultipart<T extends MultipartOptions>(options: ConfigInput<T>): void;
147
151
  useDataSource<T extends DataSourceOptions>(options: ConfigInput<T>): void;
@@ -197,6 +201,7 @@ export declare class AvleonApplication {
197
201
  };
198
202
  private _mapFeatures;
199
203
  initializeDatabase(): Promise<void>;
204
+ handleSocket(socket: any): void;
200
205
  run(port?: number, fn?: CallableFunction): Promise<void>;
201
206
  getTestApp(buildOptions?: any): TestApplication;
202
207
  }
package/dist/icore.js CHANGED
@@ -58,10 +58,13 @@ const cors_1 = __importDefault(require("@fastify/cors"));
58
58
  const multipart_1 = __importDefault(require("@fastify/multipart"));
59
59
  const validation_1 = require("./validation");
60
60
  const utils_1 = require("./utils");
61
+ const socket_io_1 = require("socket.io");
62
+ const event_subscriber_1 = require("./event-subscriber");
61
63
  const isTsNode = process.env.TS_NODE_DEV ||
62
64
  process.env.TS_NODE_PROJECT ||
63
65
  process[Symbol.for("ts-node.register.instance")];
64
66
  const controllerDir = path_1.default.join(process.cwd(), isTsNode ? "./src/controllers" : "./dist/cotrollers");
67
+ const subscriberRegistry = typedi_1.default.get(event_subscriber_1.EventSubscriberRegistry);
65
68
  // InternalApplication
66
69
  class AvleonApplication {
67
70
  constructor() {
@@ -71,6 +74,7 @@ class AvleonApplication {
71
74
  this.middlewares = new Map();
72
75
  this.rMap = new Map();
73
76
  this.hasSwagger = false;
77
+ this._hasWebsocket = false;
74
78
  this.globalSwaggerOptions = {};
75
79
  this.dataSourceOptions = undefined;
76
80
  this.controllers = [];
@@ -78,7 +82,7 @@ class AvleonApplication {
78
82
  this.dataSource = undefined;
79
83
  this.isMapFeatures = false;
80
84
  this.registerControllerAuto = false;
81
- this.registerControllerPath = './src';
85
+ this.registerControllerPath = "./src";
82
86
  this.metaCache = new Map();
83
87
  this.app = (0, fastify_1.default)();
84
88
  this.appConfig = new config_1.AppConfig();
@@ -122,7 +126,7 @@ class AvleonApplication {
122
126
  if (options.ui && options.ui == "scalar") {
123
127
  const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
124
128
  failOnMissing: true,
125
- customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
129
+ customMessage: "Install \"@scalar/fastify-api-reference\" to enable API docs.\n\n npm install @scalar/fastify-api-reference",
126
130
  });
127
131
  await this.app.register(scalarPlugin, {
128
132
  routePrefix: rPrefix,
@@ -141,7 +145,7 @@ class AvleonApplication {
141
145
  else {
142
146
  const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
143
147
  failOnMissing: true,
144
- customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
148
+ customMessage: "Install \"@fastify/swagger-ui\" to enable API docs.\n\n npm install @fastify/swagger-ui",
145
149
  });
146
150
  await this.app.register(fastifySwaggerUi, {
147
151
  logo: logo ? logo : null,
@@ -152,8 +156,8 @@ class AvleonApplication {
152
156
  }
153
157
  _isConfigClass(input) {
154
158
  var _a;
155
- return (typeof input === 'function' &&
156
- typeof input.prototype === 'object' &&
159
+ return (typeof input === "function" &&
160
+ typeof input.prototype === "object" &&
157
161
  ((_a = input.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === input);
158
162
  }
159
163
  useCors(corsOptions) {
@@ -168,6 +172,19 @@ class AvleonApplication {
168
172
  }
169
173
  this.app.register(cors_1.default, coptions);
170
174
  }
175
+ useWebSocket(socketOptions) {
176
+ this._hasWebsocket = true;
177
+ this._initWebSocket(socketOptions);
178
+ }
179
+ async _initWebSocket(options) {
180
+ const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
181
+ failOnMissing: true,
182
+ customMessage: "Install \"fastify-socket.io\" to enable socket.io.\n\n run pnpm install fastify-socket.io",
183
+ });
184
+ await this.app.register(fsSocketIO, options);
185
+ const socketIO = await this.app.io;
186
+ typedi_1.default.set(socket_io_1.Server, socketIO);
187
+ }
171
188
  useOpenApi(configOrClass) {
172
189
  let openApiConfig;
173
190
  if (this._isConfigClass(configOrClass)) {
@@ -211,8 +228,7 @@ class AvleonApplication {
211
228
  this.dataSource = datasource;
212
229
  typedi_1.default.set("idatasource", datasource);
213
230
  }
214
- _useCache(options) {
215
- }
231
+ _useCache(options) { }
216
232
  useMiddlewares(mclasses) {
217
233
  for (const mclass of mclasses) {
218
234
  const cls = typedi_1.default.get(mclass);
@@ -317,7 +333,7 @@ class AvleonApplication {
317
333
  const args = await this._mapArgs(reqClone, allMeta);
318
334
  for (let paramMeta of allMeta.params) {
319
335
  if (paramMeta.required) {
320
- (0, validation_1.validateOrThrow)({ [paramMeta.key]: args[paramMeta.index] }, { [paramMeta.key]: { type: paramMeta.dataType } }, { location: 'param' });
336
+ (0, validation_1.validateOrThrow)({ [paramMeta.key]: args[paramMeta.index] }, { [paramMeta.key]: { type: paramMeta.dataType } }, { location: "param" });
321
337
  }
322
338
  }
323
339
  for (let queryMeta of allMeta.query) {
@@ -333,7 +349,7 @@ class AvleonApplication {
333
349
  }
334
350
  }
335
351
  if (queryMeta.required) {
336
- (0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: 'queryparam' });
352
+ (0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: "queryparam" });
337
353
  }
338
354
  }
339
355
  for (let bodyMeta of allMeta.body) {
@@ -443,7 +459,7 @@ class AvleonApplication {
443
459
  process.env.TS_NODE_PROJECT ||
444
460
  process[Symbol.for("ts-node.register.instance")];
445
461
  const controllerDir = path_1.default.join(process.cwd(), this.registerControllerPath);
446
- return isTsNode ? controllerDir : controllerDir.replace('src', 'dist');
462
+ return isTsNode ? controllerDir : controllerDir.replace("src", "dist");
447
463
  }
448
464
  async autoControllers(controllersPath) {
449
465
  const conDir = this._resolveControllerDir(controllersPath);
@@ -457,8 +473,8 @@ class AvleonApplication {
457
473
  const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
458
474
  for (const exported of Object.values(module)) {
459
475
  if (typeof exported === "function" && (0, container_1.isApiController)(exported)) {
460
- console.log('adding', exported.name);
461
- if (!this.controllers.some(con => exported.name == con.name)) {
476
+ console.log("adding", exported.name);
477
+ if (!this.controllers.some((con) => exported.name == con.name)) {
462
478
  this.controllers.push(exported);
463
479
  }
464
480
  //this.buildController(exported);
@@ -470,7 +486,7 @@ class AvleonApplication {
470
486
  useControllers(controllers) {
471
487
  if (Array.isArray(controllers)) {
472
488
  this.controllers = controllers;
473
- controllers.forEach(controller => {
489
+ controllers.forEach((controller) => {
474
490
  if (!this.controllers.includes(controller)) {
475
491
  this.controllers.push(controller);
476
492
  }
@@ -593,14 +609,17 @@ class AvleonApplication {
593
609
  return this._routeHandler(path, "DELETE", fn);
594
610
  }
595
611
  _mapFeatures() {
596
- const features = typedi_1.default.get('features');
597
- console.log('Features', features);
612
+ const features = typedi_1.default.get("features");
613
+ console.log("Features", features);
598
614
  }
599
615
  async initializeDatabase() {
600
616
  if (this.dataSourceOptions && this.dataSource) {
601
617
  await this.dataSource.initialize();
602
618
  }
603
619
  }
620
+ handleSocket(socket) {
621
+ subscriberRegistry.register(socket);
622
+ }
604
623
  async run(port = 4000, fn) {
605
624
  if (this.alreadyRun)
606
625
  throw new system_exception_1.SystemUseError("App already running");
@@ -631,7 +650,8 @@ class AvleonApplication {
631
650
  });
632
651
  this.app.setErrorHandler(async (error, req, res) => {
633
652
  const handledErr = this._handleError(error);
634
- if (error instanceof exceptions_1.ValidationErrorException || error instanceof exceptions_1.BadRequestException) {
653
+ if (error instanceof exceptions_1.ValidationErrorException ||
654
+ error instanceof exceptions_1.BadRequestException) {
635
655
  return res.status(handledErr.code).send({
636
656
  code: handledErr.code,
637
657
  error: handledErr.error,
@@ -641,6 +661,21 @@ class AvleonApplication {
641
661
  return res.status(handledErr.code).send(handledErr);
642
662
  });
643
663
  await this.app.ready();
664
+ if (this._hasWebsocket) {
665
+ await this.app.io.on('connection', this.handleSocket);
666
+ await this.app.io.use((socket, next) => {
667
+ const token = socket.handshake.auth.token;
668
+ try {
669
+ console.log('token', token);
670
+ const user = { id: 1, name: "tareq" };
671
+ socket.data.user = user; // this powers @AuthUser()
672
+ next();
673
+ }
674
+ catch (_a) {
675
+ next(new Error('Unauthorized'));
676
+ }
677
+ });
678
+ }
644
679
  await this.app.listen({ port });
645
680
  console.log(`Application running on http://127.0.0.1:${port}`);
646
681
  }
@@ -683,7 +718,7 @@ class AvleonApplication {
683
718
  delete: async (url, options) => this.app.inject({ method: "DELETE", url, ...options }),
684
719
  options: async (url, options) => this.app.inject({ method: "OPTIONS", url, ...options }),
685
720
  getController: (controller, deps = []) => {
686
- const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
721
+ const paramTypes = Reflect.getMetadata("design:paramtypes", controller) || [];
687
722
  deps.forEach((dep, i) => {
688
723
  typedi_1.default.set(paramTypes[i], dep);
689
724
  });
@@ -704,7 +739,7 @@ class AvleonTest {
704
739
  process.env.NODE_ENV = "test";
705
740
  }
706
741
  static getController(controller, deps = []) {
707
- const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
742
+ const paramTypes = Reflect.getMetadata("design:paramtypes", controller) || [];
708
743
  deps.forEach((dep, i) => {
709
744
  typedi_1.default.set(paramTypes[i], dep);
710
745
  });
package/dist/index.d.ts CHANGED
@@ -24,5 +24,7 @@ export * from "./utils/hash";
24
24
  export * from "./multipart";
25
25
  export * from "./file-storage";
26
26
  export * from "./logger";
27
+ export * from "./event-dispatcher";
28
+ export { Subscribe, Private, PrivateSubscribe } from "./event-subscriber";
27
29
  export declare const GetSchema: typeof sw.generateSwaggerSchema;
28
30
  export { default as Container } from "./container";
package/dist/index.js CHANGED
@@ -39,7 +39,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
39
39
  return (mod && mod.__esModule) ? mod : { "default": mod };
40
40
  };
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
- exports.Container = exports.GetSchema = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
42
+ exports.Container = exports.GetSchema = exports.PrivateSubscribe = exports.Private = exports.Subscribe = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
43
43
  /**
44
44
  * @copyright 2024
45
45
  * @author Tareq Hossain
@@ -70,6 +70,11 @@ __exportStar(require("./utils/hash"), exports);
70
70
  __exportStar(require("./multipart"), exports);
71
71
  __exportStar(require("./file-storage"), exports);
72
72
  __exportStar(require("./logger"), exports);
73
+ __exportStar(require("./event-dispatcher"), exports);
74
+ var event_subscriber_1 = require("./event-subscriber");
75
+ Object.defineProperty(exports, "Subscribe", { enumerable: true, get: function () { return event_subscriber_1.Subscribe; } });
76
+ Object.defineProperty(exports, "Private", { enumerable: true, get: function () { return event_subscriber_1.Private; } });
77
+ Object.defineProperty(exports, "PrivateSubscribe", { enumerable: true, get: function () { return event_subscriber_1.PrivateSubscribe; } });
73
78
  exports.GetSchema = sw.generateSwaggerSchema;
74
79
  var container_1 = require("./container");
75
80
  Object.defineProperty(exports, "Container", { enumerable: true, get: function () { return __importDefault(container_1).default; } });
@@ -1,2 +1,2 @@
1
- export * from './hash';
2
- export * from './optional-require';
1
+ export * from "./hash";
2
+ export * from "./optional-require";
@@ -37,7 +37,6 @@ exports.optionalRequire = optionalRequire;
37
37
  exports.optionalImport = optionalImport;
38
38
  function optionalRequire(moduleName, options = {}) {
39
39
  try {
40
- // eslint-disable-next-line @typescript-eslint/no-var-requires
41
40
  return require(moduleName);
42
41
  }
43
42
  catch (err) {
@@ -57,7 +56,8 @@ async function optionalImport(moduleName, options = {}) {
57
56
  return mod;
58
57
  }
59
58
  catch (err) {
60
- if ((err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND") &&
59
+ if ((err.code === "ERR_MODULE_NOT_FOUND" ||
60
+ err.code === "MODULE_NOT_FOUND") &&
61
61
  err.message.includes(moduleName)) {
62
62
  if (options.failOnMissing) {
63
63
  throw new Error(options.customMessage ||
@@ -26,7 +26,7 @@ export type ValidationProps = {
26
26
  [key: string]: ValidationRule;
27
27
  };
28
28
  export type ValidateOptons = {
29
- location?: 'header' | 'queryparam' | 'param' | 'body' | 'custom';
29
+ location?: "header" | "queryparam" | "param" | "body" | "custom";
30
30
  };
31
31
  export declare function validateOrThrow<T extends {}>(obj: T, rules: ValidationProps, options?: ValidateOptons): any;
32
32
  export {};
@@ -0,0 +1,7 @@
1
+ export declare class AvleonSocketIo {
2
+ private io?;
3
+ sendToAll(): void;
4
+ sendOnly(): void;
5
+ sendRoom(): void;
6
+ receive(channel: string): void;
7
+ }
@@ -0,0 +1,20 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.AvleonSocketIo = void 0;
10
+ const typedi_1 = require("typedi");
11
+ let AvleonSocketIo = class AvleonSocketIo {
12
+ sendToAll() { }
13
+ sendOnly() { }
14
+ sendRoom() { }
15
+ receive(channel) { }
16
+ };
17
+ exports.AvleonSocketIo = AvleonSocketIo;
18
+ exports.AvleonSocketIo = AvleonSocketIo = __decorate([
19
+ (0, typedi_1.Service)()
20
+ ], AvleonSocketIo);
File without changes
@@ -0,0 +1 @@
1
+ "use strict";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avleon/core",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "scripts": {
@@ -56,10 +56,12 @@
56
56
  "cross-env": "^7.0.3",
57
57
  "dotenv": "^16.4.7",
58
58
  "fastify": "^5.1.0",
59
+ "fastify-socket.io": "^5.1.0",
59
60
  "highlight.js": "^11.9.0",
60
61
  "pino": "^9.6.0",
61
62
  "pino-pretty": "^13.0.0",
62
63
  "reflect-metadata": "^0.2.2",
64
+ "socket.io": "^4.8.1",
63
65
  "typedi": "^0.10.0"
64
66
  },
65
67
  "peerDependencies": {
@@ -1,5 +1,5 @@
1
1
  import "reflect-metadata";
2
- import { Config, CreateConfig, GetConfig, IConfig } from "./config";
2
+ import { AppConfig, CreateConfig, GetConfig, IConfig } from "./config";
3
3
  import { Environment } from "./environment-variables";
4
4
 
5
5
  type AppConfig = { name: string; os: string };
@@ -7,7 +7,7 @@ type AppConfig = { name: string; os: string };
7
7
  describe("Config", () => {
8
8
  describe("class", () => {
9
9
  it("should be call by get config", () => {
10
- @Config
10
+ @AppConfig
11
11
  class MyConfig {
12
12
  config(env: Environment) {
13
13
  return {
package/src/config.ts CHANGED
@@ -12,11 +12,11 @@ export interface IConfig<T = any> {
12
12
  config(env: Environment): T;
13
13
  }
14
14
 
15
- export function Config<T extends IConfig>(target: Constructable<T>) {
15
+ export function AppConfig<T extends IConfig>(target: Constructable<T>) {
16
16
  Container.set({ id: target, type: target });
17
17
  }
18
18
 
19
- export class AppConfig {
19
+ export class AvleonConfig {
20
20
  get<T extends IConfig<R>, R>(configClass: Constructable<T>): R {
21
21
  const instance = Container.get(configClass);
22
22
  if (!instance) {
@@ -0,0 +1,100 @@
1
+ import { Service, Container } from "typedi";
2
+ import { Server as SocketIOServer, Socket, ServerOptions } from "socket.io";
3
+ import { AsyncLocalStorage } from "node:async_hooks";
4
+
5
+
6
+ @Service()
7
+ export class SocketContextService {
8
+ private readonly storage = new AsyncLocalStorage<{ socket: Socket }>();
9
+
10
+ run(socket: Socket, fn: () => void | Promise<void>) {
11
+ this.storage.run({ socket }, fn);
12
+ }
13
+
14
+ getSocket(): Socket | undefined {
15
+ return this.storage.getStore()?.socket;
16
+ }
17
+ }
18
+
19
+ export type DispatchOptions = {
20
+ room?: string;
21
+ broadcast?: boolean;
22
+ transports?: ("socket" | "kafka" | "rabbitmq")[];
23
+ retry?: number;
24
+ retryDelay?: number;
25
+ };
26
+
27
+ export function sleep(ms: number) {
28
+ return new Promise((resolve) => setTimeout(resolve, ms));
29
+ }
30
+
31
+ @Service()
32
+ export class EventDispatcher {
33
+ constructor(private readonly _context: SocketContextService) {}
34
+
35
+ async dispatch<T = any>(
36
+ event: string,
37
+ data: T,
38
+ options: DispatchOptions = {}
39
+ ) {
40
+ const retryCount = options.retry ?? 0;
41
+ const delay = options.retryDelay ?? 300;
42
+
43
+ for (let attempt = 0; attempt <= retryCount; attempt++) {
44
+ try {
45
+ await this.dispatchToTransports(event, data, options);
46
+ break;
47
+ } catch (err) {
48
+ if (attempt === retryCount) throw err;
49
+ await sleep(delay * (attempt + 1));
50
+ }
51
+ }
52
+ }
53
+
54
+ private async dispatchToTransports(event: string, data: any, options: DispatchOptions) {
55
+ const transports = options.transports ?? ["socket"];
56
+
57
+ for (const transport of transports) {
58
+ if (transport === "socket") {
59
+ const io = Container.get(SocketIOServer);
60
+
61
+ //console.log('SOckert', Container.get(SocketContextService));
62
+
63
+ const context = Container.get(SocketContextService);
64
+ const socket = context.getSocket();
65
+
66
+
67
+
68
+ if (options.broadcast && socket) {
69
+ if (options.room) {
70
+ socket.broadcast.to(options.room).emit(event, data);
71
+ } else {
72
+ socket.broadcast.emit(event, data);
73
+ }
74
+ } else {
75
+ if (options.room) {
76
+ io.to(options.room).emit(event, data);
77
+ } else {
78
+ io.emit(event, data);
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+
87
+ export function Dispatch(event: string, options?: Omit<DispatchOptions, "transports"> & { transports?: DispatchOptions["transports"] }) {
88
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
89
+ const original = descriptor.value;
90
+
91
+ descriptor.value = async function (...args: any[]) {
92
+ const result = await original.apply(this, args);
93
+
94
+ const dispatcher = Container.get(EventDispatcher);
95
+ await dispatcher.dispatch(event, result, options);
96
+
97
+ return result;
98
+ };
99
+ };
100
+ }