@avleon/core 0.0.39 → 0.0.42-rc0.1

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 +113 -28
  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 +7 -5
  19. package/dist/icore.js +191 -69
  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.d.ts +3 -0
  29. package/dist/middleware.js +7 -0
  30. package/dist/middleware.test.d.ts +1 -0
  31. package/dist/middleware.test.js +121 -0
  32. package/dist/multipart.test.d.ts +1 -0
  33. package/dist/multipart.test.js +87 -0
  34. package/dist/openapi.test.d.ts +1 -0
  35. package/dist/openapi.test.js +111 -0
  36. package/dist/params.test.d.ts +1 -0
  37. package/dist/params.test.js +83 -0
  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
@@ -62,6 +62,8 @@ const socket_io_1 = require("socket.io");
62
62
  const event_subscriber_1 = require("./event-subscriber");
63
63
  const stream_1 = __importDefault(require("stream"));
64
64
  const kenx_provider_1 = require("./kenx-provider");
65
+ const controller_1 = require("./controller");
66
+ const mime_1 = __importDefault(require("mime"));
65
67
  const isTsNode = process.env.TS_NODE_DEV ||
66
68
  process.env.TS_NODE_PROJECT ||
67
69
  process[Symbol.for("ts-node.register.instance")];
@@ -112,6 +114,17 @@ class AvleonApplication {
112
114
  }
113
115
  return AvleonApplication.instance;
114
116
  }
