@avleon/core 0.0.29 → 0.0.30

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 (43) hide show
  1. package/package.json +2 -3
  2. package/src/application.ts +0 -104
  3. package/src/authentication.ts +0 -16
  4. package/src/cache.ts +0 -91
  5. package/src/collection.test.ts +0 -71
  6. package/src/collection.ts +0 -344
  7. package/src/config.test.ts +0 -35
  8. package/src/config.ts +0 -85
  9. package/src/constants.ts +0 -1
  10. package/src/container.ts +0 -54
  11. package/src/controller.ts +0 -125
  12. package/src/decorators.ts +0 -27
  13. package/src/environment-variables.ts +0 -53
  14. package/src/event-dispatcher.ts +0 -100
  15. package/src/event-subscriber.ts +0 -79
  16. package/src/exceptions/http-exceptions.ts +0 -86
  17. package/src/exceptions/index.ts +0 -1
  18. package/src/exceptions/system-exception.ts +0 -35
  19. package/src/file-storage.ts +0 -206
  20. package/src/helpers.ts +0 -324
  21. package/src/icore.ts +0 -1106
  22. package/src/index.ts +0 -32
  23. package/src/interfaces/avleon-application.ts +0 -32
  24. package/src/logger.ts +0 -72
  25. package/src/map-types.ts +0 -159
  26. package/src/middleware.ts +0 -121
  27. package/src/multipart.ts +0 -116
  28. package/src/openapi.ts +0 -372
  29. package/src/params.ts +0 -111
  30. package/src/queue.ts +0 -126
  31. package/src/response.ts +0 -74
  32. package/src/results.ts +0 -30
  33. package/src/route-methods.ts +0 -186
  34. package/src/swagger-schema.ts +0 -213
  35. package/src/testing.ts +0 -220
  36. package/src/types/app-builder.interface.ts +0 -18
  37. package/src/types/application.interface.ts +0 -7
  38. package/src/utils/hash.ts +0 -8
  39. package/src/utils/index.ts +0 -2
  40. package/src/utils/optional-require.ts +0 -50
  41. package/src/validation.ts +0 -160
  42. package/src/validator-extend.ts +0 -25
  43. package/src/websocket.ts +0 -47
