@avleon/core 0.0.40 → 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.
Files changed (52) hide show
  1. package/README.md +37 -5
  2. package/dist/cache.test.d.ts +1 -0
  3. package/dist/cache.test.js +36 -0
  4. package/dist/controller.d.ts +2 -0
  5. package/dist/controller.js +13 -0
  6. package/dist/controller.test.d.ts +1 -0
  7. package/dist/controller.test.js +111 -0
  8. package/dist/environment-variables.test.d.ts +1 -0
  9. package/dist/environment-variables.test.js +70 -0
  10. package/dist/exceptions/http-exceptions.d.ts +1 -0
  11. package/dist/exceptions/http-exceptions.js +3 -1
  12. package/dist/file-storage.d.ts +44 -9
  13. package/dist/file-storage.js +209 -59
  14. package/dist/file-storage.test.d.ts +1 -0
  15. package/dist/file-storage.test.js +104 -0
  16. package/dist/helpers.test.d.ts +1 -0
  17. package/dist/helpers.test.js +95 -0
  18. package/dist/icore.d.ts +8 -5
  19. package/dist/icore.js +225 -81
  20. package/dist/icore.test.d.ts +1 -0
  21. package/dist/icore.test.js +14 -0
  22. package/dist/index.d.ts +24 -0
  23. package/dist/index.js +8 -1
  24. package/dist/kenx-provider.test.d.ts +1 -0
  25. package/dist/kenx-provider.test.js +36 -0
  26. package/dist/logger.test.d.ts +1 -0
  27. package/dist/logger.test.js +42 -0
  28. package/dist/middleware.test.d.ts +1 -0
  29. package/dist/middleware.test.js +121 -0
  30. package/dist/multipart.test.d.ts +1 -0
  31. package/dist/multipart.test.js +87 -0
  32. package/dist/openapi.test.d.ts +1 -0
  33. package/dist/openapi.test.js +111 -0
  34. package/dist/params.test.d.ts +1 -0
  35. package/dist/params.test.js +83 -0
  36. package/dist/queue.d.ts +16 -14
  37. package/dist/queue.js +76 -63
  38. package/dist/queue.test.d.ts +1 -0
  39. package/dist/queue.test.js +79 -0
  40. package/dist/route-methods.test.d.ts +1 -0
  41. package/dist/route-methods.test.js +129 -0
  42. package/dist/swagger-schema.d.ts +42 -0
  43. package/dist/swagger-schema.js +331 -58
  44. package/dist/swagger-schema.test.d.ts +1 -0
  45. package/dist/swagger-schema.test.js +105 -0
  46. package/dist/validation.d.ts +7 -0
  47. package/dist/validation.js +2 -0
  48. package/dist/validation.test.d.ts +1 -0
  49. package/dist/validation.test.js +61 -0
  50. package/dist/websocket.test.d.ts +1 -0
  51. package/dist/websocket.test.js +27 -0
  52. package/package.json +11 -9
package/dist/icore.js CHANGED
@@ -59,9 +59,12 @@ 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");
66
+ const controller_1 = require("./controller");
67
+ const mime_1 = __importDefault(require("mime"));
65
68
  const isTsNode = process.env.TS_NODE_DEV ||
66
69
  process.env.TS_NODE_PROJECT ||
67
70
  process[Symbol.for("ts-node.register.instance")];
@@ -112,6 +115,17 @@ class AvleonApplication {
112
115
  }
113
116
  return AvleonApplication.instance;
114
117
  }
