@avleon/core 0.0.40 → 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 (50) 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 +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.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.test.d.ts +1 -0
  37. package/dist/queue.test.js +79 -0
  38. package/dist/route-methods.test.d.ts +1 -0
  39. package/dist/route-methods.test.js +129 -0
  40. package/dist/swagger-schema.d.ts +42 -0
  41. package/dist/swagger-schema.js +331 -58
  42. package/dist/swagger-schema.test.d.ts +1 -0
  43. package/dist/swagger-schema.test.js +105 -0
  44. package/dist/validation.d.ts +7 -0
  45. package/dist/validation.js +2 -0
  46. package/dist/validation.test.d.ts +1 -0
  47. package/dist/validation.test.js +61 -0
  48. package/dist/websocket.test.d.ts +1 -0
  49. package/dist/websocket.test.js +27 -0
  50. package/package.json +11 -9
package/README.md CHANGED
@@ -504,24 +504,53 @@ export class UserService {
504
504
  }
505
505
  ```
506
506
 
507
- ### File Uploads
507
+ ### File Uploads & File Storage
508
508
 
509
509
  Handle file uploads with multipart support:
510
510
 
511
511
  ```typescript
512
512
  // Configure multipart file uploads
513
513
  app.useMultipart({
514
- destination: path.join(process.cwd(), 'uploads'),
514
+ destination: path.join(process.cwd(), 'public/uploads'),
515
515
  limits: {
516
516
  fileSize: 5 * 1024 * 1024 // 5MB
517
517
  }
518
518
  });
519
-
519
+ ```
520
+ ```typescript
520
521
  // In your controller
522
+ import {FileStorage} from '@avleon/core';
523
+
524
+ //inject FileStorage into constructor
525
+ constructor(
526
+ private readonly fileStorage: FileStorage
527
+ ){}
528
+
529
+ @OpenApi({
530
+ description: "Uploading single file"
531
+ body:{
532
+ type:"object",
533
+ properties:{
534
+ file:{
535
+ type:"string",
536
+ format:"binary"
537
+ }
538
+ },
539
+ required:["file"]
540
+ }
541
+ })
521
542
  @Post('/upload')
