From c49b967ab35b9201b5878d8cd9a57630d1698391 Mon Sep 17 00:00:00 2001 From: Serhii Sol Date: Wed, 30 Aug 2023 22:56:51 +0300 Subject: [PATCH] feat(server): beta.16 - fixed namespaces in http module; moved metadata-scanner, handler-creator to the core (#196) --- .../http/app-version/src/app.module.ts | 5 +- .../http/app-version/test/express.spec.ts | 5 +- .../http/app-version/test/fastify.spec.ts | 5 +- .../http/app-version/test/koa.spec.ts | 5 +- .../metadata-scanner/test/express.spec.ts | 3 +- .../metadata-scanner/test/fastify.spec.ts | 3 +- .../http/metadata-scanner/test/koa.spec.ts | 3 +- .../app-version/test/socket-io.spec.ts | 5 +- .../metadata-scanner/test/socket-io.spec.ts | 4 +- server/package-lock.json | 4 +- server/package.json | 2 +- server/src/core/application.ts | 2 +- server/src/core/helpers/decorators.ts | 21 +++++-- server/src/core/helpers/handler.ts | 51 ++++++++++++++++ server/src/core/helpers/index.ts | 2 + server/src/core/helpers/metadata-scanner.ts | 60 +++++++++++++++++++ server/src/core/helpers/reflector.ts | 7 ++- server/src/core/providers.ts | 6 +- server/src/core/types.ts | 11 ++++ server/src/index.ts | 2 + .../src/platforms/express/express-adapter.ts | 4 +- .../src/platforms/fastify/fastify-adapter.ts | 4 ++ .../http/helpers/constants/injectables.ts | 2 +- server/src/platforms/http/helpers/index.ts | 1 - .../http/helpers/metadata-scanner.ts | 53 ---------------- .../platforms/http/helpers/route-handler.ts | 52 +++------------- .../platforms/http/helpers/route-resolver.ts | 16 +++-- server/src/platforms/http/http.module.ts | 7 ++- server/src/platforms/http/index.ts | 2 +- server/src/platforms/http/types.ts | 9 +-- server/src/platforms/koa/koa-adapter.ts | 4 +- .../sockets/helpers/event-handler.ts | 54 +++-------------- .../sockets/helpers/event-resolver.ts | 10 ++-- server/src/platforms/sockets/helpers/index.ts | 1 - .../sockets/helpers/metadata-scanner.ts | 53 ---------------- server/src/platforms/sockets/index.ts | 2 +- .../src/platforms/sockets/sockets.module.ts | 3 +- server/src/platforms/sockets/types.ts | 8 +-- .../helpers/swagger-ui/swagger-document.ts | 4 +- 39 files changed, 235 insertions(+), 260 deletions(-) create mode 100644 server/src/core/helpers/handler.ts create mode 100644 server/src/core/helpers/metadata-scanner.ts delete mode 100644 server/src/platforms/http/helpers/metadata-scanner.ts delete mode 100644 server/src/platforms/sockets/helpers/metadata-scanner.ts diff --git a/server/integration/http/app-version/src/app.module.ts b/server/integration/http/app-version/src/app.module.ts index a00d88f..cf5f192 100644 --- a/server/integration/http/app-version/src/app.module.ts +++ b/server/integration/http/app-version/src/app.module.ts @@ -1,11 +1,8 @@ -import { APP_VERSION, Module } from '@server'; +import { Module } from '@server'; import { AppController } from './app.controller'; @Module({ controllers: [AppController], - providers: [ - { provide: APP_VERSION, useValue: 'app-version' }, - ], }) export class AppModule { } diff --git a/server/integration/http/app-version/test/express.spec.ts b/server/integration/http/app-version/test/express.spec.ts index 977ef1c..7cf625b 100644 --- a/server/integration/http/app-version/test/express.spec.ts +++ b/server/integration/http/app-version/test/express.spec.ts @@ -1,4 +1,4 @@ -import { Application, HttpStatus, Module } from '@server'; +import { APP_VERSION, Application, HttpStatus, Module } from '@server'; import { ExpressAdapter } from '@server/express'; import { HttpModule } from '@server/http'; import * as request from 'supertest'; @@ -10,6 +10,9 @@ import { AppModule } from '../src/app.module'; HttpModule.create(ExpressAdapter), AppModule, ], + providers: [ + { provide: APP_VERSION, useValue: 'app-version' }, + ], }) class TestModule { } diff --git a/server/integration/http/app-version/test/fastify.spec.ts b/server/integration/http/app-version/test/fastify.spec.ts index 0a4b64e..9f84d18 100644 --- a/server/integration/http/app-version/test/fastify.spec.ts +++ b/server/integration/http/app-version/test/fastify.spec.ts @@ -1,4 +1,4 @@ -import { Application, HttpStatus, Module } from '@server'; +import { APP_VERSION, Application, HttpStatus, Module } from '@server'; import { FastifyAdapter } from '@server/fastify'; import { HttpModule } from '@server/http'; import * as request from 'supertest'; @@ -10,6 +10,9 @@ import { AppModule } from '../src/app.module'; HttpModule.create(FastifyAdapter), AppModule, ], + providers: [ + { provide: APP_VERSION, useValue: 'app-version' }, + ], }) class TestModule { } diff --git a/server/integration/http/app-version/test/koa.spec.ts b/server/integration/http/app-version/test/koa.spec.ts index 36a1401..aa1bdab 100644 --- a/server/integration/http/app-version/test/koa.spec.ts +++ b/server/integration/http/app-version/test/koa.spec.ts @@ -1,4 +1,4 @@ -import { Application, HttpStatus, Module } from '@server'; +import { APP_VERSION, Application, HttpStatus, Module } from '@server'; import { HttpModule } from '@server/http'; import { KoaAdapter } from '@server/koa'; import * as request from 'supertest'; @@ -10,6 +10,9 @@ import { AppModule } from '../src/app.module'; HttpModule.create(KoaAdapter), AppModule, ], + providers: [ + { provide: APP_VERSION, useValue: 'app-version' }, + ], }) class TestModule { } diff --git a/server/integration/http/metadata-scanner/test/express.spec.ts b/server/integration/http/metadata-scanner/test/express.spec.ts index 0c94df1..809a0ca 100644 --- a/server/integration/http/metadata-scanner/test/express.spec.ts +++ b/server/integration/http/metadata-scanner/test/express.spec.ts @@ -1,7 +1,6 @@ -import { Application, Module } from '@server'; +import { Application, MetadataScanner, Module } from '@server'; import { ExpressAdapter } from '@server/express'; import { HttpModule } from '@server/http'; -import { MetadataScanner } from '@server/http'; import { AppModule } from '../src/app.module'; diff --git a/server/integration/http/metadata-scanner/test/fastify.spec.ts b/server/integration/http/metadata-scanner/test/fastify.spec.ts index 0eb20aa..ed1da7c 100644 --- a/server/integration/http/metadata-scanner/test/fastify.spec.ts +++ b/server/integration/http/metadata-scanner/test/fastify.spec.ts @@ -1,7 +1,6 @@ -import { Application, Module } from '@server'; +import { Application, MetadataScanner, Module } from '@server'; import { FastifyAdapter } from '@server/fastify'; import { HttpModule } from '@server/http'; -import { MetadataScanner } from '@server/http'; import { AppModule } from '../src/app.module'; diff --git a/server/integration/http/metadata-scanner/test/koa.spec.ts b/server/integration/http/metadata-scanner/test/koa.spec.ts index 9651c61..f460b56 100644 --- a/server/integration/http/metadata-scanner/test/koa.spec.ts +++ b/server/integration/http/metadata-scanner/test/koa.spec.ts @@ -1,6 +1,5 @@ -import { Application, Module } from '@server'; +import { Application, MetadataScanner, Module } from '@server'; import { HttpModule } from '@server/http'; -import { MetadataScanner } from '@server/http'; import { KoaAdapter } from '@server/koa'; import { AppModule } from '../src/app.module'; diff --git a/server/integration/sockets/app-version/test/socket-io.spec.ts b/server/integration/sockets/app-version/test/socket-io.spec.ts index 911df34..eee57f8 100644 --- a/server/integration/sockets/app-version/test/socket-io.spec.ts +++ b/server/integration/sockets/app-version/test/socket-io.spec.ts @@ -1,4 +1,4 @@ -import { Application, Module } from '@server'; +import { APP_VERSION, Application, Module } from '@server'; import { SocketIoAdapter } from '@server/socket-io'; import { SocketsModule } from '@server/sockets'; import { connect, Socket } from 'socket.io-client'; @@ -10,6 +10,9 @@ import { AppModule } from '../src/app.module'; SocketsModule.create(SocketIoAdapter), AppModule, ], + providers: [ + { provide: APP_VERSION, useValue: 'app-version' }, + ], }) class TestModule { } diff --git a/server/integration/sockets/metadata-scanner/test/socket-io.spec.ts b/server/integration/sockets/metadata-scanner/test/socket-io.spec.ts index 00068cc..317f1f4 100644 --- a/server/integration/sockets/metadata-scanner/test/socket-io.spec.ts +++ b/server/integration/sockets/metadata-scanner/test/socket-io.spec.ts @@ -1,6 +1,6 @@ -import { Application, Module } from '@server'; +import { Application, MetadataScanner, Module } from '@server'; import { SocketIoAdapter } from '@server/socket-io'; -import { MetadataScanner, SocketsModule } from '@server/sockets'; +import { SocketsModule } from '@server/sockets'; import { AppModule } from '../src/app.module'; diff --git a/server/package-lock.json b/server/package-lock.json index 025894f..d27c271 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@decorators/server", - "version": "1.0.0-beta.15", + "version": "1.0.0-beta.16", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@decorators/server", - "version": "1.0.0-beta.15", + "version": "1.0.0-beta.16", "license": "MIT", "devDependencies": { "@decorators/di": "../di", diff --git a/server/package.json b/server/package.json index 11b15af..431aa74 100644 --- a/server/package.json +++ b/server/package.json @@ -148,5 +148,5 @@ ] } }, - "version": "1.0.0-beta.15" + "version": "1.0.0-beta.16" } diff --git a/server/src/core/application.ts b/server/src/core/application.ts index 5517768..637d3f7 100644 --- a/server/src/core/application.ts +++ b/server/src/core/application.ts @@ -21,11 +21,11 @@ export class Application { provide: ContainerManager, useValue: containerManger, }, - ...DEFAULT_PROVIDERS, { provide: ROOT_MODULE, useValue: rootModule, }, + ...DEFAULT_PROVIDERS, ]); const moduleResolver = await container.get(ModuleResolver); diff --git a/server/src/core/helpers/decorators.ts b/server/src/core/helpers/decorators.ts index d88e7f5..51398cc 100644 --- a/server/src/core/helpers/decorators.ts +++ b/server/src/core/helpers/decorators.ts @@ -1,16 +1,29 @@ +import { ParamMetadata } from '../types'; import { extractParamNames } from '../utils'; import { METHOD_METADATA, PARAM_TYPE_METADATA, PARAMS_METADATA, RETURN_TYPE_METADATA } from './constants'; import { Context } from './context'; -export function paramDecoratorFactory(metadata: object) { +export function paramDecoratorFactory(metadata: Partial) { return function (target: InstanceType, methodName: string, index: number) { - const params = Reflect.getMetadata(PARAMS_METADATA, target.constructor) ?? []; + const params = Reflect.getMetadata(PARAMS_METADATA, target[methodName]) ?? []; const argType = Reflect.getMetadata(PARAM_TYPE_METADATA, target, methodName)[index]; const argName = extractParamNames(target[methodName])[index]; - params.push({ argName, argType, index, methodName, ...metadata }); + params[index] = { + argName, + argType, + index, + methodName, + ...metadata, + }; - Reflect.defineMetadata(PARAMS_METADATA, params, target.constructor); + params + .filter((param: ParamMetadata) => param.paramType === metadata.paramType) + .forEach((param: ParamMetadata, index: number) => { + param.callIndex = index; + }); + + Reflect.defineMetadata(PARAMS_METADATA, params, target[methodName]); }; } diff --git a/server/src/core/helpers/handler.ts b/server/src/core/helpers/handler.ts new file mode 100644 index 0000000..a7f2732 --- /dev/null +++ b/server/src/core/helpers/handler.ts @@ -0,0 +1,51 @@ +import { Handler, ParamMetadata } from '../types'; +import { toStandardType } from '../utils'; +import { HttpStatus } from './constants'; +import { Context } from './context'; +import { ApiError } from './errors'; + +export abstract class HandlerCreator { + abstract getParam(param: ParamMetadata, args: unknown[]): Promise | unknown; + + message(message: unknown) { + if (message instanceof ApiError) { + return message.toObject(); + } + + if (message instanceof Error) { + return { message: message.message }; + } + + return message; + } + + async params(metadata: ParamMetadata[], context: Context, args: unknown[]) { + const params$ = metadata.map(param => param.factory + ? param.factory(context) + : this.getParam(param, args), + ); + const params = await Promise.all(params$); + + return params.map(paramFn => toStandardType(paramFn())); + } + + async runHandler(handler: Handler) { + try { + return await handler(); + } catch (error) { + return error; + } + } + + status(message: unknown, status: number) { + if (message instanceof ApiError) { + return message.status; + } + + if (message instanceof Error) { + return HttpStatus.INTERNAL_SERVER_ERROR; + } + + return status; + } +} diff --git a/server/src/core/helpers/index.ts b/server/src/core/helpers/index.ts index 90fe161..93ceb1d 100644 --- a/server/src/core/helpers/index.ts +++ b/server/src/core/helpers/index.ts @@ -3,6 +3,8 @@ export * from './container-manager'; export * from './context'; export * from './decorators'; export * from './errors'; +export * from './handler'; +export * from './metadata-scanner'; export * from './module-resolver'; export * from './param-validator'; export * from './pipe'; diff --git a/server/src/core/helpers/metadata-scanner.ts b/server/src/core/helpers/metadata-scanner.ts new file mode 100644 index 0000000..f99b47e --- /dev/null +++ b/server/src/core/helpers/metadata-scanner.ts @@ -0,0 +1,60 @@ +import { Inject, Injectable, Optional } from '@decorators/di'; + +import { ClassConstructor, Metadata, MethodMetadata } from '../types'; +import { addLeadingSlash, buildUrl } from '../utils'; +import { APP_VERSION, ROOT_MODULE } from './constants'; +import { Reflector } from './reflector'; + +@Injectable() +export class MetadataScanner { + constructor( + @Inject(APP_VERSION) @Optional() private appVersion = '', + @Inject(ROOT_MODULE) private rootModule: ClassConstructor, + private reflector: Reflector, + ) { } + + scan() { + return this.scanModule(this.rootModule) as M[]; + } + + private scanModule(module: ClassConstructor, parentNamespaces = []) { + const { controllers, modules, namespace } = this.reflector.getModuleMetadata(module); + const namespaces = [...parentNamespaces, namespace]; + + const methods = controllers.map(controller => { + const metadata = this.reflector.getControllerMetadata(controller); + + return metadata.methods.map((method: MethodMetadata) => { + const params = this.reflector.getParamsMetadata(controller, method.methodName); + + const pipes = metadata.pipes + .filter(([, methodName]) => !methodName || methodName === method.methodName) + .map(([pipe]) => pipe); + + const version = metadata.options?.ignoreVersion ? '' : this.appVersion; + + const paths = [version, ...namespaces, metadata.url, method.url].filter(Boolean); + const url = addLeadingSlash(buildUrl(...paths)); + + return { + ...method, + controller, + module, + params, + paths, + pipes, + url, + }; + }); + }); + + const nestedMethods = modules.map(module => + this.scanModule(module, namespaces), + ); + + return [ + ...nestedMethods.flat(), + ...methods.flat(), + ]; + } +} diff --git a/server/src/core/helpers/reflector.ts b/server/src/core/helpers/reflector.ts index 47d7a80..798086f 100644 --- a/server/src/core/helpers/reflector.ts +++ b/server/src/core/helpers/reflector.ts @@ -8,10 +8,9 @@ export class Reflector { getControllerMetadata(controller: ClassConstructor) { const metadata = Reflect.getMetadata(CONTROLLER_METADATA, controller) as ControllerMetadata; const methods = (Reflect.getMetadata(METHOD_METADATA, controller) ?? []) as MethodMetadata[]; - const params = (Reflect.getMetadata(PARAMS_METADATA, controller) ?? []) as ParamMetadata[]; const pipes = (Reflect.getMetadata(PIPES_METADATA, controller) ?? []) as [ClassConstructor, string?][]; - return { ...metadata, methods, params, pipes }; + return { ...metadata, methods, pipes }; } getMetadata(key: string, target: unknown, propertyKey?: string) { @@ -21,4 +20,8 @@ export class Reflector { getModuleMetadata(module: ClassConstructor) { return Reflect.getMetadata(MODULE_METADATA, module) as ModuleMetadata; } + + getParamsMetadata(controller: ClassConstructor, methodName: string) { + return (Reflect.getMetadata(PARAMS_METADATA, controller.prototype[methodName]) ?? []) as ParamMetadata[]; + } } diff --git a/server/src/core/providers.ts b/server/src/core/providers.ts index a62dc00..5551100 100644 --- a/server/src/core/providers.ts +++ b/server/src/core/providers.ts @@ -1,12 +1,16 @@ import { Provider } from '@decorators/di'; -import { ModuleResolver, ParamValidator, Pipeline, Reflector } from './helpers'; +import { MetadataScanner, ModuleResolver, ParamValidator, Pipeline, Reflector } from './helpers'; export const DEFAULT_PROVIDERS = [ { provide: Reflector, useClass: Reflector, }, + { + provide: MetadataScanner, + useClass: MetadataScanner, + }, { provide: ModuleResolver, useClass: ModuleResolver, diff --git a/server/src/core/types.ts b/server/src/core/types.ts index d6bd951..4bb4264 100644 --- a/server/src/core/types.ts +++ b/server/src/core/types.ts @@ -38,6 +38,8 @@ export interface ParamMetadata { // argument name defined in the function argName?: string; argType?: Handler | ClassConstructor; + // If decorator is used multiple times over the same method + callIndex: number; factory?: (context: any) => Promise | any; index: number; methodName: string; @@ -45,3 +47,12 @@ export interface ParamMetadata { paramType: string; paramValidator?: Validator; } + +export interface Metadata extends MethodMetadata { + controller: ClassConstructor; + module: ClassConstructor; + params: ParamMetadata[]; + paths: string[]; + pipes: ClassConstructor[]; + url: string; +} diff --git a/server/src/index.ts b/server/src/index.ts index b287fa5..2f738a8 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,8 +1,10 @@ import 'reflect-metadata'; export { Application } from './core/application'; export * from './core/decorators'; +export { MetadataScanner } from './core/helpers'; export * from './core/helpers/constants/http-status'; export * from './core/helpers/constants/injectables'; +export { Context } from './core/helpers/context'; export { createParamDecorator, Decorate } from './core/helpers/decorators'; export * from './core/helpers/errors'; export { PipeHandle, ProcessPipe } from './core/helpers/pipe'; diff --git a/server/src/platforms/express/express-adapter.ts b/server/src/platforms/express/express-adapter.ts index fbb5726..c2a3cf9 100644 --- a/server/src/platforms/express/express-adapter.ts +++ b/server/src/platforms/express/express-adapter.ts @@ -14,7 +14,9 @@ export class ExpressAdapter implements HttpApplicationAdapter { } close() { - this.server?.close(); + if (this.server.listening) { + this.server.close(); + } } getParam(type: ParameterType, name: string, req: express.Request, res: express.Response) { diff --git a/server/src/platforms/fastify/fastify-adapter.ts b/server/src/platforms/fastify/fastify-adapter.ts index 6eb7d94..dea087f 100644 --- a/server/src/platforms/fastify/fastify-adapter.ts +++ b/server/src/platforms/fastify/fastify-adapter.ts @@ -16,6 +16,10 @@ export class FastifyAdapter implements HttpApplicationAdapter { async close() { await this.app.close(); + + if (this.server.listening) { + this.server.close(); + } } getParam(type: ParameterType, name: string, req: Fastify.FastifyRequest, res: Fastify.FastifyReply) { diff --git a/server/src/platforms/http/helpers/constants/injectables.ts b/server/src/platforms/http/helpers/constants/injectables.ts index 90e3616..364fbfd 100644 --- a/server/src/platforms/http/helpers/constants/injectables.ts +++ b/server/src/platforms/http/helpers/constants/injectables.ts @@ -1,3 +1,3 @@ import { InjectionToken } from '@decorators/di'; -export const HTTP_ADAPTER = new InjectionToken('__server__:adapter'); +export const HTTP_ADAPTER = new InjectionToken('__server_http__:adapter'); diff --git a/server/src/platforms/http/helpers/index.ts b/server/src/platforms/http/helpers/index.ts index ff74041..8e7bc33 100644 --- a/server/src/platforms/http/helpers/index.ts +++ b/server/src/platforms/http/helpers/index.ts @@ -1,6 +1,5 @@ export * from './constants'; export * from './http-application-adapter'; export * from './http-context'; -export * from './metadata-scanner'; export * from './route-handler'; export * from './route-resolver'; diff --git a/server/src/platforms/http/helpers/metadata-scanner.ts b/server/src/platforms/http/helpers/metadata-scanner.ts deleted file mode 100644 index adf61c7..0000000 --- a/server/src/platforms/http/helpers/metadata-scanner.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Inject, Injectable, Optional } from '@decorators/di'; - -import { addLeadingSlash, APP_VERSION, buildUrl, ClassConstructor, isEnum, MethodMetadata, Reflector, ROOT_MODULE } from '../../../core'; -import { RouteMetadata } from '../types'; -import { HttpMethodType, METHOD_TEMPLATE_METADATA } from './constants'; - -@Injectable() -export class MetadataScanner { - constructor( - @Inject(APP_VERSION) @Optional() private appVersion: string, - @Inject(ROOT_MODULE) private rootModule: ClassConstructor, - private reflector: Reflector, - ) { } - - scan() { - return this.scanModule(this.rootModule); - } - - private scanModule(module: ClassConstructor, parentNamespace = ''): RouteMetadata[] { - const { controllers, modules, namespace } = this.reflector.getModuleMetadata(module); - - const routes = controllers.map(controller => { - const metadata = this.reflector.getControllerMetadata(controller); - const methods = metadata.methods - .filter((method: MethodMetadata) => isEnum(HttpMethodType, method.type)); - - return methods.map((method: MethodMetadata) => { - const template = this.reflector.getMetadata(METHOD_TEMPLATE_METADATA, controller.prototype[method.methodName]) as string; - const params = metadata.params.filter(param => param.methodName === method.methodName); - const pipes = metadata.pipes - .filter(([, methodName]) => !methodName || methodName === method.methodName) - .map(([pipe]) => pipe); - - const version = metadata.options?.ignoreVersion || !this.appVersion ? '' : this.appVersion; - const url = addLeadingSlash(buildUrl(version, parentNamespace, namespace, metadata.url, method.url)); - - return { - ...method, - controller, - module, - params, - pipes, - template, - url, - } as RouteMetadata; - }); - }); - - const nestedRoutes = modules.map(module => this.scanModule(module, namespace)); - - return [...nestedRoutes.flat(), ...routes.flat()]; - } -} diff --git a/server/src/platforms/http/helpers/route-handler.ts b/server/src/platforms/http/helpers/route-handler.ts index ecf39ea..735666f 100644 --- a/server/src/platforms/http/helpers/route-handler.ts +++ b/server/src/platforms/http/helpers/route-handler.ts @@ -1,18 +1,20 @@ import { Inject, Injectable, Optional } from '@decorators/di'; -import { ApiError, GLOBAL_PIPE, Handler, HttpStatus, ParamMetadata, ParamValidator, Pipeline, ProcessPipe, toStandardType } from '../../../core'; +import { GLOBAL_PIPE, Handler, HandlerCreator, HttpStatus, ParamMetadata, ParamValidator, Pipeline, ProcessPipe } from '../../../core'; import { HTTP_ADAPTER, ParameterType } from './constants'; import { HttpApplicationAdapter } from './http-application-adapter'; import { HttpContext } from './http-context'; @Injectable() -export class RouteHandler { +export class RouteHandler extends HandlerCreator { constructor( @Inject(HTTP_ADAPTER) private adapter: HttpApplicationAdapter, @Inject(GLOBAL_PIPE) @Optional() private pipes: ProcessPipe[] = [], private pipeline: Pipeline, private paramValidator: ParamValidator, - ) { } + ) { + super(); + } createHandler( controller: InstanceType, @@ -70,47 +72,7 @@ export class RouteHandler { }; } - message(message: unknown) { - if (message instanceof ApiError) { - return message.toObject(); - } - - if (message instanceof Error) { - return { message: message.message }; - } - - return message; - } - - async params(metadata: ParamMetadata[], context: HttpContext, args: unknown[]) { - const params$ = metadata - .sort((a, b) => a.index - b.index) - .map(param => param.factory - ? param.factory(context) - : this.adapter.getParam(param.paramType as ParameterType, param.paramName, ...args), - ); - const params = await Promise.all(params$); - - return params.map(paramFn => toStandardType(paramFn())); - } - - status(message: unknown, status: number) { - if (message instanceof ApiError) { - return message.status; - } - - if (message instanceof Error) { - return HttpStatus.INTERNAL_SERVER_ERROR; - } - - return status; - } - - private async runHandler(handler: Handler) { - try { - return await handler(); - } catch (error) { - return error; - } + getParam(param: ParamMetadata, args: unknown[]): unknown { + return this.adapter.getParam(param.paramType as ParameterType, param.paramName, ...args); } } diff --git a/server/src/platforms/http/helpers/route-resolver.ts b/server/src/platforms/http/helpers/route-resolver.ts index f69e802..f6c3d05 100644 --- a/server/src/platforms/http/helpers/route-resolver.ts +++ b/server/src/platforms/http/helpers/route-resolver.ts @@ -1,10 +1,9 @@ import { Inject, Injectable } from '@decorators/di'; -import { asyncMap, ClassConstructor, ContainerManager, ProcessPipe } from '../../../core'; +import { asyncMap, ClassConstructor, ContainerManager, isEnum, MetadataScanner, ProcessPipe, Reflector } from '../../../core'; import { RouteMetadata } from '../types'; -import { HTTP_ADAPTER } from './constants'; +import { HTTP_ADAPTER, HttpMethodType, METHOD_TEMPLATE_METADATA } from './constants'; import { HttpApplicationAdapter } from './http-application-adapter'; -import { MetadataScanner } from './metadata-scanner'; import { RouteHandler } from './route-handler'; @Injectable() @@ -14,10 +13,12 @@ export class RouteResolver { private containerManager: ContainerManager, private metadataScanner: MetadataScanner, private routeHandler: RouteHandler, + private reflector: Reflector, ) { } async resolve() { - const metadatas = this.metadataScanner.scan(); + const metadatas = this.metadataScanner.scan() + .filter(meta => isEnum(HttpMethodType, meta.type)); const baseRoutes = metadatas.filter(meta => !meta.url.includes('*')); const wildcardRoutes = metadatas @@ -33,13 +34,18 @@ export class RouteResolver { container.get(pipe), ); + const template = this.reflector.getMetadata( + METHOD_TEMPLATE_METADATA, + metadata.controller.prototype[metadata.methodName], + ); + const handler = this.routeHandler.createHandler( controller, metadata.methodName, metadata.params, routePipes, metadata.status, - metadata.template, + template, ); routes.push({ diff --git a/server/src/platforms/http/http.module.ts b/server/src/platforms/http/http.module.ts index eb77570..c70f59a 100644 --- a/server/src/platforms/http/http.module.ts +++ b/server/src/platforms/http/http.module.ts @@ -1,11 +1,10 @@ import { Inject } from '@decorators/di'; import { APP_SERVER, ClassConstructor, Module, ModuleWithProviders, Server } from '../../core'; -import { HTTP_ADAPTER, HttpApplicationAdapter, MetadataScanner, RouteHandler, RouteResolver } from './helpers'; +import { HTTP_ADAPTER, HttpApplicationAdapter, RouteHandler, RouteResolver } from './helpers'; @Module({ providers: [ - MetadataScanner, RouteHandler, RouteResolver, ], @@ -44,6 +43,10 @@ export class HttpModule { await this.adapter.listen(); + if (this.server.listening) { + return; + } + return this.server.listen(port); } diff --git a/server/src/platforms/http/index.ts b/server/src/platforms/http/index.ts index e1724fd..c375e52 100644 --- a/server/src/platforms/http/index.ts +++ b/server/src/platforms/http/index.ts @@ -1,4 +1,4 @@ export * from './decorators'; -export { HTTP_ADAPTER, HttpApplicationAdapter, HttpContext, MetadataScanner, ParameterType } from './helpers'; +export { HTTP_ADAPTER, HttpApplicationAdapter, HttpContext, ParameterType } from './helpers'; export * from './http.module'; export { AdapterRoute, RouteMetadata } from './types'; diff --git a/server/src/platforms/http/types.ts b/server/src/platforms/http/types.ts index 45bf9c6..efd70e5 100644 --- a/server/src/platforms/http/types.ts +++ b/server/src/platforms/http/types.ts @@ -1,12 +1,7 @@ -import { ClassConstructor, Handler, MethodMetadata, ParamMetadata } from '../../core'; +import { Handler, Metadata } from '../../core'; -export interface RouteMetadata extends MethodMetadata { - controller: ClassConstructor; - module: ClassConstructor; - params: ParamMetadata[]; - pipes: ClassConstructor[]; +export interface RouteMetadata extends Metadata { status?: number; - template?: string; } export interface AdapterRoute { diff --git a/server/src/platforms/koa/koa-adapter.ts b/server/src/platforms/koa/koa-adapter.ts index 8daabc1..1a63895 100644 --- a/server/src/platforms/koa/koa-adapter.ts +++ b/server/src/platforms/koa/koa-adapter.ts @@ -17,7 +17,9 @@ export class KoaAdapter implements HttpApplicationAdapter { } close() { - this.server?.close(); + if (this.server.listening) { + this.server.close(); + } } getParam(type: ParameterType, name: string, ctx: Koa.Context) { diff --git a/server/src/platforms/sockets/helpers/event-handler.ts b/server/src/platforms/sockets/helpers/event-handler.ts index 205c45a..bc14403 100644 --- a/server/src/platforms/sockets/helpers/event-handler.ts +++ b/server/src/platforms/sockets/helpers/event-handler.ts @@ -1,19 +1,21 @@ import { Inject, Injectable, Optional } from '@decorators/di'; -import { ApiError, GLOBAL_PIPE, Handler, ParamMetadata, ParamValidator, Pipeline, ProcessPipe, toStandardType } from '../../../core'; +import { GLOBAL_PIPE, Handler, HandlerCreator, ParamMetadata, ParamValidator, Pipeline, ProcessPipe } from '../../../core'; import { AckFunction } from '../types'; import { ParameterType, SOCKETS_ADAPTER } from './constants'; import { SocketsApplicationAdapter } from './sockets-application-adapter'; import { SocketsContext } from './sockets-context'; @Injectable() -export class EventHandler { +export class EventHandler extends HandlerCreator { constructor( @Inject(SOCKETS_ADAPTER) private adapter: SocketsApplicationAdapter, @Inject(GLOBAL_PIPE) @Optional() private pipes: ProcessPipe[] = [], private pipeline: Pipeline, private paramValidator: ParamValidator, - ) { } + ) { + super(); + } createHandler( controller: InstanceType, @@ -72,49 +74,7 @@ export class EventHandler { }; } - message(message: unknown) { - if (message instanceof ApiError) { - return message.toObject(); - } - - if (message instanceof Error) { - return { message: message.message }; - } - - return message; - } - - async params(metadata: ParamMetadata[], context: SocketsContext, args: unknown[]) { - let indexOverride = 0; - - const params$ = metadata - .sort((a, b) => a.index - b.index) - .map(param => { - if (param.factory) { - return param.factory(context); - } - - let index = param.index; - - if (param.paramType === ParameterType.PARAM) { - index = indexOverride; - - indexOverride++; - } - - return this.adapter.getParam(param.paramType as ParameterType, index, ...args); - }); - - const params = await Promise.all(params$); - - return params.map(paramFn => toStandardType(paramFn())); - } - - private async runHandler(handler: Handler) { - try { - return await handler(); - } catch (error) { - return error; - } + getParam(param: ParamMetadata, args: unknown[]): unknown { + return this.adapter.getParam(param.paramType as ParameterType, param.callIndex, ...args); } } diff --git a/server/src/platforms/sockets/helpers/event-resolver.ts b/server/src/platforms/sockets/helpers/event-resolver.ts index 9400db5..3cf0bf2 100644 --- a/server/src/platforms/sockets/helpers/event-resolver.ts +++ b/server/src/platforms/sockets/helpers/event-resolver.ts @@ -1,9 +1,9 @@ import { Inject, Injectable } from '@decorators/di'; -import { asyncMap, ClassConstructor, ContainerManager, ProcessPipe } from '../../../core'; -import { SOCKETS_ADAPTER } from './constants'; +import { asyncMap, ClassConstructor, ContainerManager, isEnum, MetadataScanner, ProcessPipe } from '../../../core'; +import { EventMetadata } from '../types'; +import { EventType, SOCKETS_ADAPTER } from './constants'; import { EventHandler } from './event-handler'; -import { MetadataScanner } from './metadata-scanner'; import { SocketsApplicationAdapter } from './sockets-application-adapter'; @Injectable() @@ -16,7 +16,9 @@ export class EventResolver { ) { } async resolve() { - const metadatas = this.metadataScanner.scan(); + const metadatas = this.metadataScanner.scan() + .filter(meta => isEnum(EventType, meta.type)); + const events = []; for (const metadata of metadatas) { diff --git a/server/src/platforms/sockets/helpers/index.ts b/server/src/platforms/sockets/helpers/index.ts index ee20316..3e63bff 100644 --- a/server/src/platforms/sockets/helpers/index.ts +++ b/server/src/platforms/sockets/helpers/index.ts @@ -1,6 +1,5 @@ export * from './constants'; export * from './event-handler'; export * from './event-resolver'; -export * from './metadata-scanner'; export * from './sockets-application-adapter'; export * from './sockets-context'; diff --git a/server/src/platforms/sockets/helpers/metadata-scanner.ts b/server/src/platforms/sockets/helpers/metadata-scanner.ts deleted file mode 100644 index 87b666f..0000000 --- a/server/src/platforms/sockets/helpers/metadata-scanner.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Inject, Injectable, Optional } from '@decorators/di'; - -import { addLeadingSlash, APP_VERSION, buildUrl, ClassConstructor, isEnum, Reflector, ROOT_MODULE } from '../../../core'; -import { EventMetadata } from '../types'; -import { EventType } from './constants'; - -@Injectable() -export class MetadataScanner { - constructor( - @Inject(APP_VERSION) @Optional() private appVersion: string, - @Inject(ROOT_MODULE) private rootModule: ClassConstructor, - private reflector: Reflector, - ) { } - - scan() { - return this.scanModule(this.rootModule); - } - - private scanModule(module: ClassConstructor, parentNamespaces: string[] = []): EventMetadata[] { - const { controllers, modules, namespace } = this.reflector.getModuleMetadata(module); - - const events = controllers.map(controller => { - const metadata = this.reflector.getControllerMetadata(controller); - const methods = metadata.methods - .filter((method: EventMetadata) => isEnum(EventType, method.type)); - - return methods.map((method: EventMetadata) => { - const params = metadata.params.filter(param => param.methodName === method.methodName); - const pipes = metadata.pipes - .filter(([, methodName]) => !methodName || methodName === method.methodName) - .map(([pipe]) => pipe); - - const version = metadata.options?.ignoreVersion || !this.appVersion ? '' : this.appVersion; - const paths = [version, ...parentNamespaces, namespace, metadata.url].filter(Boolean); - const url = addLeadingSlash(buildUrl(...paths)); - - return { - ...method, - controller, - module, - params, - paths, - pipes, - url, - } as EventMetadata; - }); - }); - - const nestedEvents = modules.map(module => this.scanModule(module, [...parentNamespaces, namespace])); - - return [...nestedEvents.flat(), ...events.flat()]; - } -} diff --git a/server/src/platforms/sockets/index.ts b/server/src/platforms/sockets/index.ts index e1b33eb..f7b35f3 100644 --- a/server/src/platforms/sockets/index.ts +++ b/server/src/platforms/sockets/index.ts @@ -1,4 +1,4 @@ export * from './decorators'; -export { MetadataScanner, ParameterType, SOCKETS_ADAPTER, SocketsApplicationAdapter, SocketsContext } from './helpers'; +export { ParameterType, SOCKETS_ADAPTER, SocketsApplicationAdapter, SocketsContext } from './helpers'; export * from './sockets.module'; export { AdapterEvent, EventMetadata } from './types'; diff --git a/server/src/platforms/sockets/sockets.module.ts b/server/src/platforms/sockets/sockets.module.ts index 418b321..f6dd86a 100644 --- a/server/src/platforms/sockets/sockets.module.ts +++ b/server/src/platforms/sockets/sockets.module.ts @@ -1,11 +1,10 @@ import { Inject } from '@decorators/di'; import { APP_SERVER, ClassConstructor, Module, ModuleWithProviders, Server } from '../../core'; -import { EventHandler, EventResolver, MetadataScanner, SOCKETS_ADAPTER, SocketsApplicationAdapter } from './helpers'; +import { EventHandler, EventResolver, SOCKETS_ADAPTER, SocketsApplicationAdapter } from './helpers'; @Module({ providers: [ - MetadataScanner, EventHandler, EventResolver, ], diff --git a/server/src/platforms/sockets/types.ts b/server/src/platforms/sockets/types.ts index 3f9c328..d2bd269 100644 --- a/server/src/platforms/sockets/types.ts +++ b/server/src/platforms/sockets/types.ts @@ -1,13 +1,9 @@ -import { ClassConstructor, Handler, MethodMetadata, ParamMetadata } from '../../core'; +import { Handler, Metadata } from '../../core'; export type AckFunction = (...args: any[]) => void; -export interface EventMetadata extends MethodMetadata { - controller: ClassConstructor; +export interface EventMetadata extends Metadata { event?: string; - module: ClassConstructor; - params: ParamMetadata[]; - pipes: ClassConstructor[]; } export interface AdapterEvent { diff --git a/server/src/platforms/swagger/helpers/swagger-ui/swagger-document.ts b/server/src/platforms/swagger/helpers/swagger-ui/swagger-document.ts index afadc69..d6008a2 100644 --- a/server/src/platforms/swagger/helpers/swagger-ui/swagger-document.ts +++ b/server/src/platforms/swagger/helpers/swagger-ui/swagger-document.ts @@ -1,8 +1,8 @@ import { Inject, Injectable, Optional } from '@decorators/di'; import { OpenAPIV3_1 } from 'openapi-types'; -import { APP_VERSION, ClassConstructor, Handler, Reflector } from '../../../../core'; -import { MetadataScanner, ParameterType, RouteMetadata } from '../../../http'; +import { APP_VERSION, ClassConstructor, Handler, MetadataScanner, Reflector } from '../../../../core'; +import { ParameterType, RouteMetadata } from '../../../http'; import { ApiResponse, SwaggerConfig } from '../../types'; import { METHOD_API_RESPONSE_METADATA, METHOD_API_RESPONSES_METADATA, METHOD_API_SECURITY_METADATA, PROPERTY_API_PARAMETER_METADATA, SWAGGER_CONFIG } from '../constants'; import { getValidationMeta, isStandardType, pick, replaceUrlParameters, typeToContentType } from './utils';