118
+ useLogger(corsOptions) {
119
+ let coptions = {};
120
+ if (corsOptions) {
121
+ if (this._isConfigClass(corsOptions)) {
122
+ coptions = this.appConfig.get(corsOptions);
123
+ }
124
+ else {
125
+ coptions = corsOptions;
126
+ }
127
+ }
128
+ }
115
129
  isDevelopment() {
116
130
  const env = container_1.default.get(environment_variables_1.Environment);
117
131
  return env.get("NODE_ENV") == "development";
@@ -128,7 +142,7 @@ class AvleonApplication {
128
142
  if (options.ui && options.ui == "scalar") {
129
143
  const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
130
144
  failOnMissing: true,
131
- customMessage: "Install \"@scalar/fastify-api-reference\" to enable API docs.\n\n npm install @scalar/fastify-api-reference",
145
+ customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
132
146
  });
133
147
  await this.app.register(scalarPlugin, {
134
148
  routePrefix: rPrefix,
@@ -147,7 +161,7 @@ class AvleonApplication {
147
161
  else {
148
162
  const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
149
163
  failOnMissing: true,
150
- customMessage: "Install \"@fastify/swagger-ui\" to enable API docs.\n\n npm install @fastify/swagger-ui",
164
+ customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
151
165
  });
152
166
  await this.app.register(fastifySwaggerUi, {
153
167
  logo: logo ? logo : null,
@@ -178,14 +192,16 @@ class AvleonApplication {
178
192
  this._hasWebsocket = true;
179
193
  this._initWebSocket(socketOptions);
180
194
  }
195
+ useSocketIO(socketOptions) {
196
+ this._hasWebsocket = true;
197
+ this._initWebSocket(socketOptions);
198
+ }
181
199
  async _initWebSocket(options) {
182
200
  const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
183
201
  failOnMissing: true,
184
- customMessage: "Install \"fastify-socket.io\" to enable socket.io.\n\n run pnpm install fastify-socket.io",
202
+ customMessage: 'Install "fastify-socket.io" to enable socket.io.\n\n run pnpm install fastify-socket.io',
185
203
  });
186
- await this.app.register(fsSocketIO, options);
187
- const socketIO = await this.app.io;
188
- typedi_1.default.set(socket_io_1.Server, socketIO);
204
+ this.app.register(fsSocketIO, options);
189
205
  }
190
206
  useOpenApi(configOrClass) {
191
207
  let openApiConfig;
@@ -209,8 +225,8 @@ class AvleonApplication {
209
225
  if (multipartOptions) {
210
226
  this.multipartOptions = multipartOptions;
211
227
  this.app.register(multipart_1.default, {
212
- ...this.multipartOptions,
213
228
  attachFieldsToBody: true,
229
+ ...this.multipartOptions,
214
230
  });
215
231
  }
216
232
  }
@@ -281,6 +297,7 @@ class AvleonApplication {
281
297
  * @returns void
282
298
  */
283
299
  async buildController(controller) {
300
+ var _a, _b;
284
301
  const ctrl = typedi_1.default.get(controller);
285
302
  const controllerMeta = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, ctrl.constructor);
286
303
  if (!controllerMeta)
@@ -318,10 +335,39 @@ class AvleonApplication {
318
335
  if (!swaggerMeta.body && bodySchema) {
319
336
  schema = { ...schema, body: bodySchema };
320
337
  }
338
+ // const isMultipart =
339
+ // schema.consumes?.includes('multipart/form-data') ||
340
+ // (schema.body && schema.body.type === 'object' &&
341
+ // Object.values(schema.body.properties || {}).some((p: any) => p.format === 'binary'));
342
+ const isMultipart = ((_a = schema === null || schema === void 0 ? void 0 : schema.consumes) === null || _a === void 0 ? void 0 : _a.includes("multipart/form-data")) ||
343
+ Object.values(((_b = schema === null || schema === void 0 ? void 0 : schema.body) === null || _b === void 0 ? void 0 : _b.properties) || {}).some((p) => p.format === "binary");
344
+ // Prepare the route schema
345
+ let routeSchema = schema;
346
+ if (isMultipart) {
347
+ schema.consumes = ["multipart/form-data"];
348
+ if (!schema.body) {
349
+ schema.body = {
350
+ type: "object",
351
+ properties: {},
352
+ };
353
+ }
354
+ for (const param of allMeta.body) {
355
+ if (param.type == "route:file") {
356
+ schema.body.properties[param.key] = {
357
+ type: "string",
358
+ format: "binary",
359
+ };
360
+ }
361
+ else {
362
+ schema.body.properties[param.key] = { type: param.dataType };
363
+ }
364
+ }
365
+ }
321
366
  this.app.route({
322
367
  url: routePath,
323
368
  method: methodmetaOptions.method.toUpperCase(),
324
- schema: { ...schema },
369
+ schema: routeSchema,
370
+ attachValidation: isMultipart,
325
371
  handler: async (req, res) => {
326
372
  let reqClone = req;
327
373
  // class level authrization
@@ -368,23 +414,25 @@ class AvleonApplication {
368
414
  (0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: "queryparam" });
369
415
  }
370
416
  }
371
- for (let bodyMeta of allMeta.body) {
372
- if (bodyMeta.validatorClass) {
373
- const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
374
- if (err) {
375
- return await res.code(400).send({
376
- code: 400,
377
- error: "ValidationError",
378
- errors: err,
379
- message: err.message,
380
- });
417
+ if (!isMultipart) {
418
+ for (let bodyMeta of allMeta.body) {
419
+ if (bodyMeta.validatorClass) {
420
+ const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
421
+ if (err) {
422
+ return await res.code(400).send({
423
+ code: 400,
424
+ error: "ValidationError",
425
+ errors: err,
426
+ message: err.message,
427
+ });
428
+ }
381
429
  }
382
430
  }
383
431
  }
384
432
  const result = await prototype[method].apply(ctrl, args);
385
433
  // Custom wrapped file download
386
- if (result === null || result === void 0 ? void 0 : result.__isFileDownload) {
387
- const { stream, filename, contentType = "application/octet-stream", } = result;
434
+ if (result === null || result === void 0 ? void 0 : result.download) {
435
+ const { stream, filename } = result;
388
436
  if (!stream || typeof stream.pipe !== "function") {
389
437
  return res.code(500).send({
390
438
  code: 500,
@@ -392,6 +440,9 @@ class AvleonApplication {
392
440
  message: "Invalid stream object",
393
441
  });
394
442
  }
443
+ const contentType = result.contentType ||
444
+ mime_1.default.getType(filename) ||
445
+ "application/octet-stream";
395
446
  res.header("Content-Type", contentType);
396
447
  res.header("Content-Disposition", `attachment; filename="${filename}"`);
397
448
  stream.on("error", (err) => {
@@ -427,61 +478,128 @@ class AvleonApplication {
427
478
  }
428
479
  }
429
480
  /**
430
- * map all request parameters
431
- * @param req
432
- * @param meta
433
- * @returns
481
+ * Maps request data to controller method arguments based on decorators
482
+ * @param req - The incoming request object
483
+ * @param meta - Metadata about method parameters
484
+ * @returns Array of arguments to pass to the controller method
434
485
  */
435
486
  async _mapArgs(req, meta) {
436
- var _a;
487
+ var _a, _b, _c, _d;
488
+ // Initialize args cache on request if not present
437
489
  if (!req.hasOwnProperty("_argsCache")) {
438
490
  Object.defineProperty(req, "_argsCache", {
439
491
  value: new Map(),
440
492
  enumerable: false,
493
+ writable: false,
494
+ configurable: false,
441
495
  });
442
496
  }
443
497
  const cache = req._argsCache;
444
- const cacheKey = JSON.stringify(meta); // Faster key-based lookup
498
+ const cacheKey = JSON.stringify(meta);
499
+ // Return cached result if available
445
500
  if (cache.has(cacheKey)) {
446
501
  return cache.get(cacheKey);
447
502
  }
448
- const args = meta.params.map((p) => req.params[p.key] || null);
449
- meta.query.forEach((q) => (args[q.index] = q.key === "all" ? req.query : req.query[q.key]));
450
- meta.body.forEach((body) => (args[body.index] = { ...req.body, ...req.formData }));
451
- meta.currentUser.forEach((user) => (args[user.index] = req.user));
452
- meta.headers.forEach((header) => (args[header.index] =
453
- header.key === "all" ? req.headers : req.headers[header.key]));
454
- if (meta.file) {
455
- for await (let f of meta.file) {
456
- args[f.index] = await req.file();
457
- }
503
+ // Initialize args array with correct length
504
+ const maxIndex = Math.max(...meta.params.map((p) => p.index || 0), ...meta.query.map((q) => q.index), ...meta.body.map((b) => b.index), ...meta.currentUser.map((u) => u.index), ...meta.headers.map((h) => h.index), ...(((_a = meta.request) === null || _a === void 0 ? void 0 : _a.map((r) => r.index)) || []), ...(((_b = meta.file) === null || _b === void 0 ? void 0 : _b.map((f) => f.index)) || []), ...(((_c = meta.files) === null || _c === void 0 ? void 0 : _c.map((f) => f.index)) || []), -1) + 1;
505
+ const args = new Array(maxIndex).fill(undefined);
506
+ // Map route parameters
507
+ meta.params.forEach((p) => {
508
+ args[p.index] =
509
+ p.key == "all" ? { ...req.query } : req.params[p.key] || null;
510
+ });
511
+ // Map query parameters
512
+ meta.query.forEach((q) => {
513
+ args[q.index] = q.key == "all" ? { ...req.query } : req.query[q.key];
514
+ });
515
+ // Map body data (including form data)
516
+ meta.body.forEach((body) => {
517
+ args[body.index] = { ...req.body, ...req.formData };
518
+ });
519
+ // Map current user
520
+ meta.currentUser.forEach((user) => {
521
+ args[user.index] = req.user;
522
+ });
523
+ // Map headers
524
+ meta.headers.forEach((header) => {
525
+ args[header.index] =
526
+ header.key === "all" ? { ...req.headers } : req.headers[header.key];
527
+ });
528
+ // Map request object
529
+ if (meta.request && meta.request.length > 0) {
530
+ meta.request.forEach((r) => {
531
+ args[r.index] = req;
532
+ });
458
533
  }
459
- if (meta.files &&
460
- ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.startsWith("multipart/form-data")) === true) {
534
+ // Handle file uploads (single or multiple files)
535
+ const needsFiles = (meta.file && meta.file.length > 0) ||
536
+ (meta.files && meta.files.length > 0);
537
+ if (needsFiles &&
538
+ ((_d = req.headers["content-type"]) === null || _d === void 0 ? void 0 : _d.startsWith("multipart/form-data"))) {
461
539
  const files = await req.saveRequestFiles();
462
540
  if (!files || files.length === 0) {
463
- throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
541
+ // Only throw error if files are explicitly required
542
+ if (meta.files && meta.files.length > 0) {
543
+ throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
544
+ }
545
+ // For single file (@File()), set to null
546
+ if (meta.file && meta.file.length > 0) {
547
+ meta.file.forEach((f) => {
548
+ args[f.index] = null;
549
+ });
550
+ }
464
551
  }
465
- const fileInfo = files.map((file) => ({
466
- type: file.type,
467
- filepath: file.filepath,
468
- fieldname: file.fieldname,
469
- filename: file.filename,
470
- encoding: file.encoding,
471
- mimetype: file.mimetype,
472
- fields: file.fields,
473
- }));
474
- for await (let f of meta.files) {
475
- const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
476
- if (f.fieldName != "all" && findex == -1) {
477
- throw new exceptions_1.BadRequestException(`${f.fieldName} doesn't exists in request files tree.`);
552
+ else {
553
+ // Create file info objects
554
+ const fileInfo = files.map((file) => ({
555
+ type: file.type,
556
+ filepath: file.filepath,
557
+ fieldname: file.fieldname,
558
+ filename: file.filename,
559
+ encoding: file.encoding,
560
+ mimetype: file.mimetype,
561
+ fields: file.fields,
562
+ }));
563
+ // Handle single file decorator (@File())
564
+ if (meta.file && meta.file.length > 0) {
565
+ meta.file.forEach((f) => {
566
+ if (f.fieldName === "all") {
567
+ // Return first file if "all" is specified
568
+ args[f.index] = fileInfo[0] || null;
569
+ }
570
+ else {
571
+ // Find specific file by fieldname
572
+ const file = fileInfo.find((x) => x.fieldname === f.fieldName);
573
+ if (!file) {
574
+ throw new exceptions_1.BadRequestException(`File field "${f.fieldName}" not found in uploaded files`);
575
+ }
576
+ args[f.index] = file;
577
+ }
578
+ });
579
+ }
580
+ if (meta.files && meta.files.length > 0) {
581
+ meta.files.forEach((f) => {
582
+ if (f.fieldName === "all") {
583
+ args[f.index] = fileInfo;
584
+ }
585
+ else {
586
+ const matchingFiles = fileInfo.filter((x) => x.fieldname === f.fieldName);
587
+ if (matchingFiles.length === 0) {
588
+ throw new exceptions_1.BadRequestException(`No files found for field "${f.fieldName}"`);
589
+ }
590
+ args[f.index] = matchingFiles;
591
+ }
592
+ });
478
593
  }
479
- args[f.index] =
480
- f.fieldName == "all"
481
- ? fileInfo
482
- : fileInfo.filter((x) => x.fieldname == f.fieldName);
483
594
  }
484
595
  }
596
+ else if (needsFiles) {
597
+ // Files expected but request is not multipart/form-data
598
+ throw new exceptions_1.BadRequestException({
599
+ error: "Invalid content type. Expected multipart/form-data for file uploads",
600
+ });
601
+ }
602
+ // Cache the result
485
603
  cache.set(cacheKey, args);
486
604
  return args;
487
605
  }
@@ -497,6 +615,7 @@ class AvleonApplication {
497
615
  return this.metaCache.get(cacheKey);
498
616
  }
499
617
  const meta = {
618
+ request: Reflect.getMetadata(controller_1.REQUEST_METADATA_KEY, prototype, method) || [],
500
619
  params: Reflect.getMetadata(container_1.PARAM_META_KEY, prototype, method) || [],
501
620
  query: Reflect.getMetadata(container_1.QUERY_META_KEY, prototype, method) || [],
502
621
  body: Reflect.getMetadata(container_1.REQUEST_BODY_META_KEY, prototype, method) || [],
@@ -557,7 +676,7 @@ class AvleonApplication {
557
676
  if (this.controllers.length > 0) {
558
677
  for (let controller of this.controllers) {
559
678
  if ((0, container_1.isApiController)(controller)) {
560
- this.buildController(controller);
679
+ await this.buildController(controller);
561
680
  }
562
681
  else {
563
682
  throw new system_exception_1.SystemUseError("Not a api controller.");
@@ -657,7 +776,15 @@ class AvleonApplication {
657
776
  }
658
777
  }
659
778
  handleSocket(socket) {
779
+ const contextService = typedi_1.default.get(event_dispatcher_1.SocketContextService);
660
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
+ };
661
788
  }
662
789
  async run(port = 4000, fn) {
663
790
  if (this.alreadyRun)
@@ -677,7 +804,7 @@ class AvleonApplication {
677
804
  this.rMap.forEach((value, key) => {
678
805
  const [m, r] = key.split(":");
679
806
  this.app.route({
680
- method: m,
807
+ method: m.toUpperCase(),
681
808
  url: r,
682
809
  schema: value.schema || {},
683
810
  preHandler: value.middlewares ? value.middlewares : [],
@@ -687,32 +814,49 @@ class AvleonApplication {
687
814
  },
688
815
  });
689
816
  });
690
- this.app.setErrorHandler(async (error, req, res) => {
691
- const handledErr = this._handleError(error);
692
- if (error instanceof exceptions_1.ValidationErrorException ||
693
- error instanceof exceptions_1.BadRequestException) {
694
- return res.status(handledErr.code).send({
695
- code: handledErr.code,
696
- error: handledErr.error,
697
- errors: handledErr.message,
698
- });
817
+ this.app.setErrorHandler((error, request, reply) => {
818
+ if (error instanceof exceptions_1.BaseHttpException) {
819
+ const response = {
820
+ code: error.code,
821
+ status: "Error",
822
+ data: error.payload,
823
+ };
824
+ return reply
825
+ .status(error.code || 500)
826
+ .type("application/json")
827
+ .serializer((payload) => JSON.stringify(payload))
828
+ .send(response);
699
829
  }
700
- return res.status(handledErr.code).send(handledErr);
830
+ return reply.status(500).send({
831
+ code: 500,
832
+ message: error.message || "Internal Server Error",
833
+ });
701
834
  });
702
835
  await this.app.ready();
703
836
  if (this._hasWebsocket) {
704
- await this.app.io.on("connection", this.handleSocket);
705
- await this.app.io.use((socket, next) => {
706
- const token = socket.handshake.auth.token;
707
- try {
708
- const user = { id: 1, name: "tareq" };
709
- socket.data.user = user; // this powers @AuthUser()
710
- next();
711
- }
712
- catch (_a) {
713
- next(new Error("Unauthorized"));
714
- }
715
- });
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));
716
860
  }
717
861
  await this.app.listen({ port });
718
862
  console.log(`Application running on http://127.0.0.1:${port}`);
@@ -723,7 +867,7 @@ class AvleonApplication {
723
867
  this.rMap.forEach((value, key) => {
724
868
  const [m, r] = key.split(":");
725
869
  this.app.route({
726
- method: m,
870
+ method: m.toUpperCase(),
727
871
  url: r,
728
872
  schema: value.schema || {},
729
873
  preHandler: value.middlewares ? value.middlewares : [],
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const icore_1 = require("./icore");
4
+ describe("Avleon.createApplication", () => {
5
+ it("should return an instance of AvleonApplication", () => {
6
+ const app = icore_1.Avleon.createApplication();
7
+ expect(app).toBeInstanceOf(icore_1.AvleonApplication);
8
+ });
9
+ it("should always return the same instance (singleton)", () => {
10
+ const app1 = icore_1.Avleon.createApplication();
11
+ const app2 = icore_1.Avleon.createApplication();
12
+ expect(app1).toBe(app2);
13
+ });
14
+ });
package/dist/index.d.ts CHANGED
@@ -28,4 +28,28 @@ export * from "./event-dispatcher";
28
28
  export * from "./kenx-provider";
29
29
  export { Subscribe, Private } from "./event-subscriber";
30
30
  export declare const GetSchema: typeof sw.generateSwaggerSchema;
31
+ export declare const GetObjectSchema: typeof sw.CreateSwaggerObjectSchema;
32
+ export declare const OpenApiOk: (args1: any) => {
33
+ description: string;
34
+ content: {
35
+ "application/json": {
36
+ schema: {
37
+ type: string;
38
+ properties: {
39
+ code: {
40
+ type: string;
41
+ example: number;
42
+ };
43
+ status: {
44
+ type: string;
45
+ example: string;
46
+ };
47
+ data: any;
48
+ };
49
+ };
50
+ };
51
+ };
52
+ };
53
+ export declare const OpenApiResponse: typeof sw.OpenApiResponse;
54
+ export declare const OpenApiProperty: typeof sw.OpenApiProperty;
31
55
  export { default as AvleonContainer } 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.AvleonContainer = exports.GetSchema = exports.Private = exports.Subscribe = exports.exclude = exports.pick = exports.validateRequestBody = exports.inject = void 0;
42
+ exports.AvleonContainer = exports.OpenApiProperty = exports.OpenApiResponse = exports.OpenApiOk = exports.GetObjectSchema = exports.GetSchema = 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
@@ -76,5 +76,12 @@ var event_subscriber_1 = require("./event-subscriber");
76
76
  Object.defineProperty(exports, "Subscribe", { enumerable: true, get: function () { return event_subscriber_1.Subscribe; } });
77
77
  Object.defineProperty(exports, "Private", { enumerable: true, get: function () { return event_subscriber_1.Private; } });
78
78
  exports.GetSchema = sw.generateSwaggerSchema;
79
+ exports.GetObjectSchema = sw.CreateSwaggerObjectSchema;
80
+ const OpenApiOk = (args1) => {
81
+ return sw.OpenApiResponse(200, args1, "Success");
82
+ };
83
+ exports.OpenApiOk = OpenApiOk;
84
+ exports.OpenApiResponse = sw.OpenApiResponse;
85
+ exports.OpenApiProperty = sw.OpenApiProperty;
79
86
  var container_1 = require("./container");
80
87
  Object.defineProperty(exports, "AvleonContainer", { enumerable: true, get: function () { return __importDefault(container_1).default; } });
@@ -0,0 +1 @@
1
+ import "reflect-metadata";
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("reflect-metadata");
4
+ const typedi_1 = require("typedi");
5
+ const kenx_provider_1 = require("./kenx-provider");
6
+ jest.mock("knex", () => jest.fn(() => ({ mock: "knexInstance" })));
7
+ describe("DB", () => {
8
+ beforeEach(() => {
9
+ typedi_1.Container.reset();
10
+ });
11
+ it("should throw error if client is accessed before init", () => {
12
+ const db = new kenx_provider_1.DB();
13
+ expect(() => db.client).toThrow("Knex is not initialized. Call DB.init(config) first.");
14
+ });
15
+ it("should initialize knex and set connection", () => {
16
+ const db = new kenx_provider_1.DB();
17
+ const config = { client: "sqlite3", connection: {} };
18
+ const conn = db.init(config);
19
+ expect(conn).toEqual({ mock: "knexInstance" });
20
+ expect(db.client).toEqual({ mock: "knexInstance" });
21
+ expect(typedi_1.Container.get("KnexConnection")).toEqual({ mock: "knexInstance" });
22
+ });
23
+ it("should use existing connection from Container", () => {
24
+ const existingConn = { mock: "existingKnex" };
25
+ typedi_1.Container.set("KnexConnection", existingConn);
26
+ const db = new kenx_provider_1.DB();
27
+ expect(db.client).toBe(existingConn);
28
+ });
29
+ // it("should not reinitialize if connection exists", () => {
30
+ // const db = new DB();
31
+ // const config = { client: "sqlite3", connection: {} };
32
+ // db.init(config as Knex.Config);
33
+ // const conn2 = db.init(config as Knex.Config);
34
+ // expect(conn2).toEqual({ mock: "knexInstance" });
35
+ // });
36
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const logger_1 = require("./logger");
7
+ const pino_1 = __importDefault(require("pino"));
8
+ jest.mock("pino");
9
+ describe("LoggerService constructor", () => {
10
+ const mockPino = pino_1.default;
11
+ beforeEach(() => {
12
+ mockPino.mockClear();
13
+ });
14
+ it("should initialize logger with default level 'info' when LOG_LEVEL is not set", () => {
15
+ delete process.env.LOG_LEVEL;
16
+ new logger_1.LoggerService();
17
+ expect(mockPino).toHaveBeenCalledWith({
18
+ level: "info",
19
+ transport: {
20
+ target: "pino-pretty",
21
+ options: {
22
+ translateTime: "SYS:standard",
23
+ ignore: "pid,hostname",
24
+ },
25
+ },
26
+ });
27
+ });
28
+ it("should initialize logger with LOG_LEVEL from environment", () => {
29
+ process.env.LOG_LEVEL = "debug";
30
+ new logger_1.LoggerService();
31
+ expect(mockPino).toHaveBeenCalledWith({
32
+ level: "debug",
33
+ transport: {
34
+ target: "pino-pretty",
35
+ options: {
36
+ translateTime: "SYS:standard",
37
+ ignore: "pid,hostname",
38
+ },
39
+ },
40
+ });
41
+ });
42
+ });
@@ -0,0 +1 @@
1
+ import "reflect-metadata";