522
- async uploadFile(@MultipartFile() file: any) {
543
+ async uploadSingleFile(@UploadFile('file') file: MultipartFile) {
523
544
  // Process uploaded file
524
- return HttpResponse.Ok({ filename: file.filename });
545
+ const result = await this.fileStorage.save(file);
546
+ // or with new name
547
+ // const result = await this.fileStorage.save(file, {as:newname.ext});
548
+ // result
549
+ // {
550
+ // uploadPath:"/uplaod",
551
+ // staticPath: "/static/"
552
+ //}
553
+ return result;
525
554
  }
526
555
  ```
527
556
 
@@ -530,6 +559,9 @@ async uploadFile(@MultipartFile() file: any) {
530
559
  Serve static files:
531
560
 
532
561
  ```typescript
562
+ import path from 'path';
563
+
564
+
533
565
  app.useStaticFiles({
534
566
  path: path.join(process.cwd(), "public"),
535
567
  prefix: "/static/",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const cache_1 = require("./cache");
4
+ describe("CacheManager (in-memory)", () => {
5
+ let cache;
6
+ beforeEach(() => {
7
+ cache = new cache_1.CacheManager();
8
+ });
9
+ it("should set and get a value", async () => {
10
+ await cache.set("foo", "bar");
11
+ const result = await cache.get("foo");
12
+ expect(result).toBe("bar");
13
+ });
14
+ it("should return null for missing key", async () => {
15
+ const result = await cache.get("missing");
16
+ expect(result).toBeNull();
17
+ });
18
+ it("should delete a key", async () => {
19
+ await cache.set("foo", "bar");
20
+ await cache.delete("foo");
21
+ const result = await cache.get("foo");
22
+ expect(result).toBeNull();
23
+ });
24
+ it("should associate keys with tags and invalidate by tag", async () => {
25
+ await cache.set("a", 1, ["tag1"]);
26
+ await cache.set("b", 2, ["tag1", "tag2"]);
27
+ await cache.set("c", 3, ["tag2"]);
28
+ await cache.invalidateTag("tag1");
29
+ expect(await cache.get("a")).toBeNull();
30
+ expect(await cache.get("b")).toBeNull();
31
+ expect(await cache.get("c")).toBe(3);
32
+ });
33
+ it("should not fail when invalidating a non-existent tag", async () => {
34
+ await expect(cache.invalidateTag("nope")).resolves.not.toThrow();
35
+ });
36
+ });
@@ -4,6 +4,8 @@
4
4
  * @email xtrinsic96@gmail.com
5
5
  * @url https://github.com/xtareq
6
6
  */
7
+ export declare const REQUEST_METADATA_KEY: unique symbol;
8
+ export declare function AvleonRequest(): ParameterDecorator;
7
9
  /**
8
10
  * Options for configuring a controller.
9
11
  * @remarks
@@ -6,10 +6,23 @@
6
6
  * @url https://github.com/xtareq
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.REQUEST_METADATA_KEY = void 0;
10
+ exports.AvleonRequest = AvleonRequest;
9
11
  exports.createControllerDecorator = createControllerDecorator;
10
12
  exports.ApiController = ApiController;
11
13
  const typedi_1 = require("typedi");
12
14
  const container_1 = require("./container");
15
+ exports.REQUEST_METADATA_KEY = Symbol('avleon:request');
16
+ function AvleonRequest() {
17
+ return (target, propertyKey, parameterIndex) => {
18
+ const existingParams = Reflect.getMetadata(exports.REQUEST_METADATA_KEY, target, propertyKey) || [];
19
+ existingParams.push({
20
+ index: parameterIndex,
21
+ type: 'request',
22
+ });
23
+ Reflect.defineMetadata(exports.REQUEST_METADATA_KEY, existingParams, target, propertyKey);
24
+ };
25
+ }
13
26
  function createControllerDecorator(type = "web") {
14
27
  return function (pathOrOptions, maybeOptions) {
15
28
  return function (target) {
@@ -0,0 +1 @@
1
+ import "reflect-metadata";
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
19
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
20
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
21
+ 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;
22
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
23
+ };
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ require("reflect-metadata");
43
+ const controller_1 = require("./controller");
44
+ const containerModule = __importStar(require("./container"));
45
+ const typedi_1 = require("typedi");
46
+ describe("Controller Decorators", () => {
47
+ beforeEach(() => {
48
+ jest.clearAllMocks();
49
+ });
50
+ describe("ApiController", () => {
51
+ it("should apply metadata and Service decorator when used as a class decorator", () => {
52
+ const registerControllerSpy = jest.spyOn(containerModule, "registerController");
53
+ let TestController = class TestController {
54
+ };
55
+ TestController = __decorate([
56
+ controller_1.ApiController
57
+ ], TestController);
58
+ expect(Reflect.getMetadata(containerModule.API_CONTROLLER_METADATA_KEY, TestController)).toBe(true);
59
+ expect(registerControllerSpy).toHaveBeenCalledWith(TestController);
60
+ expect(Reflect.getMetadata(containerModule.CONTROLLER_META_KEY, TestController)).toEqual({
61
+ type: "api",
62
+ path: "/",
63
+ options: {},
64
+ });
65
+ });
66
+ it("should apply metadata and Service decorator when used with a path", () => {
67
+ const registerControllerSpy = jest.spyOn(containerModule, "registerController");
68
+ let TestController = class TestController {
69
+ };
70
+ TestController = __decorate([
71
+ (0, controller_1.ApiController)("/test")
72
+ ], TestController);
73
+ expect(Reflect.getMetadata(containerModule.API_CONTROLLER_METADATA_KEY, TestController)).toBe(true);
74
+ expect(registerControllerSpy).toHaveBeenCalledWith(TestController);
75
+ expect(Reflect.getMetadata(containerModule.CONTROLLER_META_KEY, TestController)).toEqual({
76
+ type: "api",
77
+ path: "/test",
78
+ options: {},
79
+ });
80
+ });
81
+ it("should apply metadata and Service decorator when used with options", () => {
82
+ const registerControllerSpy = jest.spyOn(containerModule, "registerController");
83
+ const options = { name: "Custom", path: "/custom", version: "1.0.0" };
84
+ let TestController = class TestController {
85
+ };
86
+ TestController = __decorate([
87
+ (0, controller_1.ApiController)(options)
88
+ ], TestController);
89
+ expect(Reflect.getMetadata(containerModule.API_CONTROLLER_METADATA_KEY, TestController)).toBe(true);
90
+ expect(registerControllerSpy).toHaveBeenCalledWith(TestController);
91
+ expect(Reflect.getMetadata(containerModule.CONTROLLER_META_KEY, TestController)).toEqual({
92
+ type: "api",
93
+ path: "/custom",
94
+ options,
95
+ });
96
+ });
97
+ const originalService = typedi_1.Service;
98
+ // @ts-ignore
99
+ containerModule.Service = undefined;
100
+ expect(() => {
101
+ let TestController = class TestController {
102
+ };
103
+ TestController = __decorate([
104
+ controller_1.ApiController
105
+ ], TestController);
106
+ }).toThrow("Service decorator is not a function");
107
+ // Restore
108
+ // @ts-ignore
109
+ containerModule.Service = originalService;
110
+ });
111
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,70 @@
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 environment_variables_1 = require("./environment-variables");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ jest.mock("fs");
10
+ const mockedFs = fs_1.default;
11
+ describe("Environment", () => {
12
+ const envFilePath = path_1.default.join(process.cwd(), ".env");
13
+ const envContent = "TEST_KEY=123\nANOTHER_KEY=abc";
14
+ const parsedEnv = { TEST_KEY: "123", ANOTHER_KEY: "abc" };
15
+ beforeEach(() => {
16
+ jest.resetModules();
17
+ mockedFs.existsSync.mockClear();
18
+ mockedFs.readFileSync.mockClear();
19
+ process.env.TEST_KEY = "override";
20
+ process.env.ONLY_PROCESS = "proc";
21
+ });
22
+ afterEach(() => {
23
+ delete process.env.TEST_KEY;
24
+ delete process.env.ONLY_PROCESS;
25
+ });
26
+ it("should get value from process.env if present", () => {
27
+ mockedFs.existsSync.mockReturnValue(true);
28
+ mockedFs.readFileSync.mockReturnValue(envContent);
29
+ const env = new environment_variables_1.Environment();
30
+ expect(env.get("TEST_KEY")).toBe("override");
31
+ });
32
+ it("should get value from .env file if not in process.env", () => {
33
+ mockedFs.existsSync.mockReturnValue(true);
34
+ mockedFs.readFileSync.mockReturnValue(envContent);
35
+ const env = new environment_variables_1.Environment();
36
+ expect(env.get("ANOTHER_KEY")).toBe("abc");
37
+ });
38
+ it("should return undefined for missing key", () => {
39
+ mockedFs.existsSync.mockReturnValue(true);
40
+ mockedFs.readFileSync.mockReturnValue(envContent);
41
+ const env = new environment_variables_1.Environment();
42
+ expect(env.get("MISSING_KEY")).toBeUndefined();
43
+ });
44
+ it("should throw EnvironmentVariableNotFound for getOrThrow if missing", () => {
45
+ mockedFs.existsSync.mockReturnValue(true);
46
+ mockedFs.readFileSync.mockReturnValue(envContent);
47
+ const env = new environment_variables_1.Environment();
48
+ expect(() => env.getOrThrow("MISSING_KEY")).toThrow();
49
+ });
50
+ it("should get all variables with process.env taking precedence", () => {
51
+ mockedFs.existsSync.mockReturnValue(true);
52
+ mockedFs.readFileSync.mockReturnValue(envContent);
53
+ const env = new environment_variables_1.Environment();
54
+ const all = env.getAll();
55
+ expect(all.TEST_KEY).toBe("override");
56
+ expect(all.ANOTHER_KEY).toBe("abc");
57
+ expect(all.ONLY_PROCESS).toBe("proc");
58
+ });
59
+ it("should handle missing .env file gracefully", () => {
60
+ mockedFs.existsSync.mockReturnValue(false);
61
+ const env = new environment_variables_1.Environment();
62
+ expect(env.get("ONLY_PROCESS")).toBe("proc");
63
+ expect(env.get("TEST_KEY")).toBe("override");
64
+ });
65
+ it("should return empty object if error occurs during parsing", () => {
66
+ mockedFs.existsSync.mockImplementation(() => { throw new Error("fs error"); });
67
+ const env = new environment_variables_1.Environment();
68
+ expect(env.getAll()).toEqual({});
69
+ });
70
+ });
@@ -7,6 +7,7 @@
7
7
  export declare abstract class BaseHttpException extends Error {
8
8
  code: number;
9
9
  name: string;
10
+ payload: any;
10
11
  constructor(message: any);
11
12
  isCustomException(): boolean;
12
13
  }
@@ -9,9 +9,11 @@ exports.HttpExceptions = exports.ForbiddenException = exports.UnauthorizedExcept
9
9
  */
10
10
  class BaseHttpException extends Error {
11
11
  constructor(message) {
12
- super(JSON.stringify(message));
12
+ const stringMessage = typeof message === "string" ? message : JSON.stringify(message);
13
+ super(stringMessage);
13
14
  this.code = 500;
14
15
  this.name = "HttpException";
16
+ this.payload = typeof message === "string" ? { message } : message;
15
17
  }
16
18
  isCustomException() {
17
19
  return true;
@@ -1,4 +1,4 @@
1
- import { PathLike } from "fs";
1
+ import fs, { PathLike } from "fs";
2
2
  import { MultipartFile } from "./multipart";
3
3
  interface TransformOptions {
4
4
  resize?: {
@@ -10,9 +10,9 @@ interface TransformOptions {
10
10
  }
11
11
  export interface FileStorageInterface {
12
12
  transform(options: TransformOptions): FileStorage;
13
- save(file: MultipartFile, options?: SaveOptionsSingle): Promise<MultipartFile | undefined>;
14
- saveAll(files: MultipartFile[], options?: SaveOptions): Promise<MultipartFile[] | undefined>;
15
- remove(filepath: PathLike): Promise<void>;
13
+ save(file: MultipartFile, options?: SaveOptionsSingle): Promise<MultipartFile>;
14
+ saveAll(files: MultipartFile[], options?: SaveOptions): Promise<MultipartFile[]>;
15
+ remove(filepath: string): Promise<void>;
16
16
  }
17
17
  export interface SaveOptions {
18
18
  overwrite?: boolean;
@@ -23,12 +23,47 @@ export interface SaveOptionsSingle extends SaveOptions {
23
23
  }
24
24
  export declare class FileStorage implements FileStorageInterface {
25
25
  private transformOptions;
26
- transform(options: TransformOptions): this;
27
- private isFileExists;
28
- save(f: MultipartFile, options?: SaveOptionsSingle): Promise<import("@fastify/multipart").MultipartFile | undefined>;
29
- remove(filepath: PathLike): Promise<void>;
30
- saveAll(files: MultipartFile[], options?: SaveOptions): Promise<MultipartFile[]>;
26
+ private readonly baseDir;
27
+ private readonly maxFileSize;
28
+ constructor();
29
+ /**
30
+ * Set transformation options for the next save operation
31
+ */
32
+ transform(options: TransformOptions): FileStorage;
33
+ getUploadFile(fliePath: string): Promise<Buffer<ArrayBufferLike>>;
34
+ download(filepath: PathLike): Promise<{
35
+ download: boolean;
36
+ stream: fs.ReadStream;
37
+ filename: string;
38
+ }>;
39
+ downloadAs(filepath: PathLike, filename: string): Promise<{
40
+ download: boolean;
41
+ stream: fs.ReadStream;
42
+ filename: string;
43
+ }>;
44
+ /**
45
+ * Save a single file with optional transformations
46
+ */
47
+ save(f: MultipartFile, options?: SaveOptionsSingle): Promise<any>;
48
+ /**
49
+ * Save multiple files
50
+ */
51
+ saveAll(files: MultipartFile[], options?: SaveOptions): Promise<any[]>;
52
+ /**
53
+ * Remove a file from storage
54
+ */
55
+ remove(filepath: string): Promise<void>;
56
+ /**
57
+ * Process image with transformations using sharp
58
+ */
31
59
  private processImage;
60
+ /**
61
+ * Helper methods
62
+ */
63
+ private isFileExists;
32
64
  private ensureDirectoryExists;
65
+ private removeFileSync;
66
+ private isImageFile;
67
+ private validateFilename;
33
68
  }
34
69
  export {};