117
+ useLogger(corsOptions) {
118
+ let coptions = {};
119
+ if (corsOptions) {
120
+ if (this._isConfigClass(corsOptions)) {
121
+ coptions = this.appConfig.get(corsOptions);
122
+ }
123
+ else {
124
+ coptions = corsOptions;
125
+ }
126
+ }
127
+ }
115
128
  isDevelopment() {
116
129
  const env = container_1.default.get(environment_variables_1.Environment);
117
130
  return env.get("NODE_ENV") == "development";
@@ -128,7 +141,7 @@ class AvleonApplication {
128
141
  if (options.ui && options.ui == "scalar") {
129
142
  const scalarPlugin = (0, utils_1.optionalRequire)("@scalar/fastify-api-reference", {
130
143
  failOnMissing: true,
131
- customMessage: "Install \"@scalar/fastify-api-reference\" to enable API docs.\n\n npm install @scalar/fastify-api-reference",
144
+ customMessage: 'Install "@scalar/fastify-api-reference" to enable API docs.\n\n npm install @scalar/fastify-api-reference',
132
145
  });
133
146
  await this.app.register(scalarPlugin, {
134
147
  routePrefix: rPrefix,
@@ -147,7 +160,7 @@ class AvleonApplication {
147
160
  else {
148
161
  const fastifySwaggerUi = (0, utils_1.optionalRequire)("@fastify/swagger-ui", {
149
162
  failOnMissing: true,
150
- customMessage: "Install \"@fastify/swagger-ui\" to enable API docs.\n\n npm install @fastify/swagger-ui",
163
+ customMessage: 'Install "@fastify/swagger-ui" to enable API docs.\n\n npm install @fastify/swagger-ui',
151
164
  });
152
165
  await this.app.register(fastifySwaggerUi, {
153
166
  logo: logo ? logo : null,
@@ -181,7 +194,7 @@ class AvleonApplication {
181
194
  async _initWebSocket(options) {
182
195
  const fsSocketIO = (0, utils_1.optionalRequire)("fastify-socket.io", {
183
196
  failOnMissing: true,
184
- customMessage: "Install \"fastify-socket.io\" to enable socket.io.\n\n run pnpm install fastify-socket.io",
197
+ customMessage: 'Install "fastify-socket.io" to enable socket.io.\n\n run pnpm install fastify-socket.io',
185
198
  });
186
199
  await this.app.register(fsSocketIO, options);
187
200
  const socketIO = await this.app.io;
@@ -209,8 +222,8 @@ class AvleonApplication {
209
222
  if (multipartOptions) {
210
223
  this.multipartOptions = multipartOptions;
211
224
  this.app.register(multipart_1.default, {
212
- ...this.multipartOptions,
213
225
  attachFieldsToBody: true,
226
+ ...this.multipartOptions,
214
227
  });
215
228
  }
216
229
  }
@@ -281,6 +294,7 @@ class AvleonApplication {
281
294
  * @returns void
282
295
  */
283
296
  async buildController(controller) {
297
+ var _a, _b;
284
298
  const ctrl = typedi_1.default.get(controller);
285
299
  const controllerMeta = Reflect.getMetadata(container_1.CONTROLLER_META_KEY, ctrl.constructor);
286
300
  if (!controllerMeta)
@@ -318,10 +332,39 @@ class AvleonApplication {
318
332
  if (!swaggerMeta.body && bodySchema) {
319
333
  schema = { ...schema, body: bodySchema };
320
334
  }
335
+ // const isMultipart =
336
+ // schema.consumes?.includes('multipart/form-data') ||
337
+ // (schema.body && schema.body.type === 'object' &&
338
+ // Object.values(schema.body.properties || {}).some((p: any) => p.format === 'binary'));
339
+ const isMultipart = ((_a = schema === null || schema === void 0 ? void 0 : schema.consumes) === null || _a === void 0 ? void 0 : _a.includes("multipart/form-data")) ||
340
+ 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");
341
+ // Prepare the route schema
342
+ let routeSchema = schema;
343
+ if (isMultipart) {
344
+ schema.consumes = ["multipart/form-data"];
345
+ if (!schema.body) {
346
+ schema.body = {
347
+ type: "object",
348
+ properties: {},
349
+ };
350
+ }
351
+ for (const param of allMeta.body) {
352
+ if (param.type == "route:file") {
353
+ schema.body.properties[param.key] = {
354
+ type: "string",
355
+ format: "binary",
356
+ };
357
+ }
358
+ else {
359
+ schema.body.properties[param.key] = { type: param.dataType };
360
+ }
361
+ }
362
+ }
321
363
  this.app.route({
322
364
  url: routePath,
323
365
  method: methodmetaOptions.method.toUpperCase(),
324
- schema: { ...schema },
366
+ schema: routeSchema,
367
+ attachValidation: isMultipart,
325
368
  handler: async (req, res) => {
326
369
  let reqClone = req;
327
370
  // class level authrization
@@ -368,23 +411,25 @@ class AvleonApplication {
368
411
  (0, validation_1.validateOrThrow)({ [queryMeta.key]: args[queryMeta.index] }, { [queryMeta.key]: { type: queryMeta.dataType } }, { location: "queryparam" });
369
412
  }
370
413
  }
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
- });
414
+ if (!isMultipart) {
415
+ for (let bodyMeta of allMeta.body) {
416
+ if (bodyMeta.validatorClass) {
417
+ const err = await (0, helpers_1.validateObjectByInstance)(bodyMeta.dataType, args[bodyMeta.index]);
418
+ if (err) {
419
+ return await res.code(400).send({
420
+ code: 400,
421
+ error: "ValidationError",
422
+ errors: err,
423
+ message: err.message,
424
+ });
425
+ }
381
426
  }
382
427
  }
383
428
  }
384
429
  const result = await prototype[method].apply(ctrl, args);
385
430
  // Custom wrapped file download
386
- if (result === null || result === void 0 ? void 0 : result.__isFileDownload) {
387
- const { stream, filename, contentType = "application/octet-stream", } = result;
431
+ if (result === null || result === void 0 ? void 0 : result.download) {
432
+ const { stream, filename } = result;
388
433
  if (!stream || typeof stream.pipe !== "function") {
389
434
  return res.code(500).send({
390
435
  code: 500,
@@ -392,6 +437,9 @@ class AvleonApplication {
392
437
  message: "Invalid stream object",
393
438
  });
394
439
  }
440
+ const contentType = result.contentType ||
441
+ mime_1.default.getType(filename) ||
442
+ "application/octet-stream";
395
443
  res.header("Content-Type", contentType);
396
444
  res.header("Content-Disposition", `attachment; filename="${filename}"`);
397
445
  stream.on("error", (err) => {
@@ -427,61 +475,128 @@ class AvleonApplication {
427
475
  }
428
476
  }
429
477
  /**
430
- * map all request parameters
431
- * @param req
432
- * @param meta
433
- * @returns
478
+ * Maps request data to controller method arguments based on decorators
479
+ * @param req - The incoming request object
480
+ * @param meta - Metadata about method parameters
481
+ * @returns Array of arguments to pass to the controller method
434
482
  */
435
483
  async _mapArgs(req, meta) {
436
- var _a;
484
+ var _a, _b, _c, _d;
485
+ // Initialize args cache on request if not present
437
486
  if (!req.hasOwnProperty("_argsCache")) {
438
487
  Object.defineProperty(req, "_argsCache", {
439
488
  value: new Map(),
440
489
  enumerable: false,
490
+ writable: false,
491
+ configurable: false,
441
492
  });
442
493
  }
443
494
  const cache = req._argsCache;
444
- const cacheKey = JSON.stringify(meta); // Faster key-based lookup
495
+ const cacheKey = JSON.stringify(meta);
496
+ // Return cached result if available
445
497
  if (cache.has(cacheKey)) {
446
498
  return cache.get(cacheKey);
447
499
  }
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
- }
500
+ // Initialize args array with correct length
501
+ 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;
502
+ const args = new Array(maxIndex).fill(undefined);
503
+ // Map route parameters
504
+ meta.params.forEach((p) => {
505
+ args[p.index] =
506
+ p.key == "all" ? { ...req.query } : req.params[p.key] || null;
507
+ });
508
+ // Map query parameters
509
+ meta.query.forEach((q) => {
510
+ args[q.index] = q.key == "all" ? { ...req.query } : req.query[q.key];
511
+ });
512
+ // Map body data (including form data)
513
+ meta.body.forEach((body) => {
514
+ args[body.index] = { ...req.body, ...req.formData };
515
+ });
516
+ // Map current user
517
+ meta.currentUser.forEach((user) => {
518
+ args[user.index] = req.user;
519
+ });
520
+ // Map headers
521
+ meta.headers.forEach((header) => {
522
+ args[header.index] =
523
+ header.key === "all" ? { ...req.headers } : req.headers[header.key];
524
+ });
525
+ // Map request object
526
+ if (meta.request && meta.request.length > 0) {
527
+ meta.request.forEach((r) => {
528
+ args[r.index] = req;
529
+ });
458
530
  }
459
- if (meta.files &&
460
- ((_a = req.headers["content-type"]) === null || _a === void 0 ? void 0 : _a.startsWith("multipart/form-data")) === true) {
531
+ // Handle file uploads (single or multiple files)
532
+ const needsFiles = (meta.file && meta.file.length > 0) ||
533
+ (meta.files && meta.files.length > 0);
534
+ if (needsFiles &&
535
+ ((_d = req.headers["content-type"]) === null || _d === void 0 ? void 0 : _d.startsWith("multipart/form-data"))) {
461
536
  const files = await req.saveRequestFiles();
462
537
  if (!files || files.length === 0) {
463
- throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
538
+ // Only throw error if files are explicitly required
539
+ if (meta.files && meta.files.length > 0) {
540
+ throw new exceptions_1.BadRequestException({ error: "No files uploaded" });
541
+ }
542
+ // For single file (@File()), set to null
543
+ if (meta.file && meta.file.length > 0) {
544
+ meta.file.forEach((f) => {
545
+ args[f.index] = null;
546
+ });
547
+ }
464
548
  }
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.`);
549
+ else {
550
+ // Create file info objects
551
+ const fileInfo = files.map((file) => ({
552
+ type: file.type,
553
+ filepath: file.filepath,
554
+ fieldname: file.fieldname,
555
+ filename: file.filename,
556
+ encoding: file.encoding,
557
+ mimetype: file.mimetype,
558
+ fields: file.fields,
559
+ }));
560
+ // Handle single file decorator (@File())
561
+ if (meta.file && meta.file.length > 0) {
562
+ meta.file.forEach((f) => {
563
+ if (f.fieldName === "all") {
564
+ // Return first file if "all" is specified
565
+ args[f.index] = fileInfo[0] || null;
566
+ }
567
+ else {
568
+ // Find specific file by fieldname
569
+ const file = fileInfo.find((x) => x.fieldname === f.fieldName);
570
+ if (!file) {
571
+ throw new exceptions_1.BadRequestException(`File field "${f.fieldName}" not found in uploaded files`);
572
+ }
573
+ args[f.index] = file;
574
+ }
575
+ });
576
+ }
577
+ if (meta.files && meta.files.length > 0) {
578
+ meta.files.forEach((f) => {
579
+ if (f.fieldName === "all") {
580
+ args[f.index] = fileInfo;
581
+ }
582
+ else {
583
+ const matchingFiles = fileInfo.filter((x) => x.fieldname === f.fieldName);
584
+ if (matchingFiles.length === 0) {
585
+ throw new exceptions_1.BadRequestException(`No files found for field "${f.fieldName}"`);
586
+ }
587
+ args[f.index] = matchingFiles;
588
+ }
589
+ });
478
590
  }
479
- args[f.index] =
480
- f.fieldName == "all"
481
- ? fileInfo
482
- : fileInfo.filter((x) => x.fieldname == f.fieldName);
483
591
  }
484
592
  }
593
+ else if (needsFiles) {
594
+ // Files expected but request is not multipart/form-data
595
+ throw new exceptions_1.BadRequestException({
596
+ error: "Invalid content type. Expected multipart/form-data for file uploads",
597
+ });
598
+ }
599
+ // Cache the result
485
600
  cache.set(cacheKey, args);
486
601
  return args;
487
602
  }
@@ -497,6 +612,7 @@ class AvleonApplication {
497
612
  return this.metaCache.get(cacheKey);
498
613
  }
499
614
  const meta = {
615
+ request: Reflect.getMetadata(controller_1.REQUEST_METADATA_KEY, prototype, method) || [],
500
616
  params: Reflect.getMetadata(container_1.PARAM_META_KEY, prototype, method) || [],
501
617
  query: Reflect.getMetadata(container_1.QUERY_META_KEY, prototype, method) || [],
502
618
  body: Reflect.getMetadata(container_1.REQUEST_BODY_META_KEY, prototype, method) || [],
@@ -557,7 +673,7 @@ class AvleonApplication {
557
673
  if (this.controllers.length > 0) {
558
674
  for (let controller of this.controllers) {
559
675
  if ((0, container_1.isApiController)(controller)) {
560
- this.buildController(controller);
676
+ await this.buildController(controller);
561
677
  }
562
678
  else {
563
679
  throw new system_exception_1.SystemUseError("Not a api controller.");
@@ -677,7 +793,7 @@ class AvleonApplication {
677
793
  this.rMap.forEach((value, key) => {
678
794
  const [m, r] = key.split(":");
679
795
  this.app.route({
680
- method: m,
796
+ method: m.toUpperCase(),
681
797
  url: r,
682
798
  schema: value.schema || {},
683
799
  preHandler: value.middlewares ? value.middlewares : [],
@@ -687,19 +803,6 @@ class AvleonApplication {
687
803
  },
688
804
  });
689
805
  });
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
- });
699
- }
700
- return res.status(handledErr.code).send(handledErr);
701
- });
702
- await this.app.ready();
703
806
  if (this._hasWebsocket) {
704
807
  await this.app.io.on("connection", this.handleSocket);
705
808
  await this.app.io.use((socket, next) => {
@@ -714,6 +817,25 @@ class AvleonApplication {
714
817
  }
715
818
  });
716
819
  }
820
+ this.app.setErrorHandler((error, request, reply) => {
821
+ if (error instanceof exceptions_1.BaseHttpException) {
822
+ const response = {
823
+ code: error.code,
824
+ status: "Error",
825
+ data: error.payload,
826
+ };
827
+ return reply
828
+ .status(error.code || 500)
829
+ .type("application/json")
830
+ .serializer((payload) => JSON.stringify(payload))
831
+ .send(response);
832
+ }
833
+ return reply.status(500).send({
834
+ code: 500,
835
+ message: error.message || "Internal Server Error",
836
+ });
837
+ });
838
+ await this.app.ready();
717
839
  await this.app.listen({ port });
718
840
  console.log(`Application running on http://127.0.0.1:${port}`);
719
841
  }
@@ -723,7 +845,7 @@ class AvleonApplication {
723
845
  this.rMap.forEach((value, key) => {
724
846
  const [m, r] = key.split(":");
725
847
  this.app.route({
726
- method: m,
848
+ method: m.toUpperCase(),
727
849
  url: r,
728
850
  schema: value.schema || {},
729
851
  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
+ });
@@ -14,6 +14,9 @@ export type AuthReturnTypes = IRequest | Promise<IRequest>;
14
14
  interface AuthorizeClass {
15
15
  authorize(req: IRequest, options?: any): AuthReturnTypes;
16
16
  }
17
+ export declare function CanAuthorize(target: {
18
+ new (...args: any[]): AuthorizeClass;
19
+ }): void;
17
20
  export declare function AppAuthorization(target: {
18
21
  new (...args: any[]): AuthorizeClass;
19
22
  }): void;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthorizeMiddleware = exports.AvleonMiddleware = void 0;
4
+ exports.CanAuthorize = CanAuthorize;
4
5
  exports.AppAuthorization = AppAuthorization;
5
6
  exports.Authorized = Authorized;
6
7
  exports.AppMiddleware = AppMiddleware;
@@ -19,6 +20,12 @@ exports.AvleonMiddleware = AvleonMiddleware;
19
20
  class AuthorizeMiddleware {
20
21
  }
21
22
  exports.AuthorizeMiddleware = AuthorizeMiddleware;
23
+ function CanAuthorize(target) {
24
+ if (typeof target.prototype.authorize !== "function") {
25
+ throw new Error(`Class "${target.name}" must implement an "authorize" method.`);
26
+ }
27
+ (0, typedi_1.Service)()(target);
28
+ }
22
29
  function AppAuthorization(target) {
23
30
  if (typeof target.prototype.authorize !== "function") {
24
31
  throw new Error(`Class "${target.name}" must implement an "authorize" method.`);
@@ -0,0 +1 @@
1
+ import "reflect-metadata";