package/src/icore.ts DELETED
@@ -1,1106 +0,0 @@
1
- /**
2
- * @copyright 2024
3
- * @author Tareq Hossain
4
- * @email xtrinsic96@gmail.com
5
- * @url https://github.com/xtareq
6
- */
7
- import fastify, {
8
- FastifyInstance,
9
- FastifyReply,
10
- FastifyRequest,
11
- HookHandlerDoneFunction,
12
- preHandlerHookHandler,
13
- RouteGenericInterface,
14
- InjectOptions,
15
- LightMyRequestResponse,
16
- } from "fastify";
17
- import Container, { Constructable } from "typedi";
18
- import fs from "fs/promises";
19
- import path from "path";
20
- import container, {
21
- CONTROLLER_META_KEY,
22
- ROUTE_META_KEY,
23
- PARAM_META_KEY,
24
- QUERY_META_KEY,
25
- REQUEST_BODY_META_KEY,
26
- REQUEST_HEADER_META_KEY,
27
- getRegisteredControllers,
28
- isApiController,
29
- REQUEST_USER_META_KEY,
30
- AUTHORIZATION_META_KEY,
31
- REQUEST_BODY_FILES_KEY,
32
- REQUEST_BODY_FILE_KEY,
33
- FEATURE_KEY,
34
- } from "./container";
35
- import {
36
- Constructor,
37
- formatUrl,
38
- isValidJsonString,
39
- validateObjectByInstance,
40
- } from "./helpers";
41
- import { SystemUseError } from "./exceptions/system-exception";
42
- import { existsSync, PathLike } from "fs";
43
- import { DataSource, DataSourceOptions } from "typeorm";
44
- import { AvleonMiddleware } from "./middleware";
45
- import {
46
- BadRequestException,
47
- BaseHttpException,
48
- ValidationErrorException,
49
- } from "./exceptions";
50
- import { OpenApiOptions, OpenApiUiOptions } from "./openapi";
51
- import swagger from "@fastify/swagger";
52
- import { AvleonConfig, IConfig } from "./config";
53
- import { Environment } from "./environment-variables";
54
- import cors, { FastifyCorsOptions } from "@fastify/cors";
55
- import fastifyMultipart, { FastifyMultipartOptions } from "@fastify/multipart";
56
- import { MultipartFile } from "./multipart";
57
- import { validateOrThrow } from "./validation";
58
- import { optionalRequire } from "./utils";
59
-
60
- import { ServerOptions, Server as SocketIoServer } from "socket.io";
61
- import { SocketContextService } from "./event-dispatcher";
62
- import { EventSubscriberRegistry } from "./event-subscriber";
63
-
64
- export type FuncRoute = {
65
- handler: any;
66
- middlewares?: any[];
67
- schema?: {};
68
- };
69
-
70
- // IRequest
71
- export interface IRequest extends FastifyRequest {
72
- params: any;
73
- query: any;
74
- body: any;
75
- headers: any;
76
- user?: any;
77
- }
78
-
79
- export interface DoneFunction extends HookHandlerDoneFunction { }
80
- // IResponse
81
- export interface IResponse extends FastifyReply { }
82
-
83
- export type TestResponseType = LightMyRequestResponse;
84
- export type TestResponse = TestResponseType | Promise<TestResponseType>;
85
-
86
- export interface TestApplication {
87
- get: (url: string, options?: InjectOptions) => TestResponse;
88
- post: (url: string, options?: InjectOptions) => TestResponse;
89
- put: (url: string, options?: InjectOptions) => TestResponse;
90
- patch: (url: string, options?: InjectOptions) => TestResponse;
91
- delete: (url: string, options?: InjectOptions) => TestResponse;
92
- options: (url: string, options?: InjectOptions) => TestResponse;
93
- getController?: <T>(controller: Constructor<T>) => T;
94
- }
95
-
96
- // ParamMetaOptions
97
- export interface ParamMetaOptions {
98
- index: number;
99
- key: string;
100
- name: string;
101
- required: boolean;
102
- validate: boolean;
103
- dataType: any;
104
- validatorClass: boolean;
105
- schema?: any;
106
- type:
107
- | "route:param"
108
- | "route:query"
109
- | "route:body"
110
- | "route:header"
111
- | "route:user"
112
- | "route:file"
113
- | "route:files";
114
- }
115
-
116
- export interface ParamMetaFilesOptions {
117
- index: number;
118
- type: "route:files";
119
- files: MultipartFile[];
120
- fieldName: string;
121
- }
122
-
123
- // Method Param Meta options
124
- export interface MethodParamMeta {
125
- params: ParamMetaOptions[];
126
- query: ParamMetaOptions[];
127
- body: ParamMetaOptions[];
128
- headers: ParamMetaOptions[];
129
- currentUser: ParamMetaOptions[];
130
- swagger?: OpenApiUiOptions;
131
- file?: any[];
132
- files?: ParamMetaFilesOptions[];
133
- }
134
-
135
- interface IRoute {
136
- url: string;
137
- method: string;
138
- controller: string;
139
- }
140
-
141
- const isTsNode =
142
- process.env.TS_NODE_DEV ||
143
- process.env.TS_NODE_PROJECT ||
144
- (process as any)[Symbol.for("ts-node.register.instance")];
145
- const controllerDir = path.join(
146
- process.cwd(),
147
- isTsNode ? "./src/controllers" : "./dist/cotrollers",
148
- );
149
-
150
- type StaticFileOptions = {
151
- path?: PathLike;
152
- prefix?: string;
153
- };
154
-
155
- type MultipartOptions = {
156
- destination: PathLike;
157
- } & FastifyMultipartOptions;
158
-
159
- type AvleonApp = FastifyInstance;
160
-
161
- export type TestAppOptions = {
162
- controllers: Constructor[];
163
- dataSource?: DataSource;
164
- };
165
-
166
- export interface AvleonTestAppliction {
167
- addDataSource: (dataSourceOptions: DataSourceOptions) => void;
168
- getApp: (options?: TestAppOptions) => any;
169
- getController: <T>(controller: Constructor<T>, deps: any[]) => T;
170
- }
171
-
172
- export type AutoControllerOptions = {
173
- auto: true;
174
- path?: string;
175
- };
176
- export interface IAvleonApplication {
177
- isDevelopment(): boolean;
178
- useCors(corsOptions?: FastifyCorsOptions): void;
179
- useDataSource<
180
- T extends IConfig<R>,
181
- R = ReturnType<InstanceType<Constructable<T>>["config"]>,
182
- >(
183
- ConfigClass: Constructable<T>,
184
- modifyConfig?: (config: R) => R,
185
- ): void;
186
- useOpenApi<
187
- T extends IConfig<R>,
188
- R = ReturnType<InstanceType<Constructable<T>>["config"]>,
189
- >(
190
- ConfigClass: Constructable<T>,
191
- modifyConfig?: (config: R) => R,
192
- ): void;
193
-
194
- useMultipart(options: MultipartOptions): void;
195
- useCache(options: any): void;
196
-
197
- useMiddlewares<T extends AvleonMiddleware>(mclasses: Constructor<T>[]): void;
198
- useAuthoriztion<T extends any>(middleware: Constructor<T>): void;
199
- mapRoute<T extends (...args: any[]) => any>(
200
- method: "get" | "post" | "put" | "delete",
201
- path: string,
202
- fn: T,
203
- ): Promise<void>;
204
- mapGet<T extends (...args: any[]) => any>(path: string, fn: T): any;
205
- mapPost<T extends (...args: any[]) => any>(path: string, fn: T): any;
206
- mapPut<T extends (...args: any[]) => any>(path: string, fn: T): any;
207
- mapDelete<T extends (...args: any[]) => any>(path: string, fn: T): any;
208
- useControllers(controllers: any[]): any;
209
- useControllers(controllersOptions: AutoControllerOptions): any;
210
- useControllers(controllersOrOptions: any[] | AutoControllerOptions): any;
211
- useStaticFiles(options?: StaticFileOptions): void;
212
- run(port?: number): Promise<void>;
213
- getTestApp(): TestApplication;
214
- }
215
- type OpenApiConfigClass<T = any> = Constructable<IConfig<T>>;
216
- type OpenApiConfigInput<T = any> = OpenApiConfigClass<T> | T;
217
-
218
- type ConfigClass<T = any> = Constructable<IConfig<T>>;
219
- type ConfigInput<T = any> = ConfigClass<T> | T;
220
-
221
- interface FastifyWithIO extends FastifyInstance {
222
- io?: any
223
- }
224
-
225
- const subscriberRegistry = Container.get(EventSubscriberRegistry);
226
- // InternalApplication
227
- export class AvleonApplication {
228
- private static instance: AvleonApplication;
229
- private static buildOptions: any = {};
230
- private app!: FastifyWithIO;
231
- private routeSet = new Set<string>(); // Use Set for fast duplicate detection
232
- private alreadyRun = false;
233
- private routes: Map<string, Function> = new Map();
234
- private middlewares: Map<string, AvleonMiddleware> = new Map();
235
- private rMap = new Map<string, FuncRoute>();
236
- private hasSwagger = false;
237
- private _hasWebsocket = false;
238
- private globalSwaggerOptions: any = {};
239
- private dataSourceOptions?: DataSourceOptions = undefined;
240
- private controllers: any[] = [];
241
- private authorizeMiddleware?: any = undefined;
242
- private appConfig: AvleonConfig;
243
- private dataSource?: DataSource = undefined;
244
- private isMapFeatures = false;
245
- private registerControllerAuto = false;
246
- private registerControllerPath = "./src";
247
-
248
- private metaCache = new Map<string, MethodParamMeta>();
249
- private multipartOptions: FastifyMultipartOptions | undefined;
250
- private constructor() {
251
- this.app = fastify();
252
- this.appConfig = new AvleonConfig();
253
- }
254
-
255
- static getApp(): AvleonApplication {
256
- let isTestEnv = process.env.NODE_ENV == "test";
257
- if (!AvleonApplication.instance) {
258
- AvleonApplication.instance = new AvleonApplication();
259
- }
260
- return AvleonApplication.instance;
261
- }
262
-
263
- static getInternalApp(buildOptions: any): AvleonApplication {
264
- let isTestEnv = process.env.NODE_ENV == "test";
265
- if (!AvleonApplication.instance) {
266
- AvleonApplication.instance = new AvleonApplication();
267
- }
268
- AvleonApplication.buildOptions = buildOptions;
269
- if (buildOptions.dataSourceOptions) {
270
- AvleonApplication.instance.dataSourceOptions =
271
- buildOptions.dataSourceOptions;
272
- const typeorm = require("typeorm");
273
- const datasource = new typeorm.DataSource(
274
- buildOptions.dataSourceOptions,
275
- ) as DataSource;
276
-
277
- Container.set<DataSource>("idatasource", datasource);
278
- AvleonApplication.instance.dataSource = datasource;
279
- }
280
- return AvleonApplication.instance;
281
- }
282
-
283
- isDevelopment() {
284
- const env = container.get(Environment);
285
- return env.get("NODE_ENV") == "development";
286
- }
287
-
288
- private async initSwagger(options: OpenApiUiOptions) {
289
- const { routePrefix, logo, ui, theme, configuration, ...restOptions } =
290
- options;
291
-
292
- this.app.register(swagger, {
293
- openapi: {
294
- openapi: "3.0.0",
295
- ...restOptions,
296
- },
297
- });
298
- const rPrefix = routePrefix ? routePrefix : "/docs";
299
-
300
- if (options.ui && options.ui == "scalar") {
301
- const scalarPlugin = optionalRequire("@scalar/fastify-api-reference", {
302
- failOnMissing: true,
303
- customMessage:
304
- "Install \"@scalar/fastify-api-reference\" to enable API docs.\n\n npm install @scalar/fastify-api-reference",
305
- });
306
- await this.app.register(scalarPlugin, {
307
- routePrefix: rPrefix as any,
308
- configuration: configuration
309
- ? configuration
310
- : {
311
- metaData: {
312
- title: "Avleon Api",
313
- ogTitle: "Avleon",
314
- },
315
- theme: options.theme ? options.theme : "kepler",
316
- favicon: "/static/favicon.png",
317
- },
318
- });
319
- } else {
320
- const fastifySwaggerUi = optionalRequire("@fastify/swagger-ui", {
321
- failOnMissing: true,
322
- customMessage:
323
- "Install \"@fastify/swagger-ui\" to enable API docs.\n\n npm install @fastify/swagger-ui",
324
- });
325
- await this.app.register(fastifySwaggerUi, {
326
- logo: logo ? logo : null,
327
- theme: theme ? theme : {},
328
- routePrefix: rPrefix as any,
329
- });
330
- }
331
- }
332
-
333
- private _isConfigClass<T>(input: any): input is ConfigClass<T> {
334
- return (
335
- typeof input === "function" &&
336
- typeof input.prototype === "object" &&
337
- input.prototype?.constructor === input
338
- );
339
- }
340
- useCors<T = FastifyCorsOptions>(corsOptions?: ConfigInput<T>) {
341
- let coptions: any = {};
342
- if (corsOptions) {
343
- if (this._isConfigClass<T>(corsOptions)) {
344
- coptions = this.appConfig.get(corsOptions) as T;
345
- } else {
346
- coptions = corsOptions as T;
347
- }
348
- }
349
- this.app.register(cors, coptions);
350
- }
351
-
352
- useWebSocket<T = Partial<ServerOptions>>(socketOptions: ConfigInput<T>) {
353
- this._hasWebsocket = true;
354
- this._initWebSocket(socketOptions);
355
- }
356
-
357
- private async _initWebSocket(options:any) {
358
- const fsSocketIO = optionalRequire("fastify-socket.io", {
359
- failOnMissing: true,
360
- customMessage:
361
- "Install \"fastify-socket.io\" to enable socket.io.\n\n run pnpm install fastify-socket.io",
362
- });
363
- await this.app.register(fsSocketIO, options);
364
- const socketIO = await this.app.io;
365
- Container.set(SocketIoServer, socketIO);
366
- }
367
-
368
- useOpenApi<T = OpenApiUiOptions>(configOrClass: OpenApiConfigInput<T>) {
369
- let openApiConfig: T;
370
- if (this._isConfigClass(configOrClass)) {
371
- openApiConfig = this.appConfig.get(configOrClass);
372
- } else {
373
- openApiConfig = configOrClass as T;
374
- }
375
- this.globalSwaggerOptions = openApiConfig;
376
- this.hasSwagger = true;
377
- }
378
-
379
- useMultipart<T extends MultipartOptions>(options: ConfigInput<T>) {
380
- let multipartOptions: T;
381
- if (this._isConfigClass(options)) {
382
- multipartOptions = this.appConfig.get(options);
383
- } else {
384
- multipartOptions = options as T;
385
- }
386
- if (multipartOptions) {
387
- this.multipartOptions = multipartOptions;
388
- this.app.register(fastifyMultipart, {
389
- ...this.multipartOptions,
390
- attachFieldsToBody: true,
391
- });
392
- }
393
- }
394
-
395
- useDataSource<T extends DataSourceOptions>(options: ConfigInput<T>) {
396
- let dataSourceOptions: T;
397
- if (this._isConfigClass(options)) {
398
- dataSourceOptions = this.appConfig.get(options);
399
- } else {
400
- dataSourceOptions = options as T;
401
- }
402
-
403
- if (!dataSourceOptions)
404
- throw new SystemUseError("Invlaid datasource options.");
405
-
406
- this.dataSourceOptions = dataSourceOptions;
407
- const typeorm = require("typeorm");
408
- const datasource = new typeorm.DataSource(dataSourceOptions) as DataSource;
409
-
410
- this.dataSource = datasource;
411
-
412
- Container.set<DataSource>("idatasource", datasource);
413
- }
414
-
415
- private _useCache(options: any) { }
416
-
417
- useMiddlewares<T extends AvleonMiddleware>(mclasses: Constructor<T>[]) {
418
- for (const mclass of mclasses) {
419
- const cls = Container.get<T>(mclass);
420
- this.middlewares.set(mclass.name, cls);
421
- this.app.addHook("preHandler", cls.invoke);
422
- }
423
- }
424
-
425
- useAuthoriztion<T extends any>(middleware: Constructor<T>) {
426
- this.authorizeMiddleware = middleware as any;
427
- }
428
-
429
- useStaticFiles(
430
- options: StaticFileOptions = { path: undefined, prefix: undefined },
431
- ) {
432
- this.app.register(require("@fastify/static"), {
433
- root: options.path ? options.path : path.join(process.cwd(), "public"),
434
- prefix: options.prefix ? options.prefix : "/static/",
435
- });
436
- }
437
-
438
- private handleMiddlewares<T extends AvleonMiddleware>(
439
- mclasses: Constructor<T>[],
440
- ) {
441
- for (const mclass of mclasses) {
442
- const cls = Container.get<T>(mclass.constructor);
443
- this.middlewares.set(mclass.name, cls);
444
- this.app.addHook("preHandler", cls.invoke);
445
- }
446
- }
447
-
448
- private executeMiddlewares(target: any, propertyKey?: string) {
449
- const classMiddlewares =
450
- Reflect.getMetadata("controller:middleware", target.constructor) || [];
451
- const methodMiddlewares = propertyKey
452
- ? Reflect.getMetadata("route:middleware", target, propertyKey) || []
453
- : [];
454
-
455
- return [...classMiddlewares, ...methodMiddlewares];
456
- }
457
-
458
- /**
459
- * build controller
460
- * @param controller
461
- * @returns void
462
- */
463
- private async buildController(controller: any) {
464
- const ctrl: any = Container.get(controller);
465
- const controllerMeta = Reflect.getMetadata(
466
- CONTROLLER_META_KEY,
467
- ctrl.constructor,
468
- );
469
- if (!controllerMeta) return;
470
- const prototype = Object.getPrototypeOf(ctrl);
471
- const methods = Object.getOwnPropertyNames(prototype).filter(
472
- (name) => name !== "constructor",
473
- );
474
- const tag = ctrl.constructor.name.replace("Controller", "");
475
- const swaggerControllerMeta =
476
- Reflect.getMetadata("controller:openapi", ctrl.constructor) || {};
477
- const authClsMeata = Reflect.getMetadata(
478
- AUTHORIZATION_META_KEY,
479
- ctrl.constructor,
480
- ) || { authorize: false, options: undefined };
481
-
482
- for await (const method of methods) {
483
- const methodMeta = Reflect.getMetadata(ROUTE_META_KEY, prototype, method);
484
- if (!methodMeta) continue;
485
- const methodmetaOptions = {
486
- method: methodMeta.method.toLowerCase(),
487
- path: formatUrl(controllerMeta.path + methodMeta.path),
488
- };
489
- const routeKey = `${methodmetaOptions.method}:${methodmetaOptions.path}`;
490
- if (!this.routeSet.has(routeKey)) {
491
- this.routeSet.add(routeKey);
492
- }
493
- const classMiddlewares = this.executeMiddlewares(ctrl, method);
494
-
495
- // handle openapi data
496
- const swaggerMeta =
497
- Reflect.getMetadata("route:openapi", prototype, method) || {};
498
-
499
- const authClsMethodMeata = Reflect.getMetadata(
500
- AUTHORIZATION_META_KEY,
501
- ctrl.constructor,
502
- method,
503
- ) || { authorize: false, options: undefined };
504
- const allMeta = this._processMeta(prototype, method);
505
-
506
- let bodySchema: any = null;
507
- allMeta.body.forEach((r) => {
508
- if (r.schema) {
509
- bodySchema = { ...r.schema };
510
- }
511
- });
512
-
513
- const routePath =
514
- methodmetaOptions.path == "" ? "/" : methodmetaOptions.path;
515
-
516
- let schema = { ...swaggerControllerMeta, ...swaggerMeta, tags: [tag] };
517
- if (!swaggerMeta.body && bodySchema) {
518
- schema = { ...schema, body: bodySchema };
519
- }
520
-
521
- this.app.route({
522
- url: routePath,
523
- method: methodmetaOptions.method.toUpperCase(),
524
- schema: { ...schema },
525
- handler: async (req, res) => {
526
- let reqClone = req as IRequest;
527
-
528
- // class level authrization
529
- if (authClsMeata.authorize && this.authorizeMiddleware) {
530
- const cls = container.get(this.authorizeMiddleware) as any;
531
- await cls.authorize(reqClone, authClsMeata.options);
532
- if (res.sent) return;
533
- }
534
-
535
- // method level authorization
536
- if (authClsMethodMeata.authorize && this.authorizeMiddleware) {
537
- const cls = container.get(this.authorizeMiddleware) as any;
538
- await cls.authorize(reqClone, authClsMethodMeata.options);
539
- if (res.sent) return;
540
- }
541
- if (classMiddlewares.length > 0) {
542
- for (let m of classMiddlewares) {
543
- const cls = Container.get<AvleonMiddleware>(m.constructor);
544
- reqClone = (await cls.invoke(reqClone, res)) as IRequest;
545
- if (res.sent) return;
546
- }
547
- }
548
- const args = await this._mapArgs(reqClone, allMeta);
549
-
550
- for (let paramMeta of allMeta.params) {
551
- if (paramMeta.required) {
552
- validateOrThrow(
553
- { [paramMeta.key]: args[paramMeta.index] },
554
- { [paramMeta.key]: { type: paramMeta.dataType } },
555
- { location: "param" },
556
- );
557
- }
558
- }
559
-
560
- for (let queryMeta of allMeta.query) {
561
- if (queryMeta.validatorClass) {
562
- const err = await validateObjectByInstance(
563
- queryMeta.dataType,
564
- args[queryMeta.index],
565
- );
566
- if (err) {
567
- return await res.code(400).send({
568
- code: 400,
569
- error: "ValidationError",
570
- errors: err,
571
- message: err.message,
572
- });
573
- }
574
- }
575
- if (queryMeta.required) {
576
- validateOrThrow(
577
- { [queryMeta.key]: args[queryMeta.index] },
578
- { [queryMeta.key]: { type: queryMeta.dataType } },
579
- { location: "queryparam" },
580
- );
581
- }
582
- }
583
-
584
- for (let bodyMeta of allMeta.body) {
585
- if (bodyMeta.validatorClass) {
586
- const err = await validateObjectByInstance(
587
- bodyMeta.dataType,
588
- args[bodyMeta.index],
589
- );
590
- if (err) {
591
- return await res.code(400).send({
592
- code: 400,
593
- error: "ValidationError",
594
- errors: err,
595
- message: err.message,
596
- });
597
- }
598
- }
599
- }
600
- const result = await prototype[method].apply(ctrl, args);
601
- return result;
602
- },
603
- });
604
- }
605
- }
606
-
607
- /**
608
- * map all request parameters
609
- * @param req
610
- * @param meta
611
- * @returns
612
- */
613
- private async _mapArgs(req: IRequest, meta: MethodParamMeta): Promise<any[]> {
614
- if (!req.hasOwnProperty("_argsCache")) {
615
- Object.defineProperty(req, "_argsCache", {
616
- value: new Map<string, any[]>(),
617
- enumerable: false,
618
- });
619
- }
620
-
621
- const cache: Map<string, any[]> = (req as any)._argsCache;
622
- const cacheKey = JSON.stringify(meta); // Faster key-based lookup
623
-
624
- if (cache.has(cacheKey)) {
625
- return cache.get(cacheKey)!;
626
- }
627
-
628
- const args: any[] = meta.params.map((p) => req.params[p.key] || null);
629
- meta.query.forEach(
630
- (q) => (args[q.index] = q.key === "all" ? req.query : req.query[q.key]),
631
- );
632
- meta.body.forEach(
633
- (body) => (args[body.index] = { ...req.body, ...req.formData }),
634
- );
635
- meta.currentUser.forEach((user) => (args[user.index] = req.user));
636
- meta.headers.forEach(
637
- (header) =>
638
- (args[header.index] =
639
- header.key === "all" ? req.headers : req.headers[header.key]),
640
- );
641
-
642
- if (meta.file) {
643
- for await (let f of meta.file) {
644
- args[f.index] = await req.file();
645
- }
646
- }
647
-
648
- if (
649
- meta.files &&
650
- req.headers["content-type"]?.startsWith("multipart/form-data") === true
651
- ) {
652
- const files = await req.saveRequestFiles();
653
- if (!files || files.length === 0) {
654
- throw new BadRequestException({ error: "No files uploaded" });
655
- }
656
-
657
- const fileInfo = files.map((file) => ({
658
- type: file.type,
659
- filepath: file.filepath,
660
- fieldname: file.fieldname,
661
- filename: file.filename,
662
- encoding: file.encoding,
663
- mimetype: file.mimetype,
664
- fields: file.fields,
665
- }));
666
- for await (let f of meta.files) {
667
- const findex = fileInfo.findIndex((x) => x.fieldname == f.fieldName);
668
- if (f.fieldName != "all" && findex == -1) {
669
- throw new BadRequestException(
670
- `${f.fieldName} doesn't exists in request files tree.`,
671
- );
672
- }
673
- args[f.index] =
674
- f.fieldName == "all"
675
- ? fileInfo
676
- : fileInfo.filter((x) => x.fieldname == f.fieldName);
677
- }
678
- }
679
-
680
- cache.set(cacheKey, args);
681
- return args;
682
- }
683
-
684
- /**
685
- * Process Meta for controlelr class methods
686
- * @param prototype
687
- * @param method
688
- * @returns
689
- */
690
- private _processMeta(prototype: any, method: string): MethodParamMeta {
691
- const cacheKey = `${prototype.constructor.name}_${method}`;
692
- if (this.metaCache.has(cacheKey)) {
693
- return this.metaCache.get(cacheKey)!;
694
- }
695
-
696
- const meta: MethodParamMeta = {
697
- params: Reflect.getMetadata(PARAM_META_KEY, prototype, method) || [],
698
- query: Reflect.getMetadata(QUERY_META_KEY, prototype, method) || [],
699
- body: Reflect.getMetadata(REQUEST_BODY_META_KEY, prototype, method) || [],
700
- file: Reflect.getMetadata(REQUEST_BODY_FILE_KEY, prototype, method) || [],
701
- files:
702
- Reflect.getMetadata(REQUEST_BODY_FILES_KEY, prototype, method) || [],
703
- headers:
704
- Reflect.getMetadata(REQUEST_HEADER_META_KEY, prototype, method) || [],
705
- currentUser:
706
- Reflect.getMetadata(REQUEST_USER_META_KEY, prototype, method) || [],
707
- // swagger: Reflect.getMetadata("route:openapi", prototype, method) || {}
708
- };
709
-
710
- this.metaCache.set(cacheKey, meta);
711
- return meta;
712
- }
713
-
714
- private _resolveControllerDir(dir?: string) {
715
- const isTsNode =
716
- process.env.TS_NODE_DEV ||
717
- process.env.TS_NODE_PROJECT ||
718
- (process as any)[Symbol.for("ts-node.register.instance")];
719
- const controllerDir = path.join(process.cwd(), this.registerControllerPath);
720
-
721
- return isTsNode ? controllerDir : controllerDir.replace("src", "dist");
722
- }
723
-
724
- private async autoControllers(controllersPath?: string) {
725
- const conDir = this._resolveControllerDir(controllersPath);
726
-
727
- const files = await fs.readdir(conDir, { recursive: true });
728
- for (const file of files) {
729
- const isTestFile = /\.(test|spec|e2e-spec)\.ts$/.test(file);
730
- if (isTestFile) continue;
731
- if (isTsNode ? file.endsWith(".ts") : file.endsWith(".js")) {
732
- const filePath = path.join(conDir, file);
733
- const module = await import(filePath);
734
- for (const exported of Object.values(module)) {
735
- if (typeof exported === "function" && isApiController(exported)) {
736
- console.log("adding", exported.name);
737
- if (!this.controllers.some((con) => exported.name == con.name)) {
738
- this.controllers.push(exported);
739
- }
740
-
741
- //this.buildController(exported);
742
- }
743
- }
744
- }
745
- }
746
- }
747
-
748
- useControllers(controllers: Constructor[] | AutoControllerOptions) {
749
- if (Array.isArray(controllers)) {
750
- this.controllers = controllers;
751
-
752
- controllers.forEach((controller) => {
753
- if (!this.controllers.includes(controller)) {
754
- this.controllers.push(controller);
755
- }
756
- });
757
- } else {
758
- this.registerControllerAuto = true;
759
- if (controllers.path) {
760
- this.registerControllerPath = controllers.path;
761
- }
762
- }
763
- }
764
-
765
- // addFeature(feature:{controllers:Function[]}){
766
- // feature.controllers.forEach(c=> this.controllers.push(c))
767
- // }
768
-
769
- // mapFeature(){
770
- // if(!this.isMapFeatures){
771
- // this.isMapFeatures = true;
772
- // }
773
- // }
774
-
775
- private async _mapControllers() {
776
- if (this.controllers.length > 0) {
777
- for (let controller of this.controllers) {
778
- if (isApiController(controller)) {
779
- this.buildController(controller);
780
- } else {
781
- throw new SystemUseError("Not a api controller.");
782
- }
783
- }
784
- }
785
- }
786
-
787
- // useControllersAuto(controllerPath?:string) {
788
- // this.registerControllerAuto = true;
789
- // //this.autoControllers();
790
- // }
791
-
792
- private async mapFn(fn: Function) {
793
- const original = fn;
794
- fn = function () { };
795
- return fn;
796
- }
797
-
798
- private _handleError(error: any): {
799
- code: number;
800
- error: string;
801
- message: any;
802
- } {
803
- if (error instanceof BaseHttpException) {
804
- return {
805
- code: error.code,
806
- error: error.name,
807
- message: isValidJsonString(error.message)
808
- ? JSON.parse(error.message)
809
- : error.message,
810
- };
811
- }
812
- return {
813
- code: 500,
814
- error: "INTERNAL_ERROR",
815
- message: error.message ? error.message : "Something going wrong.",
816
- };
817
- }
818
-
819
- async mapRoute<T extends (...args: any[]) => any>(
820
- method: "get" | "post" | "put" | "delete",
821
- path: string = "",
822
- fn: T,
823
- ) {
824
- await this.mapFn(fn);
825
-
826
- this.app[method](path, async (req: any, res: any) => {
827
-
828
- try {
829
- const result = await fn.apply(this, [req, res]);
830
- if (typeof result === "object" && result !== null) {
831
- res.json(result);
832
- } else {
833
- res.send(result);
834
- }
835
- } catch (error) {
836
- console.error(`Error in ${method} route handler:`, error);
837
- const handledErr = this._handleError(error);
838
- res.status(handledErr.code).send(handledErr);
839
- }
840
- });
841
- }
842
- private _routeHandler<T extends (...args: any[]) => any>(
843
- routePath: string,
844
- method: string,
845
- fn: T,
846
- ) {
847
- const routeKey = method + ":" + routePath;
848
- this.rMap.set(routeKey, {
849
- handler: fn,
850
- middlewares: [],
851
- schema: {},
852
- });
853
-
854
- const route = {
855
- useMiddleware: <M extends AvleonMiddleware>(
856
- middlewares: Constructor<AvleonMiddleware>[],
857
- ) => {
858
- const midds = Array.isArray(middlewares) ? middlewares : [middlewares];
859
- const ms: any[] = (midds as unknown as any[]).map((mclass) => {
860
- const cls = Container.get<AvleonMiddleware>(mclass);
861
- this.middlewares.set(mclass.name, cls);
862
- return cls.invoke;
863
- });
864
-
865
- const r = this.rMap.get(routeKey);
866
- if (r) {
867
- r.middlewares = ms;
868
- }
869
- return route;
870
- },
871
-
872
- useOpenApi: (options: OpenApiOptions) => {
873
- const r = this.rMap.get(routeKey);
874
- if (r) {
875
- r.schema = options;
876
- }
877
- return route;
878
- },
879
- };
880
-
881
- return route;
882
- }
883
-
884
- mapGet<T extends (...args: any[]) => any>(path: string = "", fn: T) {
885
- return this._routeHandler(path, "GET", fn);
886
- }
887
-
888
- mapPost<T extends (...args: any[]) => any>(path: string = "", fn: T) {
889
- return this._routeHandler(path, "POST", fn);
890
- }
891
-
892
- mapPut<T extends (...args: any[]) => any>(path: string = "", fn: T) {
893
- return this._routeHandler(path, "PUT", fn);
894
- }
895
-
896
- mapDelete<T extends (...args: any[]) => any>(path: string = "", fn: T) {
897
- return this._routeHandler(path, "DELETE", fn);
898
- }
899
-
900
- private _mapFeatures() {
901
- const features = Container.get("features");
902
- console.log("Features", features);
903
- }
904
-
905
- async initializeDatabase() {
906
- if (this.dataSourceOptions && this.dataSource) {
907
- await this.dataSource.initialize();
908
- }
909
- }
910
-
911
-
912
- handleSocket(socket: any) {
913
- subscriberRegistry.register(socket);
914
- }
915
-
916
- async run(port: number = 4000, fn?: CallableFunction): Promise<void> {
917
- if (this.alreadyRun) throw new SystemUseError("App already running");
918
- this.alreadyRun = true;
919
-
920
- if (this.hasSwagger) {
921
- await this.initSwagger(this.globalSwaggerOptions);
922
- }
923
- await this.initializeDatabase();
924
- if (this.isMapFeatures) {
925
- this._mapFeatures();
926
- }
927
-
928
- if (this.registerControllerAuto) {
929
- await this.autoControllers();
930
- }
931
- await this._mapControllers();
932
-
933
- this.rMap.forEach((value, key) => {
934
- const [m, r] = key.split(":");
935
- this.app.route({
936
- method: m,
937
- url: r,
938
- schema: value.schema || {},
939
- preHandler: value.middlewares ? value.middlewares : [],
940
- handler: async (req, res) => {
941
- const result = await value.handler.apply(this, [req, res]);
942
- return result;
943
- },
944
- });
945
- });
946
- this.app.setErrorHandler(async (error, req, res) => {
947
- const handledErr = this._handleError(error);
948
- if (
949
- error instanceof ValidationErrorException ||
950
- error instanceof BadRequestException
951
- ) {
952
- return res.status(handledErr.code).send({
953
- code: handledErr.code,
954
- error: handledErr.error,
955
- errors: handledErr.message,
956
- });
957
- }
958
- return res.status(handledErr.code).send(handledErr);
959
- });
960
-
961
- await this.app.ready();
962
- if (this._hasWebsocket) {
963
- await this.app.io.on("connection", this.handleSocket);
964
- await this.app.io.use((socket: { handshake: { auth: { token: any; }; }; data: { user: any; }; }, next:any) => {
965
- const token = socket.handshake.auth.token;
966
- try {
967
- console.log("token", token);
968
- const user = {id:1, name:"tareq"};
969
- socket.data.user = user; // this powers @AuthUser()
970
- next();
971
- } catch {
972
- next(new Error("Unauthorized"));
973
- }
974
- });
975
- }
976
- await this.app.listen({ port });
977
- console.log(`Application running on http://127.0.0.1:${port}`);
978
- }
979
- getTestApp(buildOptions?: any): TestApplication {
980
- try {
981
- // }
982
- // this.initializeDatabase();
983
- this._mapControllers();
984
- this.rMap.forEach((value, key) => {
985
- const [m, r] = key.split(":");
986
- this.app.route({
987
- method: m,
988
- url: r,
989
- schema: value.schema || {},
990
- preHandler: value.middlewares ? value.middlewares : [],
991
- handler: async (req, res) => {
992
- const result = await value.handler.apply(this, [req, res]);
993
- return result;
994
- },
995
- });
996
- });
997
-
998
- this.app.setErrorHandler(async (error, req, res) => {
999
- const handledErr = this._handleError(error);
1000
- if (error instanceof ValidationErrorException) {
1001
- return res.status(handledErr.code).send({
1002
- code: handledErr.code,
1003
- error: handledErr.error,
1004
- errors: handledErr.message,
1005
- });
1006
- }
1007
- return res.status(handledErr.code).send(handledErr);
1008
- });
1009
- // return this.app as any;
1010
- //
1011
- return {
1012
- get: async (url: string, options?: InjectOptions) =>
1013
- this.app.inject({ method: "GET", url, ...options }),
1014
- post: async (url: string, options?: InjectOptions) =>
1015
- this.app.inject({ method: "POST", url, ...options }),
1016
- put: async (url: string, options?: InjectOptions) =>
1017
- this.app.inject({ method: "PUT", url, ...options }),
1018
- patch: async (url: string, options?: InjectOptions) =>
1019
- this.app.inject({ method: "PATCH", url, ...options }),
1020
- delete: async (url: string, options?: InjectOptions) =>
1021
- this.app.inject({ method: "DELETE", url, ...options }),
1022
- options: async (url: string, options?: InjectOptions) =>
1023
- this.app.inject({ method: "OPTIONS", url, ...options }),
1024
- getController: <T>(controller: Constructor<T>, deps: any[] = []) => {
1025
- const paramTypes =
1026
- Reflect.getMetadata("design:paramtypes", controller) || [];
1027
-
1028
- deps.forEach((dep, i) => {
1029
- Container.set(paramTypes[i], dep);
1030
- });
1031
-
1032
- return Container.get(controller);
1033
- },
1034
- };
1035
- } catch (error) {
1036
- console.log(error);
1037
- throw new SystemUseError("Can't get test appliction");
1038
- }
1039
- }
1040
- }
1041
-
1042
- export type Application = typeof AvleonApplication;
1043
-
1044
- // Applciation Builder
1045
- export interface ITestBuilder {
1046
- getTestApplication(): AvleonTestAppliction;
1047
- createTestApplication(options: any): AvleonTestAppliction;
1048
- }
1049
-
1050
- export interface IAppBuilder {
1051
- registerPlugin<T extends Function, S extends {}>(
1052
- plugin: T,
1053
- options: S,
1054
- ): Promise<void>;
1055
- addDataSource<
1056
- T extends IConfig<R>,
1057
- R = ReturnType<InstanceType<Constructable<T>>["config"]>,
1058
- >(
1059
- ConfigClass: Constructable<T>,
1060
- modifyConfig?: (config: R) => R,
1061
- ): void;
1062
- build<T extends IAvleonApplication>(): T;
1063
- }
1064
-
1065
- export class AvleonTest {
1066
- private constructor() {
1067
- process.env.NODE_ENV = "test";
1068
- }
1069
- static getController<T>(controller: Constructor<T>, deps: any[] = []) {
1070
- const paramTypes =
1071
- Reflect.getMetadata("design:paramtypes", controller) || [];
1072
-
1073
- deps.forEach((dep, i) => {
1074
- Container.set(paramTypes[i], dep);
1075
- });
1076
-
1077
- return Container.get(controller);
1078
- }
1079
-
1080
- private getService<T>(service: Constructor<T>) {
1081
- return Container.get(service);
1082
- }
1083
-
1084
- static createTestApplication(options: TestAppOptions) {
1085
- const app = AvleonApplication.getInternalApp({
1086
- dataSourceOptions: options.dataSource ? options.dataSource : null,
1087
- });
1088
- app.useControllers([...options.controllers]);
1089
- return app.getTestApp();
1090
- }
1091
-
1092
- static from(app: AvleonApplication) {
1093
- return app.getTestApp();
1094
- }
1095
-
1096
- static clean() {
1097
- Container.reset();
1098
- }
1099
- }
1100
-
1101
- export class Avleon {
1102
- static createApplication() {
1103
- const app = AvleonApplication.getApp();
1104
- return app;
1105
- }
1106
- }