@avleon/core 0.0.24 → 0.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application.d.ts +21 -0
- package/dist/cache.d.ts +12 -0
- package/dist/cache.js +78 -0
- package/dist/icore.d.ts +27 -18
- package/dist/icore.js +89 -44
- package/dist/testing.js +1 -1
- package/package.json +21 -12
- package/src/application.ts +29 -0
- package/src/cache.ts +91 -0
- package/src/config.ts +3 -0
- package/src/controller.ts +1 -2
- package/src/exceptions/http-exceptions.ts +1 -1
- package/src/icore.ts +214 -138
- package/src/testing.ts +1 -1
package/dist/application.d.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { Constructor } from "./helpers";
|
|
2
2
|
import { RouteShorthandMethod } from "fastify";
|
|
3
|
+
export interface AvleonApplication {
|
|
4
|
+
useCors: () => void;
|
|
5
|
+
useOpenApi: () => void;
|
|
6
|
+
useView: () => void;
|
|
7
|
+
useAuth: () => void;
|
|
8
|
+
useMultipart: () => void;
|
|
9
|
+
useDataSource: () => void;
|
|
10
|
+
useMiddlewares: () => void;
|
|
11
|
+
useControllers: () => void;
|
|
12
|
+
useAutoControllers: () => void;
|
|
13
|
+
useStaticFiles: () => void;
|
|
14
|
+
useCustomErrorHandler: () => void;
|
|
15
|
+
mapGroup: () => any;
|
|
16
|
+
mapGet: () => any;
|
|
17
|
+
mapPost: () => any;
|
|
18
|
+
mapPut: () => any;
|
|
19
|
+
mapPatch: () => any;
|
|
20
|
+
mapDelete: () => any;
|
|
21
|
+
mapView: () => any;
|
|
22
|
+
run: (port: number) => void;
|
|
23
|
+
}
|
|
3
24
|
export interface InlineRoutes {
|
|
4
25
|
get: RouteShorthandMethod;
|
|
5
26
|
post: RouteShorthandMethod;
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
export declare class CacheManager {
|
|
3
|
+
private store;
|
|
4
|
+
private tagsMap;
|
|
5
|
+
private redis;
|
|
6
|
+
constructor(redisInstance?: Redis);
|
|
7
|
+
private redisTagKey;
|
|
8
|
+
get<T>(key: string): Promise<T | null>;
|
|
9
|
+
set<T>(key: string, value: T, tags?: string[], ttl?: number): Promise<void>;
|
|
10
|
+
delete(key: string): Promise<void>;
|
|
11
|
+
invalidateTag(tag: string): Promise<void>;
|
|
12
|
+
}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CacheManager = void 0;
|
|
4
|
+
class CacheManager {
|
|
5
|
+
constructor(redisInstance) {
|
|
6
|
+
this.store = new Map();
|
|
7
|
+
this.tagsMap = new Map();
|
|
8
|
+
this.redis = null;
|
|
9
|
+
this.redis = redisInstance || null;
|
|
10
|
+
}
|
|
11
|
+
redisTagKey(tag) {
|
|
12
|
+
return `cache-tags:${tag}`;
|
|
13
|
+
}
|
|
14
|
+
async get(key) {
|
|
15
|
+
if (this.redis) {
|
|
16
|
+
const val = await this.redis.get(key);
|
|
17
|
+
return val ? JSON.parse(val) : null;
|
|
18
|
+
}
|
|
19
|
+
const cached = this.store.get(key);
|
|
20
|
+
return cached ? cached.data : null;
|
|
21
|
+
}
|
|
22
|
+
async set(key, value, tags = [], ttl = 3600) {
|
|
23
|
+
const entry = {
|
|
24
|
+
data: value,
|
|
25
|
+
timestamp: Date.now(),
|
|
26
|
+
};
|
|
27
|
+
if (this.redis) {
|
|
28
|
+
await this.redis.set(key, JSON.stringify(entry.data), 'EX', ttl);
|
|
29
|
+
for (const tag of tags) {
|
|
30
|
+
await this.redis.sadd(this.redisTagKey(tag), key);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
this.store.set(key, entry);
|
|
35
|
+
for (const tag of tags) {
|
|
36
|
+
if (!this.tagsMap.has(tag))
|
|
37
|
+
this.tagsMap.set(tag, new Set());
|
|
38
|
+
this.tagsMap.get(tag).add(key);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async delete(key) {
|
|
43
|
+
if (this.redis) {
|
|
44
|
+
await this.redis.del(key);
|
|
45
|
+
// Also clean up from any tag sets
|
|
46
|
+
const tagKeys = await this.redis.keys('cache-tags:*');
|
|
47
|
+
for (const tagKey of tagKeys) {
|
|
48
|
+
await this.redis.srem(tagKey, key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
this.store.delete(key);
|
|
53
|
+
for (const keys of this.tagsMap.values()) {
|
|
54
|
+
keys.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async invalidateTag(tag) {
|
|
59
|
+
if (this.redis) {
|
|
60
|
+
const tagKey = this.redisTagKey(tag);
|
|
61
|
+
const keys = await this.redis.smembers(tagKey);
|
|
62
|
+
if (keys.length) {
|
|
63
|
+
await this.redis.del(...keys); // delete all cached keys
|
|
64
|
+
await this.redis.del(tagKey); // delete the tag set
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const keys = this.tagsMap.get(tag);
|
|
69
|
+
if (keys) {
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
this.store.delete(key);
|
|
72
|
+
}
|
|
73
|
+
this.tagsMap.delete(tag);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.CacheManager = CacheManager;
|
package/dist/icore.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { FastifyReply, FastifyRequest, HookHandlerDoneFunction, InjectOptions, L
|
|
|
8
8
|
import { Constructable } from "typedi";
|
|
9
9
|
import { Constructor } from "./helpers";
|
|
10
10
|
import { PathLike } from "fs";
|
|
11
|
-
import { DataSourceOptions } from "typeorm";
|
|
11
|
+
import { DataSource, DataSourceOptions } from "typeorm";
|
|
12
12
|
import { AppMiddleware } from "./middleware";
|
|
13
13
|
import { OpenApiOptions, OpenApiUiOptions } from "./openapi";
|
|
14
14
|
import { IConfig } from "./config";
|
|
@@ -78,19 +78,24 @@ type MultipartOptions = {
|
|
|
78
78
|
} & FastifyMultipartOptions;
|
|
79
79
|
export type TestAppOptions = {
|
|
80
80
|
controllers: Constructor[];
|
|
81
|
+
dataSource?: DataSource;
|
|
81
82
|
};
|
|
82
83
|
export interface AvleonTestAppliction {
|
|
83
84
|
addDataSource: (dataSourceOptions: DataSourceOptions) => void;
|
|
84
85
|
getApp: (options?: TestAppOptions) => any;
|
|
85
|
-
getController: <T>(controller: Constructor<T
|
|
86
|
+
getController: <T>(controller: Constructor<T>, deps: any[]) => T;
|
|
86
87
|
}
|
|
88
|
+
export type AutoControllerOptions = {
|
|
89
|
+
auto: true;
|
|
90
|
+
path?: string;
|
|
91
|
+
};
|
|
87
92
|
export interface IAvleonApplication {
|
|
88
93
|
isDevelopment(): boolean;
|
|
89
94
|
useCors(corsOptions?: FastifyCorsOptions): void;
|
|
90
95
|
useDataSource<T extends IConfig<R>, R = ReturnType<InstanceType<Constructable<T>>["config"]>>(ConfigClass: Constructable<T>, modifyConfig?: (config: R) => R): void;
|
|
91
96
|
useOpenApi<T extends IConfig<R>, R = ReturnType<InstanceType<Constructable<T>>["config"]>>(ConfigClass: Constructable<T>, modifyConfig?: (config: R) => R): void;
|
|
92
|
-
useSwagger(options: OpenApiUiOptions): Promise<void>;
|
|
93
97
|
useMultipart(options: MultipartOptions): void;
|
|
98
|
+
useCache(options: any): void;
|
|
94
99
|
useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]): void;
|
|
95
100
|
useAuthoriztion<T extends any>(middleware: Constructor<T>): void;
|
|
96
101
|
mapRoute<T extends (...args: any[]) => any>(method: "get" | "post" | "put" | "delete", path: string, fn: T): Promise<void>;
|
|
@@ -98,11 +103,15 @@ export interface IAvleonApplication {
|
|
|
98
103
|
mapPost<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
99
104
|
mapPut<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
100
105
|
mapDelete<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
101
|
-
|
|
106
|
+
useControllers(controllers: any[]): any;
|
|
107
|
+
useControllers(controllersOptions: AutoControllerOptions): any;
|
|
108
|
+
useControllers(controllersOrOptions: any[] | AutoControllerOptions): any;
|
|
102
109
|
useStaticFiles(options?: StaticFileOptions): void;
|
|
103
110
|
run(port?: number): Promise<void>;
|
|
104
111
|
getTestApp(): TestApplication;
|
|
105
112
|
}
|
|
113
|
+
type OpenApiConfigClass<T = any> = Constructable<IConfig<T>>;
|
|
114
|
+
type OpenApiConfigInput<T = any> = OpenApiConfigClass<T> | T;
|
|
106
115
|
export declare class AvleonApplication {
|
|
107
116
|
private static instance;
|
|
108
117
|
private static buildOptions;
|
|
@@ -120,6 +129,8 @@ export declare class AvleonApplication {
|
|
|
120
129
|
private appConfig;
|
|
121
130
|
private dataSource?;
|
|
122
131
|
private isMapFeatures;
|
|
132
|
+
private registerControllerAuto;
|
|
133
|
+
private registerControllerPath;
|
|
123
134
|
private metaCache;
|
|
124
135
|
private multipartOptions;
|
|
125
136
|
private constructor();
|
|
@@ -129,7 +140,7 @@ export declare class AvleonApplication {
|
|
|
129
140
|
isDevelopment(): boolean;
|
|
130
141
|
private initSwagger;
|
|
131
142
|
useCors(corsOptions?: FastifyCorsOptions): void;
|
|
132
|
-
useOpenApi<T
|
|
143
|
+
useOpenApi<T = OpenApiUiOptions>(configOrClass: OpenApiConfigInput<T>, modifyConfig?: (config: T) => T): void;
|
|
133
144
|
/**
|
|
134
145
|
* Registers the fastify-multipart plugin with the Fastify instance.
|
|
135
146
|
* This enables handling of multipart/form-data requests, typically used for file uploads.
|
|
@@ -155,6 +166,7 @@ export declare class AvleonApplication {
|
|
|
155
166
|
* @see {@link https://typeorm.io/} for more information about TypeORM.
|
|
156
167
|
*/
|
|
157
168
|
useDataSource<T extends IConfig<R>, R = ReturnType<InstanceType<Constructable<T>>["config"]>>(ConfigClass: Constructable<T>, modifyConfig?: (config: R) => R): void;
|
|
169
|
+
useCache(options: any): void;
|
|
158
170
|
/**
|
|
159
171
|
* Registers an array of middleware classes to be executed before request handlers.
|
|
160
172
|
* It retrieves instances of the middleware classes from the dependency injection container
|
|
@@ -210,12 +222,10 @@ export declare class AvleonApplication {
|
|
|
210
222
|
* @returns
|
|
211
223
|
*/
|
|
212
224
|
private _processMeta;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
225
|
+
private _resolveControllerDir;
|
|
226
|
+
private autoControllers;
|
|
227
|
+
useControllers(controllers: Constructor[] | AutoControllerOptions): void;
|
|
216
228
|
private _mapControllers;
|
|
217
|
-
mapControllersAuto(): void;
|
|
218
|
-
handleRoute(args: any): Promise<void>;
|
|
219
229
|
private mapFn;
|
|
220
230
|
private _handleError;
|
|
221
231
|
mapRoute<T extends (...args: any[]) => any>(method: "get" | "post" | "put" | "delete", path: string | undefined, fn: T): Promise<void>;
|
|
@@ -251,20 +261,19 @@ export interface IAppBuilder {
|
|
|
251
261
|
addDataSource<T extends IConfig<R>, R = ReturnType<InstanceType<Constructable<T>>["config"]>>(ConfigClass: Constructable<T>, modifyConfig?: (config: R) => R): void;
|
|
252
262
|
build<T extends IAvleonApplication>(): T;
|
|
253
263
|
}
|
|
254
|
-
export declare class
|
|
264
|
+
export declare class AvleonTest {
|
|
255
265
|
private static instance;
|
|
256
266
|
private app;
|
|
257
267
|
private dataSourceOptions?;
|
|
258
268
|
private constructor();
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
private getController;
|
|
269
|
+
private addDatasource;
|
|
270
|
+
getController<T>(controller: Constructor<T>, deps?: any[]): T;
|
|
262
271
|
private getService;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
272
|
+
static createTestApplication(options: TestAppOptions): TestApplication;
|
|
273
|
+
static from(app: AvleonApplication): TestApplication;
|
|
274
|
+
static clean(): void;
|
|
266
275
|
}
|
|
267
|
-
export declare class
|
|
276
|
+
export declare class Avleon {
|
|
268
277
|
static createApplication(): AvleonApplication;
|
|
269
278
|
}
|
|
270
279
|
export {};
|
package/dist/icore.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.
|
|
39
|
+
exports.Avleon = exports.AvleonTest = exports.AvleonApplication = void 0;
|
|
40
40
|
/**
|
|
41
41
|
* @copyright 2024
|
|
42
42
|
* @author Tareq Hossain
|
|
@@ -50,7 +50,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
50
50
|
const container_1 = __importStar(require("./container"));
|
|
51
51
|
const helpers_1 = require("./helpers");
|
|
52
52
|
const system_exception_1 = require("./exceptions/system-exception");
|
|
53
|
-
const fs_1 = require("fs");
|
|
54
53
|
const exceptions_1 = require("./exceptions");
|
|
55
54
|
const swagger_1 = __importDefault(require("@fastify/swagger"));
|
|
56
55
|
const config_1 = require("./config");
|
|
@@ -78,6 +77,8 @@ class AvleonApplication {
|
|
|
78
77
|
this.authorizeMiddleware = undefined;
|
|
79
78
|
this.dataSource = undefined;
|
|
80
79
|
this.isMapFeatures = false;
|
|
80
|
+
this.registerControllerAuto = false;
|
|
81
|
+
this.registerControllerPath = './src';
|
|
81
82
|
this.metaCache = new Map();
|
|
82
83
|
this.app = (0, fastify_1.default)();
|
|
83
84
|
this.appConfig = new config_1.AppConfig();
|
|
@@ -153,11 +154,24 @@ class AvleonApplication {
|
|
|
153
154
|
useCors(corsOptions = {}) {
|
|
154
155
|
this.app.register(cors_1.default, corsOptions);
|
|
155
156
|
}
|
|
156
|
-
useOpenApi(
|
|
157
|
-
|
|
157
|
+
useOpenApi(configOrClass, modifyConfig) {
|
|
158
|
+
let openApiConfig;
|
|
159
|
+
const isClass = (input) => {
|
|
160
|
+
var _a;
|
|
161
|
+
return (typeof input === 'function' &&
|
|
162
|
+
typeof input.prototype === 'object' &&
|
|
163
|
+
((_a = input.prototype) === null || _a === void 0 ? void 0 : _a.constructor) === input);
|
|
164
|
+
};
|
|
165
|
+
if (isClass(configOrClass)) {
|
|
166
|
+
// It's a class constructor
|
|
167
|
+
openApiConfig = this.appConfig.get(configOrClass);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// It's a plain object
|
|
171
|
+
openApiConfig = configOrClass;
|
|
172
|
+
}
|
|
158
173
|
if (modifyConfig) {
|
|
159
|
-
|
|
160
|
-
this.globalSwaggerOptions = modifiedConfig;
|
|
174
|
+
this.globalSwaggerOptions = modifyConfig(openApiConfig);
|
|
161
175
|
}
|
|
162
176
|
else {
|
|
163
177
|
this.globalSwaggerOptions = openApiConfig;
|
|
@@ -208,6 +222,8 @@ class AvleonApplication {
|
|
|
208
222
|
this.dataSource = datasource;
|
|
209
223
|
typedi_1.default.set("idatasource", datasource);
|
|
210
224
|
}
|
|
225
|
+
useCache(options) {
|
|
226
|
+
}
|
|
211
227
|
/**
|
|
212
228
|
* Registers an array of middleware classes to be executed before request handlers.
|
|
213
229
|
* It retrieves instances of the middleware classes from the dependency injection container
|
|
@@ -463,32 +479,59 @@ class AvleonApplication {
|
|
|
463
479
|
this.metaCache.set(cacheKey, meta);
|
|
464
480
|
return meta;
|
|
465
481
|
}
|
|
466
|
-
|
|
467
|
-
const
|
|
468
|
-
|
|
482
|
+
_resolveControllerDir(dir) {
|
|
483
|
+
const isTsNode = process.env.TS_NODE_DEV ||
|
|
484
|
+
process.env.TS_NODE_PROJECT ||
|
|
485
|
+
process[Symbol.for("ts-node.register.instance")];
|
|
486
|
+
const controllerDir = path_1.default.join(process.cwd(), this.registerControllerPath);
|
|
487
|
+
return isTsNode ? controllerDir : controllerDir.replace('src', 'dist');
|
|
488
|
+
}
|
|
489
|
+
async autoControllers(controllersPath) {
|
|
490
|
+
const conDir = this._resolveControllerDir(controllersPath);
|
|
491
|
+
const files = await promises_1.default.readdir(conDir, { recursive: true });
|
|
469
492
|
for (const file of files) {
|
|
493
|
+
const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
|
|
494
|
+
if (isTestFile)
|
|
495
|
+
continue;
|
|
470
496
|
if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
|
|
471
|
-
const filePath = path_1.default.join(
|
|
497
|
+
const filePath = path_1.default.join(conDir, file);
|
|
472
498
|
const module = await Promise.resolve(`${filePath}`).then(s => __importStar(require(s)));
|
|
473
499
|
for (const exported of Object.values(module)) {
|
|
474
500
|
if (typeof exported === "function" && (0, container_1.isApiController)(exported)) {
|
|
475
|
-
|
|
501
|
+
console.log('adding', exported.name);
|
|
502
|
+
if (!this.controllers.some(con => exported.name == con.name)) {
|
|
503
|
+
this.controllers.push(exported);
|
|
504
|
+
}
|
|
505
|
+
//this.buildController(exported);
|
|
476
506
|
}
|
|
477
507
|
}
|
|
478
508
|
}
|
|
479
509
|
}
|
|
480
510
|
}
|
|
481
|
-
|
|
482
|
-
|
|
511
|
+
useControllers(controllers) {
|
|
512
|
+
if (Array.isArray(controllers)) {
|
|
513
|
+
this.controllers = controllers;
|
|
514
|
+
controllers.forEach(controller => {
|
|
515
|
+
if (!this.controllers.includes(controller)) {
|
|
516
|
+
this.controllers.push(controller);
|
|
517
|
+
}
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.registerControllerAuto = true;
|
|
522
|
+
if (controllers.path) {
|
|
523
|
+
this.registerControllerPath = controllers.path;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
483
526
|
}
|
|
484
527
|
// addFeature(feature:{controllers:Function[]}){
|
|
485
528
|
// feature.controllers.forEach(c=> this.controllers.push(c))
|
|
486
529
|
// }
|
|
487
|
-
mapFeature()
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
530
|
+
// mapFeature(){
|
|
531
|
+
// if(!this.isMapFeatures){
|
|
532
|
+
// this.isMapFeatures = true;
|
|
533
|
+
// }
|
|
534
|
+
// }
|
|
492
535
|
async _mapControllers() {
|
|
493
536
|
if (this.controllers.length > 0) {
|
|
494
537
|
for (let controller of this.controllers) {
|
|
@@ -501,13 +544,10 @@ class AvleonApplication {
|
|
|
501
544
|
}
|
|
502
545
|
}
|
|
503
546
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
async handleRoute(args) { }
|
|
547
|
+
// useControllersAuto(controllerPath?:string) {
|
|
548
|
+
// this.registerControllerAuto = true;
|
|
549
|
+
// //this.autoControllers();
|
|
550
|
+
// }
|
|
511
551
|
async mapFn(fn) {
|
|
512
552
|
const original = fn;
|
|
513
553
|
fn = function () { };
|
|
@@ -556,7 +596,7 @@ class AvleonApplication {
|
|
|
556
596
|
middlewares: [],
|
|
557
597
|
schema: {},
|
|
558
598
|
});
|
|
559
|
-
this.mapFn(fn);
|
|
599
|
+
// this.mapFn(fn);
|
|
560
600
|
const route = {
|
|
561
601
|
useMiddleware: (middlewares) => {
|
|
562
602
|
const midds = Array.isArray(middlewares) ? middlewares : [middlewares];
|
|
@@ -613,6 +653,9 @@ class AvleonApplication {
|
|
|
613
653
|
if (this.isMapFeatures) {
|
|
614
654
|
this._mapFeatures();
|
|
615
655
|
}
|
|
656
|
+
if (this.registerControllerAuto) {
|
|
657
|
+
await this.autoControllers();
|
|
658
|
+
}
|
|
616
659
|
await this._mapControllers();
|
|
617
660
|
this.rMap.forEach((value, key) => {
|
|
618
661
|
const [m, r] = key.split(":");
|
|
@@ -680,7 +723,11 @@ class AvleonApplication {
|
|
|
680
723
|
patch: async (url, options) => this.app.inject({ method: "PATCH", url, ...options }),
|
|
681
724
|
delete: async (url, options) => this.app.inject({ method: "DELETE", url, ...options }),
|
|
682
725
|
options: async (url, options) => this.app.inject({ method: "OPTIONS", url, ...options }),
|
|
683
|
-
getController: (controller) => {
|
|
726
|
+
getController: (controller, deps = []) => {
|
|
727
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
728
|
+
deps.forEach((dep, i) => {
|
|
729
|
+
typedi_1.default.set(paramTypes[i], dep);
|
|
730
|
+
});
|
|
684
731
|
return typedi_1.default.get(controller);
|
|
685
732
|
},
|
|
686
733
|
};
|
|
@@ -693,44 +740,42 @@ class AvleonApplication {
|
|
|
693
740
|
}
|
|
694
741
|
exports.AvleonApplication = AvleonApplication;
|
|
695
742
|
AvleonApplication.buildOptions = {};
|
|
696
|
-
class
|
|
743
|
+
class AvleonTest {
|
|
697
744
|
constructor() {
|
|
698
745
|
process.env.NODE_ENV = "test";
|
|
699
746
|
}
|
|
700
|
-
static createBuilder() {
|
|
701
|
-
if (!TestBuilder.instance) {
|
|
702
|
-
TestBuilder.instance = new TestBuilder();
|
|
703
|
-
}
|
|
704
|
-
return TestBuilder.instance;
|
|
705
|
-
}
|
|
706
747
|
addDatasource(options) {
|
|
707
748
|
this.dataSourceOptions = options;
|
|
708
749
|
}
|
|
709
|
-
getController(controller) {
|
|
750
|
+
getController(controller, deps = []) {
|
|
751
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
752
|
+
deps.forEach((dep, i) => {
|
|
753
|
+
typedi_1.default.set(paramTypes[i], dep);
|
|
754
|
+
});
|
|
710
755
|
return typedi_1.default.get(controller);
|
|
711
756
|
}
|
|
712
757
|
getService(service) {
|
|
713
758
|
return typedi_1.default.get(service);
|
|
714
759
|
}
|
|
715
|
-
|
|
760
|
+
static createTestApplication(options) {
|
|
716
761
|
const app = AvleonApplication.getInternalApp({
|
|
717
|
-
dataSourceOptions:
|
|
762
|
+
dataSourceOptions: options.dataSource ? options.dataSource : null,
|
|
718
763
|
});
|
|
719
|
-
app.
|
|
764
|
+
app.useControllers([...options.controllers]);
|
|
720
765
|
return app.getTestApp();
|
|
721
766
|
}
|
|
722
|
-
|
|
767
|
+
static from(app) {
|
|
723
768
|
return app.getTestApp();
|
|
724
769
|
}
|
|
725
|
-
|
|
726
|
-
|
|
770
|
+
static clean() {
|
|
771
|
+
typedi_1.default.reset();
|
|
727
772
|
}
|
|
728
773
|
}
|
|
729
|
-
exports.
|
|
730
|
-
class
|
|
774
|
+
exports.AvleonTest = AvleonTest;
|
|
775
|
+
class Avleon {
|
|
731
776
|
static createApplication() {
|
|
732
777
|
const app = AvleonApplication.getApp();
|
|
733
778
|
return app;
|
|
734
779
|
}
|
|
735
780
|
}
|
|
736
|
-
exports.
|
|
781
|
+
exports.Avleon = Avleon;
|
package/dist/testing.js
CHANGED
|
@@ -115,7 +115,7 @@ class AvleonTestBuilder {
|
|
|
115
115
|
dataSourceOptions: this.testOptions.dataSourceOptions,
|
|
116
116
|
});
|
|
117
117
|
// Map controllers
|
|
118
|
-
app.
|
|
118
|
+
app.useControllers(this.controllers);
|
|
119
119
|
// Get test application
|
|
120
120
|
return app.getTestApp();
|
|
121
121
|
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avleon/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
|
+
"types": ["./dist/icore.d.ts"],
|
|
5
6
|
"scripts": {
|
|
6
7
|
"build": "npm run clean && tsc",
|
|
7
|
-
"clean": "
|
|
8
|
+
"clean": "rimraf dist",
|
|
8
9
|
"watch": "tsc-watch",
|
|
9
10
|
"test": "jest",
|
|
10
11
|
"test:watch": "jest --watch"
|
|
11
12
|
},
|
|
12
|
-
"keywords": [],
|
|
13
|
+
"keywords": ["restapi", "avleon", "backend","fastify"],
|
|
13
14
|
"author": "Tareq Hossain",
|
|
14
15
|
"license": "ISC",
|
|
15
16
|
"devDependencies": {
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
"@types/node": "^20.17.25",
|
|
18
19
|
"class-transformer": "^0.5.1",
|
|
19
20
|
"class-validator": "^0.14.1",
|
|
21
|
+
"ioredis": "^5.6.1",
|
|
20
22
|
"jest": "^29.7.0",
|
|
21
23
|
"nodemon": "^3.1.7",
|
|
22
24
|
"sharp": "^0.33.5",
|
|
@@ -42,15 +44,22 @@
|
|
|
42
44
|
"reflect-metadata": "^0.2.2",
|
|
43
45
|
"typedi": "^0.10.0"
|
|
44
46
|
},
|
|
45
|
-
"peerDependencies": {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@scalar/fastify-api-reference": "*",
|
|
49
|
+
"ioredis": "*",
|
|
50
|
+
"sharp": "*"
|
|
51
|
+
},
|
|
52
|
+
"peerDependenciesMeta": {
|
|
53
|
+
"@scalar/fastify-api-reference": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"ioredis": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"sharp": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
62
|
+
},
|
|
54
63
|
"directories": {
|
|
55
64
|
"test": "tests"
|
|
56
65
|
},
|
package/src/application.ts
CHANGED
|
@@ -2,7 +2,36 @@ import Container from "typedi";
|
|
|
2
2
|
import { Constructor } from "./helpers";
|
|
3
3
|
import fastify, { FastifyInstance, RouteShorthandMethod } from "fastify";
|
|
4
4
|
|
|
5
|
+
export interface AvleonApplication{
|
|
6
|
+
// all public
|
|
7
|
+
useCors: () => void
|
|
8
|
+
useOpenApi:() => void
|
|
9
|
+
useView:()=> void;
|
|
10
|
+
useAuth:() => void
|
|
11
|
+
useMultipart:()=> void
|
|
12
|
+
useDataSource: () => void
|
|
13
|
+
useMiddlewares: () => void
|
|
14
|
+
useControllers: () => void
|
|
15
|
+
useAutoControllers:() => void
|
|
16
|
+
useStaticFiles: () => void
|
|
17
|
+
useCustomErrorHandler: () => void
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
// all mapping
|
|
23
|
+
mapGroup:() => any;
|
|
24
|
+
mapGet:()=> any;
|
|
25
|
+
mapPost:()=> any;
|
|
26
|
+
mapPut: () => any;
|
|
27
|
+
mapPatch: () => any;
|
|
28
|
+
mapDelete: () => any;
|
|
29
|
+
mapView: () => any;
|
|
30
|
+
|
|
31
|
+
// run
|
|
32
|
+
run:(port:number) => void
|
|
5
33
|
|
|
34
|
+
}
|
|
6
35
|
|
|
7
36
|
|
|
8
37
|
export interface InlineRoutes{
|
package/src/cache.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
|
|
3
|
+
type CacheEntry<T = any> = {
|
|
4
|
+
data: T;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export class CacheManager {
|
|
9
|
+
private store = new Map<string, CacheEntry>();
|
|
10
|
+
private tagsMap = new Map<string, Set<string>>();
|
|
11
|
+
private redis: Redis | null = null;
|
|
12
|
+
|
|
13
|
+
constructor(redisInstance?: Redis) {
|
|
14
|
+
this.redis = redisInstance || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private redisTagKey(tag: string) {
|
|
18
|
+
return `cache-tags:${tag}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get<T>(key: string): Promise<T | null> {
|
|
22
|
+
if (this.redis) {
|
|
23
|
+
const val = await this.redis.get(key);
|
|
24
|
+
return val ? JSON.parse(val) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const cached = this.store.get(key);
|
|
28
|
+
return cached ? cached.data : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async set<T>(
|
|
32
|
+
key: string,
|
|
33
|
+
value: T,
|
|
34
|
+
tags: string[] = [],
|
|
35
|
+
ttl: number = 3600
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
const entry: CacheEntry<T> = {
|
|
38
|
+
data: value,
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (this.redis) {
|
|
43
|
+
await this.redis.set(key, JSON.stringify(entry.data), 'EX', ttl);
|
|
44
|
+
for (const tag of tags) {
|
|
45
|
+
await this.redis.sadd(this.redisTagKey(tag), key);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
this.store.set(key, entry);
|
|
49
|
+
for (const tag of tags) {
|
|
50
|
+
if (!this.tagsMap.has(tag)) this.tagsMap.set(tag, new Set());
|
|
51
|
+
this.tagsMap.get(tag)!.add(key);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async delete(key: string): Promise<void> {
|
|
57
|
+
if (this.redis) {
|
|
58
|
+
await this.redis.del(key);
|
|
59
|
+
|
|
60
|
+
// Also clean up from any tag sets
|
|
61
|
+
const tagKeys = await this.redis.keys('cache-tags:*');
|
|
62
|
+
for (const tagKey of tagKeys) {
|
|
63
|
+
await this.redis.srem(tagKey, key);
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
this.store.delete(key);
|
|
67
|
+
for (const keys of this.tagsMap.values()) {
|
|
68
|
+
keys.delete(key);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async invalidateTag(tag: string): Promise<void> {
|
|
74
|
+
if (this.redis) {
|
|
75
|
+
const tagKey = this.redisTagKey(tag);
|
|
76
|
+
const keys = await this.redis.smembers(tagKey);
|
|
77
|
+
if (keys.length) {
|
|
78
|
+
await this.redis.del(...keys); // delete all cached keys
|
|
79
|
+
await this.redis.del(tagKey); // delete the tag set
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
const keys = this.tagsMap.get(tag);
|
|
83
|
+
if (keys) {
|
|
84
|
+
for (const key of keys) {
|
|
85
|
+
this.store.delete(key);
|
|
86
|
+
}
|
|
87
|
+
this.tagsMap.delete(tag);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/config.ts
CHANGED
package/src/controller.ts
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
* @url https://github.com/xtareq
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
import Container, { Service } from "typedi";
|
|
10
9
|
import container, {
|
|
11
10
|
API_CONTROLLER_METADATA_KEY,
|
|
@@ -100,7 +99,7 @@ export function ApiController(
|
|
|
100
99
|
options?: ControllerOptions,
|
|
101
100
|
): ClassDecorator;
|
|
102
101
|
export function ApiController(
|
|
103
|
-
pathOrOptions: Function |string | ControllerOptions = "/",
|
|
102
|
+
pathOrOptions: Function | string | ControllerOptions = "/",
|
|
104
103
|
mayBeOptions?: ControllerOptions,
|
|
105
104
|
): any {
|
|
106
105
|
if (typeof pathOrOptions == 'function') {
|
package/src/icore.ts
CHANGED
|
@@ -159,23 +159,29 @@ type AvleonApp = FastifyInstance;
|
|
|
159
159
|
|
|
160
160
|
export type TestAppOptions = {
|
|
161
161
|
controllers: Constructor[];
|
|
162
|
+
dataSource?: DataSource
|
|
162
163
|
};
|
|
163
164
|
|
|
164
165
|
export interface AvleonTestAppliction {
|
|
165
166
|
addDataSource: (dataSourceOptions: DataSourceOptions) => void;
|
|
166
167
|
getApp: (options?: TestAppOptions) => any;
|
|
167
|
-
getController: <T>(controller: Constructor<T
|
|
168
|
+
getController: <T>(controller: Constructor<T>, deps: any[]) => T;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type AutoControllerOptions = {
|
|
172
|
+
auto: true,
|
|
173
|
+
path?: string
|
|
168
174
|
}
|
|
169
175
|
export interface IAvleonApplication {
|
|
170
176
|
isDevelopment(): boolean;
|
|
171
177
|
useCors(corsOptions?: FastifyCorsOptions): void;
|
|
172
178
|
useDataSource<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
>(
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
): void;
|
|
179
|
+
T extends IConfig<R>,
|
|
180
|
+
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
181
|
+
>(
|
|
182
|
+
ConfigClass: Constructable<T>,
|
|
183
|
+
modifyConfig?: (config: R) => R,
|
|
184
|
+
): void;
|
|
179
185
|
useOpenApi<
|
|
180
186
|
T extends IConfig<R>,
|
|
181
187
|
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
@@ -183,8 +189,9 @@ export interface IAvleonApplication {
|
|
|
183
189
|
ConfigClass: Constructable<T>,
|
|
184
190
|
modifyConfig?: (config: R) => R,
|
|
185
191
|
): void;
|
|
186
|
-
|
|
192
|
+
|
|
187
193
|
useMultipart(options: MultipartOptions): void;
|
|
194
|
+
useCache(options: any): void
|
|
188
195
|
|
|
189
196
|
useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]): void;
|
|
190
197
|
useAuthoriztion<T extends any>(middleware: Constructor<T>): void;
|
|
@@ -198,12 +205,15 @@ export interface IAvleonApplication {
|
|
|
198
205
|
mapPost<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
199
206
|
mapPut<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
200
207
|
mapDelete<T extends (...args: any[]) => any>(path: string, fn: T): any;
|
|
201
|
-
|
|
208
|
+
useControllers(controllers: any[]): any;
|
|
209
|
+
useControllers(controllersOptions: AutoControllerOptions): any;
|
|
210
|
+
useControllers(controllersOrOptions: any[] | AutoControllerOptions): any;
|
|
202
211
|
useStaticFiles(options?: StaticFileOptions): void;
|
|
203
212
|
run(port?: number): Promise<void>;
|
|
204
213
|
getTestApp(): TestApplication;
|
|
205
214
|
}
|
|
206
|
-
|
|
215
|
+
type OpenApiConfigClass<T = any> = Constructable<IConfig<T>>;
|
|
216
|
+
type OpenApiConfigInput<T = any> = OpenApiConfigClass<T> | T;
|
|
207
217
|
// InternalApplication
|
|
208
218
|
export class AvleonApplication {
|
|
209
219
|
private static instance: AvleonApplication;
|
|
@@ -222,6 +232,8 @@ export class AvleonApplication {
|
|
|
222
232
|
private appConfig: AppConfig;
|
|
223
233
|
private dataSource?: DataSource = undefined;
|
|
224
234
|
private isMapFeatures = false;
|
|
235
|
+
private registerControllerAuto = false;
|
|
236
|
+
private registerControllerPath = './src';
|
|
225
237
|
|
|
226
238
|
private metaCache = new Map<string, MethodParamMeta>();
|
|
227
239
|
private multipartOptions: FastifyMultipartOptions | undefined;
|
|
@@ -314,31 +326,48 @@ export class AvleonApplication {
|
|
|
314
326
|
useCors(corsOptions: FastifyCorsOptions = {}) {
|
|
315
327
|
this.app.register(cors, corsOptions);
|
|
316
328
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
329
|
+
|
|
330
|
+
useOpenApi<T = OpenApiUiOptions>(
|
|
331
|
+
configOrClass: OpenApiConfigInput<T>,
|
|
332
|
+
modifyConfig?: (config: T) => T
|
|
333
|
+
) {
|
|
334
|
+
let openApiConfig: T;
|
|
335
|
+
|
|
336
|
+
const isClass = (input: any): input is OpenApiConfigClass<T> => {
|
|
337
|
+
return (
|
|
338
|
+
typeof input === 'function' &&
|
|
339
|
+
typeof input.prototype === 'object' &&
|
|
340
|
+
input.prototype?.constructor === input
|
|
341
|
+
);
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
if (isClass(configOrClass)) {
|
|
345
|
+
// It's a class constructor
|
|
346
|
+
openApiConfig = this.appConfig.get(configOrClass);
|
|
347
|
+
} else {
|
|
348
|
+
// It's a plain object
|
|
349
|
+
openApiConfig = configOrClass as T;
|
|
350
|
+
}
|
|
351
|
+
|
|
322
352
|
if (modifyConfig) {
|
|
323
|
-
|
|
324
|
-
this.globalSwaggerOptions = modifiedConfig;
|
|
353
|
+
this.globalSwaggerOptions = modifyConfig(openApiConfig);
|
|
325
354
|
} else {
|
|
326
355
|
this.globalSwaggerOptions = openApiConfig;
|
|
327
356
|
}
|
|
357
|
+
|
|
328
358
|
this.hasSwagger = true;
|
|
329
359
|
}
|
|
330
360
|
|
|
331
361
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
*/
|
|
362
|
+
/**
|
|
363
|
+
* Registers the fastify-multipart plugin with the Fastify instance.
|
|
364
|
+
* This enables handling of multipart/form-data requests, typically used for file uploads.
|
|
365
|
+
*
|
|
366
|
+
* @param {MultipartOptions} options - Options to configure the fastify-multipart plugin.
|
|
367
|
+
* @param {FastifyInstance} this.app - The Fastify instance to register the plugin with.
|
|
368
|
+
* @property {MultipartOptions} this.multipartOptions - Stores the provided multipart options.
|
|
369
|
+
* @see {@link https://github.com/fastify/fastify-multipart} for more details on available options.
|
|
370
|
+
*/
|
|
342
371
|
useMultipart(options: MultipartOptions) {
|
|
343
372
|
this.multipartOptions = options;
|
|
344
373
|
this.app.register(fastifyMultipart, {
|
|
@@ -348,57 +377,60 @@ export class AvleonApplication {
|
|
|
348
377
|
}
|
|
349
378
|
|
|
350
379
|
|
|
351
|
-
/**
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Configures and initializes a TypeORM DataSource based on the provided configuration class.
|
|
382
|
+
* It retrieves the configuration from the application's configuration service and allows for optional modification.
|
|
383
|
+
* The initialized DataSource is then stored and registered within a dependency injection container.
|
|
384
|
+
*
|
|
385
|
+
* @template T - A generic type extending the `IConfig` interface, representing the configuration class.
|
|
386
|
+
* @template R - A generic type representing the return type of the configuration method of the `ConfigClass`.
|
|
387
|
+
* @param {Constructable<T>} ConfigClass - The constructor of the configuration class to be used for creating the DataSource.
|
|
388
|
+
* @param {(config: R) => R} [modifyConfig] - An optional function that takes the initial configuration and returns a modified configuration.
|
|
389
|
+
* @returns {void}
|
|
390
|
+
* @property {DataSourceOptions} this.dataSourceOptions - Stores the final DataSource options after potential modification.
|
|
391
|
+
* @property {DataSource} this.dataSource - Stores the initialized TypeORM DataSource instance.
|
|
392
|
+
* @see {@link https://typeorm.io/} for more information about TypeORM.
|
|
393
|
+
*/
|
|
365
394
|
useDataSource<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
>(ConfigClass: Constructable<T>, modifyConfig?: (config: R) => R) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
395
|
+
T extends IConfig<R>,
|
|
396
|
+
R = ReturnType<InstanceType<Constructable<T>>["config"]>,
|
|
397
|
+
>(ConfigClass: Constructable<T>, modifyConfig?: (config: R) => R) {
|
|
398
|
+
const dsConfig: R = this.appConfig.get(ConfigClass);
|
|
399
|
+
if (modifyConfig) {
|
|
400
|
+
const modifiedConfig: R = modifyConfig(dsConfig);
|
|
401
|
+
this.dataSourceOptions = modifiedConfig as unknown as DataSourceOptions;
|
|
402
|
+
} else {
|
|
403
|
+
this.dataSourceOptions = dsConfig as unknown as DataSourceOptions;
|
|
404
|
+
}
|
|
376
405
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
406
|
+
const typeorm = require("typeorm");
|
|
407
|
+
const datasource = new typeorm.DataSource(
|
|
408
|
+
dsConfig,
|
|
409
|
+
) as DataSource;
|
|
381
410
|
|
|
382
|
-
|
|
411
|
+
this.dataSource = datasource;
|
|
383
412
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}
|
|
413
|
+
Container.set<DataSource>("idatasource",
|
|
414
|
+
datasource,
|
|
415
|
+
);
|
|
416
|
+
}
|
|
388
417
|
|
|
418
|
+
useCache(options: any) {
|
|
389
419
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Registers an array of middleware classes to be executed before request handlers.
|
|
424
|
+
* It retrieves instances of the middleware classes from the dependency injection container
|
|
425
|
+
* and adds them as 'preHandler' hooks to the Fastify application.
|
|
426
|
+
*
|
|
427
|
+
* @template T - A generic type extending the `AppMiddleware` interface, representing the middleware class.
|
|
428
|
+
* @param {Constructor<T>[]} mclasses - An array of middleware class constructors to be registered.
|
|
429
|
+
* @returns {void}
|
|
430
|
+
* @property {Map<string, T>} this.middlewares - Stores the registered middleware instances, keyed by their class names.
|
|
431
|
+
* @see {@link https://www.fastify.io/docs/latest/Reference/Hooks/#prehandler} for more information about Fastify preHandler hooks.
|
|
432
|
+
*/
|
|
433
|
+
useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
402
434
|
for (const mclass of mclasses) {
|
|
403
435
|
const cls = Container.get<T>(mclass);
|
|
404
436
|
this.middlewares.set(mclass.name, cls);
|
|
@@ -407,31 +439,31 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
407
439
|
}
|
|
408
440
|
|
|
409
441
|
|
|
410
|
-
/**
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
442
|
+
/**
|
|
443
|
+
* Registers a middleware constructor to be used for authorization purposes.
|
|
444
|
+
* The specific implementation and usage of this middleware will depend on the application's authorization logic.
|
|
445
|
+
*
|
|
446
|
+
* @template T - A generic type representing the constructor of the authorization middleware.
|
|
447
|
+
* @param {Constructor<T>} middleware - The constructor of the middleware to be used for authorization.
|
|
448
|
+
* @returns {void}
|
|
449
|
+
* @property {any} this.authorizeMiddleware - Stores the constructor of the authorization middleware.
|
|
450
|
+
*/
|
|
419
451
|
useAuthoriztion<T extends any>(middleware: Constructor<T>) {
|
|
420
452
|
this.authorizeMiddleware = middleware as any;
|
|
421
453
|
}
|
|
422
454
|
|
|
423
455
|
|
|
424
456
|
|
|
425
|
-
/**
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
457
|
+
/**
|
|
458
|
+
* Registers the `@fastify/static` plugin to serve static files.
|
|
459
|
+
* It configures the root directory and URL prefix for serving static assets.
|
|
460
|
+
*
|
|
461
|
+
* @param {StaticFileOptions} [options={ path: undefined, prefix: undefined }] - Optional configuration for serving static files.
|
|
462
|
+
* @param {string} [options.path] - The absolute path to the static files directory. Defaults to 'process.cwd()/public'.
|
|
463
|
+
* @param {string} [options.prefix] - The URL prefix for serving static files. Defaults to '/static/'.
|
|
464
|
+
* @returns {void}
|
|
465
|
+
* @see {@link https://github.com/fastify/fastify-static} for more details on available options.
|
|
466
|
+
*/
|
|
435
467
|
useStaticFiles(
|
|
436
468
|
options: StaticFileOptions = { path: undefined, prefix: undefined },
|
|
437
469
|
) {
|
|
@@ -535,7 +567,7 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
535
567
|
handler: async (req, res) => {
|
|
536
568
|
let reqClone = req as IRequest;
|
|
537
569
|
|
|
538
|
-
|
|
570
|
+
|
|
539
571
|
// class level authrization
|
|
540
572
|
if (authClsMeata.authorize && this.authorizeMiddleware) {
|
|
541
573
|
const cls = container.get(this.authorizeMiddleware) as any;
|
|
@@ -714,37 +746,74 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
714
746
|
return meta;
|
|
715
747
|
}
|
|
716
748
|
|
|
717
|
-
|
|
718
|
-
const
|
|
719
|
-
|
|
749
|
+
private _resolveControllerDir(dir?: string) {
|
|
750
|
+
const isTsNode =
|
|
751
|
+
process.env.TS_NODE_DEV ||
|
|
752
|
+
process.env.TS_NODE_PROJECT ||
|
|
753
|
+
(process as any)[Symbol.for("ts-node.register.instance")];
|
|
754
|
+
const controllerDir = path.join(
|
|
755
|
+
process.cwd(),
|
|
756
|
+
this.registerControllerPath
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
return isTsNode ? controllerDir : controllerDir.replace('src', 'dist')
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
private async autoControllers(controllersPath?: string) {
|
|
763
|
+
const conDir = this._resolveControllerDir(controllersPath);
|
|
764
|
+
|
|
765
|
+
const files = await fs.readdir(conDir, { recursive: true });
|
|
720
766
|
for (const file of files) {
|
|
767
|
+
const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
|
|
768
|
+
if (isTestFile) continue;
|
|
721
769
|
if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
|
|
722
|
-
const filePath = path.join(
|
|
770
|
+
const filePath = path.join(conDir, file);
|
|
723
771
|
const module = await import(filePath);
|
|
724
772
|
for (const exported of Object.values(module)) {
|
|
725
773
|
if (typeof exported === "function" && isApiController(exported)) {
|
|
726
|
-
|
|
774
|
+
console.log('adding', exported.name)
|
|
775
|
+
if (!this.controllers.some(con => exported.name == con.name)) {
|
|
776
|
+
this.controllers.push(exported)
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
//this.buildController(exported);
|
|
727
780
|
}
|
|
728
781
|
}
|
|
729
782
|
}
|
|
730
783
|
}
|
|
731
784
|
}
|
|
732
785
|
|
|
733
|
-
|
|
734
|
-
|
|
786
|
+
useControllers(controllers: Constructor[] | AutoControllerOptions) {
|
|
787
|
+
|
|
788
|
+
if (Array.isArray(controllers)) {
|
|
789
|
+
this.controllers = controllers;
|
|
790
|
+
|
|
791
|
+
controllers.forEach(controller => {
|
|
792
|
+
if (!this.controllers.includes(controller)) {
|
|
793
|
+
this.controllers.push(controller)
|
|
794
|
+
}
|
|
795
|
+
})
|
|
796
|
+
} else {
|
|
797
|
+
this.registerControllerAuto = true;
|
|
798
|
+
if (controllers.path) {
|
|
799
|
+
this.registerControllerPath = controllers.path;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
735
803
|
}
|
|
736
804
|
|
|
737
805
|
// addFeature(feature:{controllers:Function[]}){
|
|
738
806
|
// feature.controllers.forEach(c=> this.controllers.push(c))
|
|
739
807
|
// }
|
|
740
808
|
|
|
741
|
-
mapFeature(){
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
}
|
|
809
|
+
// mapFeature(){
|
|
810
|
+
// if(!this.isMapFeatures){
|
|
811
|
+
// this.isMapFeatures = true;
|
|
812
|
+
// }
|
|
813
|
+
// }
|
|
746
814
|
|
|
747
815
|
private async _mapControllers() {
|
|
816
|
+
|
|
748
817
|
if (this.controllers.length > 0) {
|
|
749
818
|
for (let controller of this.controllers) {
|
|
750
819
|
if (isApiController(controller)) {
|
|
@@ -756,14 +825,11 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
756
825
|
}
|
|
757
826
|
}
|
|
758
827
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
}
|
|
764
|
-
}
|
|
828
|
+
// useControllersAuto(controllerPath?:string) {
|
|
829
|
+
// this.registerControllerAuto = true;
|
|
830
|
+
// //this.autoControllers();
|
|
831
|
+
// }
|
|
765
832
|
|
|
766
|
-
async handleRoute(args: any) { }
|
|
767
833
|
|
|
768
834
|
private async mapFn(fn: Function) {
|
|
769
835
|
const original = fn;
|
|
@@ -827,8 +893,7 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
827
893
|
middlewares: [],
|
|
828
894
|
schema: {},
|
|
829
895
|
});
|
|
830
|
-
|
|
831
|
-
this.mapFn(fn);
|
|
896
|
+
// this.mapFn(fn);
|
|
832
897
|
|
|
833
898
|
const route = {
|
|
834
899
|
useMiddleware: <M extends AppMiddleware>(
|
|
@@ -878,7 +943,7 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
878
943
|
}
|
|
879
944
|
|
|
880
945
|
|
|
881
|
-
private _mapFeatures(){
|
|
946
|
+
private _mapFeatures() {
|
|
882
947
|
const features = Container.get('features');
|
|
883
948
|
console.log('Features', features);
|
|
884
949
|
}
|
|
@@ -889,7 +954,7 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
889
954
|
}
|
|
890
955
|
}
|
|
891
956
|
|
|
892
|
-
async run(port: number = 4000, fn?:CallableFunction): Promise<void> {
|
|
957
|
+
async run(port: number = 4000, fn?: CallableFunction): Promise<void> {
|
|
893
958
|
if (this.alreadyRun) throw new SystemUseError("App already running");
|
|
894
959
|
this.alreadyRun = true;
|
|
895
960
|
|
|
@@ -897,9 +962,13 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
897
962
|
await this.initSwagger(this.globalSwaggerOptions);
|
|
898
963
|
}
|
|
899
964
|
await this.initializeDatabase();
|
|
900
|
-
if(this.isMapFeatures){
|
|
965
|
+
if (this.isMapFeatures) {
|
|
901
966
|
this._mapFeatures();
|
|
902
967
|
}
|
|
968
|
+
|
|
969
|
+
if (this.registerControllerAuto) {
|
|
970
|
+
await this.autoControllers();
|
|
971
|
+
}
|
|
903
972
|
await this._mapControllers();
|
|
904
973
|
|
|
905
974
|
this.rMap.forEach((value, key) => {
|
|
@@ -975,8 +1044,15 @@ useMiddlewares<T extends AppMiddleware>(mclasses: Constructor<T>[]) {
|
|
|
975
1044
|
this.app.inject({ method: "DELETE", url, ...options }),
|
|
976
1045
|
options: async (url: string, options?: InjectOptions) =>
|
|
977
1046
|
this.app.inject({ method: "OPTIONS", url, ...options }),
|
|
978
|
-
getController: <T>(controller: Constructor<T
|
|
979
|
-
|
|
1047
|
+
getController: <T>(controller: Constructor<T>, deps: any[] = []) => {
|
|
1048
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
1049
|
+
|
|
1050
|
+
deps.forEach((dep, i) => {
|
|
1051
|
+
Container.set(paramTypes[i], dep);
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
return Container.get(controller);
|
|
1055
|
+
|
|
980
1056
|
},
|
|
981
1057
|
};
|
|
982
1058
|
} catch (error) {
|
|
@@ -1009,26 +1085,25 @@ export interface IAppBuilder {
|
|
|
1009
1085
|
build<T extends IAvleonApplication>(): T;
|
|
1010
1086
|
}
|
|
1011
1087
|
|
|
1012
|
-
export class
|
|
1013
|
-
private static instance:
|
|
1088
|
+
export class AvleonTest {
|
|
1089
|
+
private static instance: AvleonTest;
|
|
1014
1090
|
private app: any;
|
|
1015
1091
|
private dataSourceOptions?: DataSourceOptions | undefined;
|
|
1016
1092
|
private constructor() {
|
|
1017
1093
|
process.env.NODE_ENV = "test";
|
|
1018
1094
|
}
|
|
1019
1095
|
|
|
1020
|
-
|
|
1021
|
-
if (!TestBuilder.instance) {
|
|
1022
|
-
TestBuilder.instance = new TestBuilder();
|
|
1023
|
-
}
|
|
1024
|
-
return TestBuilder.instance;
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
addDatasource(options: DataSourceOptions) {
|
|
1096
|
+
private addDatasource(options: DataSourceOptions) {
|
|
1028
1097
|
this.dataSourceOptions = options;
|
|
1029
1098
|
}
|
|
1030
1099
|
|
|
1031
|
-
|
|
1100
|
+
getController<T>(controller: Constructor<T>, deps: any[] = []) {
|
|
1101
|
+
const paramTypes = Reflect.getMetadata('design:paramtypes', controller) || [];
|
|
1102
|
+
|
|
1103
|
+
deps.forEach((dep, i) => {
|
|
1104
|
+
Container.set(paramTypes[i], dep);
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1032
1107
|
return Container.get(controller);
|
|
1033
1108
|
}
|
|
1034
1109
|
|
|
@@ -1036,27 +1111,28 @@ export class TestBuilder {
|
|
|
1036
1111
|
return Container.get(service);
|
|
1037
1112
|
}
|
|
1038
1113
|
|
|
1039
|
-
|
|
1114
|
+
|
|
1115
|
+
static createTestApplication(options: TestAppOptions) {
|
|
1040
1116
|
const app = AvleonApplication.getInternalApp({
|
|
1041
|
-
dataSourceOptions:
|
|
1117
|
+
dataSourceOptions: options.dataSource ? options.dataSource : null,
|
|
1042
1118
|
});
|
|
1043
|
-
app.
|
|
1119
|
+
app.useControllers([...options.controllers]);
|
|
1044
1120
|
return app.getTestApp();
|
|
1045
1121
|
}
|
|
1046
1122
|
|
|
1047
|
-
|
|
1123
|
+
static from(app: AvleonApplication) {
|
|
1048
1124
|
return app.getTestApp();
|
|
1049
1125
|
}
|
|
1050
1126
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1127
|
+
static clean() {
|
|
1128
|
+
Container.reset();
|
|
1053
1129
|
}
|
|
1054
1130
|
}
|
|
1055
1131
|
|
|
1056
|
-
export class
|
|
1132
|
+
export class Avleon {
|
|
1057
1133
|
|
|
1058
|
-
static createApplication(){
|
|
1059
|
-
const app =
|
|
1134
|
+
static createApplication() {
|
|
1135
|
+
const app = AvleonApplication.getApp();
|
|
1060
1136
|
return app
|
|
1061
1137
|
}
|
|
1062
1138
|
|