diff --git a/server/README.md b/server/README.md index 0f90dc4..c550a82 100644 --- a/server/README.md +++ b/server/README.md @@ -24,7 +24,7 @@ Fully working example can be found in [example](example) folder. ## Application In order to create an application, use `Application` class with app root module: ```typescript -const app = await Application.create(AppModule); +const app = await Application.create(AppModule, server?); ``` Application instance provides an `inject` method to retrieve instances of any provided objects: @@ -42,6 +42,7 @@ await module.listen(3000); ```typescript import { Application, Module } from '@decorators/server'; import { HttpModule } from '@decorators/server/http'; +import { ExpressAdapter } from '@decorators/server/express'; @Module({ modules: [ @@ -52,7 +53,14 @@ export class AppModule { } ``` ## Adapters -* `ExpressAdapter` - adapter for [express](https://github.com/expressjs/express) +* `ExpressAdapter` - adapter for [express](https://github.com/expressjs/express) from `@decorators/server/express` +* `FastifyAdapter` - adapter for [fastify](https://github.com/fastify/fastify) from `@decorators/server/fastify` +* `KoaAdapter` - adapter for [koa](https://github.com/koajs/koa) from `@decorators/server/koa` + +Adapter can be instantiated with existing application (for example express application): +```ts +HttpModule.create(new ExpressAdapter(app)); +``` ## Payload vaidation Package supports [class-validator](https://github.com/typestack/class-validator) and [class-transformer](https://github.com/typestack/class-transformer) packages, basic types validation is supported as well: diff --git a/server/package-lock.json b/server/package-lock.json index 4ab8701..c97a54a 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@decorators/server", - "version": "1.0.0-beta.13", + "version": "1.0.0-beta.14", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@decorators/server", - "version": "1.0.0-beta.13", + "version": "1.0.0-beta.14", "license": "MIT", "devDependencies": { "@decorators/di": "../di", diff --git a/server/package.json b/server/package.json index e2984ff..3f9ac02 100644 --- a/server/package.json +++ b/server/package.json @@ -142,5 +142,5 @@ ] } }, - "version": "1.0.0-beta.13" + "version": "1.0.0-beta.14" } diff --git a/server/src/core/application.ts b/server/src/core/application.ts index 0bad1d6..5517768 100644 --- a/server/src/core/application.ts +++ b/server/src/core/application.ts @@ -1,17 +1,22 @@ import { InjectionToken, RootContainer } from '@decorators/di'; +import { createServer } from 'http'; -import { ContainerManager, ModuleResolver, ROOT_MODULE } from './helpers'; +import { APP_SERVER, ContainerManager, ModuleResolver, ROOT_MODULE } from './helpers'; import { DEFAULT_PROVIDERS } from './providers'; -import { ClassConstructor } from './types'; +import { ClassConstructor, Server } from './types'; export class Application { - static async create(rootModule: ClassConstructor) { + static async create(rootModule: ClassConstructor, server?: Server) { const containerManger = new ContainerManager(); const container = containerManger.create(Application); container.setParent(RootContainer); container.provide([ + { + provide: APP_SERVER, + useValue: server ?? createServer(), + }, { provide: ContainerManager, useValue: containerManger, diff --git a/server/src/core/helpers/constants/injectables.ts b/server/src/core/helpers/constants/injectables.ts index 3d6b40a..174578c 100644 --- a/server/src/core/helpers/constants/injectables.ts +++ b/server/src/core/helpers/constants/injectables.ts @@ -11,3 +11,5 @@ export const ROOT_MODULE_INSTANCE = new InjectionToken('__server__:root-module-i * Provides global application prefix/version for entire server */ export const APP_VERSION = new InjectionToken('__server__:app-version'); + +export const APP_SERVER = new InjectionToken('__server__'); diff --git a/server/src/core/types.ts b/server/src/core/types.ts index ad75756..d6bd951 100644 --- a/server/src/core/types.ts +++ b/server/src/core/types.ts @@ -1,8 +1,13 @@ import { Provider } from '@decorators/di'; +import type { Server as HttpServer } from 'http'; +import type { Http2SecureServer as Http2Server } from 'http2'; +import type { Server as HttpsServer } from 'https'; export type ClassConstructor = new (...args: any[]) => T; export type Handler = (...args: unknown[]) => Promise | unknown; +export type Server = HttpServer | HttpsServer | Http2Server; + export interface ModuleMetadata { controllers: ClassConstructor[]; modules: ClassConstructor[]; diff --git a/server/src/core/utils.ts b/server/src/core/utils.ts index c62ab92..d2df317 100644 --- a/server/src/core/utils.ts +++ b/server/src/core/utils.ts @@ -6,6 +6,7 @@ export function addLeadingSlash(url: string): string { export function buildUrl(...paths: string[]) { return paths + .filter(Boolean) .map(path => path.startsWith('/') ? path.slice(1) : path) .filter(Boolean) .join('/'); @@ -41,3 +42,7 @@ export function isClass(type: Handler | ClassConstructor) { export function isFunction(type: Handler | ClassConstructor) { return typeof type === 'function' && !isClass(type); } + +export function isEnum(type: object, val: unknown) { + return Object.values(type).includes(val); +} diff --git a/server/src/platforms/express/express-adapter.ts b/server/src/platforms/express/express-adapter.ts index c1221ba..fbb5726 100644 --- a/server/src/platforms/express/express-adapter.ts +++ b/server/src/platforms/express/express-adapter.ts @@ -1,14 +1,17 @@ import * as express from 'express'; -import { Server } from 'http'; -import { HttpApplicationAdapter, ParameterType } from '../http/helpers'; -import { Route } from '../http/types'; +import { Server } from '../../core'; +import { AdapterRoute, HttpApplicationAdapter, ParameterType } from '../http'; export class ExpressAdapter implements HttpApplicationAdapter { - server?: Server; type = 'express'; + private server: Server; - constructor(public app: express.Express = express()) { } + constructor(public app = express()) { } + + attachServer(server: Server) { + this.server = server; + } close() { this.server?.close(); @@ -31,8 +34,8 @@ export class ExpressAdapter implements HttpApplicationAdapter { return response.headersSent; } - listen(port: number) { - this.server = this.app.listen(port); + listen() { + this.server.on('request', this.app); } render(response: express.Response, template: string, message: object) { @@ -57,7 +60,7 @@ export class ExpressAdapter implements HttpApplicationAdapter { return response.send(message); } - routes(routes: Route[]) { + routes(routes: AdapterRoute[]) { for (const route of routes) { this.app[route.type]?.(route.url, route.handler); } diff --git a/server/src/platforms/fastify/fastify-adapter.ts b/server/src/platforms/fastify/fastify-adapter.ts index 3375048..6eb7d94 100644 --- a/server/src/platforms/fastify/fastify-adapter.ts +++ b/server/src/platforms/fastify/fastify-adapter.ts @@ -1,18 +1,21 @@ import * as FastifyStatic from '@fastify/static'; import * as Fastify from 'fastify'; -import { Server } from 'http'; -import { HttpApplicationAdapter, ParameterType } from '../http/helpers'; -import { Route } from '../http/types'; +import { Server } from '../../core'; +import { AdapterRoute, HttpApplicationAdapter, ParameterType } from '../http'; export class FastifyAdapter implements HttpApplicationAdapter { - server?: Server; type = 'fastify'; + private server: Server; - constructor(public app: Fastify.FastifyInstance = Fastify()) { } + constructor(public app = Fastify()) { } - close() { - this.server?.close(); + attachServer(server: Server) { + this.server = server; + } + + async close() { + await this.app.close(); } getParam(type: ParameterType, name: string, req: Fastify.FastifyRequest, res: Fastify.FastifyReply) { @@ -32,10 +35,9 @@ export class FastifyAdapter implements HttpApplicationAdapter { return response.sent; } - async listen(port: number) { - await this.app.listen({ port }); - - this.server = this.app.server; + async listen() { + await this.app.ready(); + this.server.on('request', this.app.routing); } render(_response: Fastify.FastifyReply, template: string, message: object) { @@ -60,7 +62,7 @@ export class FastifyAdapter implements HttpApplicationAdapter { return response.send(message); } - routes(routes: Route[]) { + routes(routes: AdapterRoute[]) { for (const route of routes) { this.app[route.type]?.(route.url, route.handler); } diff --git a/server/src/platforms/http/helpers/http-application-adapter.ts b/server/src/platforms/http/helpers/http-application-adapter.ts index 996650e..d6b5bd2 100644 --- a/server/src/platforms/http/helpers/http-application-adapter.ts +++ b/server/src/platforms/http/helpers/http-application-adapter.ts @@ -1,18 +1,17 @@ -import { Server } from 'http'; - -import { Route } from '../types'; +import { Server } from '../../../core'; +import { AdapterRoute } from '../types'; import { ParameterType } from './constants'; export abstract class HttpApplicationAdapter { - abstract server?: Server; abstract type: string; - abstract close(): void; + abstract attachServer(server: Server): void; + abstract close(): Promise | void; abstract getParam(type: ParameterType, name?: string, ...args: any[]): Promise<() => unknown> | (() => unknown); abstract isHeadersSent(response: unknown): Promise | boolean; - abstract listen(port: number): Promise | void; + abstract listen(): Promise | void; abstract render(response: unknown, template: string, message: unknown): Promise | string; abstract reply(response: unknown, message: unknown, statusCode?: number): Promise | unknown; - abstract routes(routes: Route[]): void; + abstract routes(routes: AdapterRoute[]): void; abstract serveStatic(prefix: string, path: string, options?: object): void; abstract set?(setting: string, value: unknown): void; abstract setHeader(response: unknown, name: string, value: string): void; diff --git a/server/src/platforms/http/helpers/metadata-scanner.ts b/server/src/platforms/http/helpers/metadata-scanner.ts index acdde46..adf61c7 100644 --- a/server/src/platforms/http/helpers/metadata-scanner.ts +++ b/server/src/platforms/http/helpers/metadata-scanner.ts @@ -1,8 +1,8 @@ import { Inject, Injectable, Optional } from '@decorators/di'; -import { addLeadingSlash, APP_VERSION, buildUrl, ClassConstructor, MethodMetadata, Reflector, ROOT_MODULE } from '../../../core'; +import { addLeadingSlash, APP_VERSION, buildUrl, ClassConstructor, isEnum, MethodMetadata, Reflector, ROOT_MODULE } from '../../../core'; import { RouteMetadata } from '../types'; -import { METHOD_TEMPLATE_METADATA } from './constants'; +import { HttpMethodType, METHOD_TEMPLATE_METADATA } from './constants'; @Injectable() export class MetadataScanner { @@ -21,8 +21,10 @@ export class MetadataScanner { const routes = controllers.map(controller => { const metadata = this.reflector.getControllerMetadata(controller); + const methods = metadata.methods + .filter((method: MethodMetadata) => isEnum(HttpMethodType, method.type)); - return metadata.methods.map((method: MethodMetadata) => { + 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 diff --git a/server/src/platforms/http/http.module.ts b/server/src/platforms/http/http.module.ts index 0155bc8..eb77570 100644 --- a/server/src/platforms/http/http.module.ts +++ b/server/src/platforms/http/http.module.ts @@ -1,6 +1,6 @@ import { Inject } from '@decorators/di'; -import { ClassConstructor, Module, ModuleWithProviders } from '../../core'; +import { APP_SERVER, ClassConstructor, Module, ModuleWithProviders, Server } from '../../core'; import { HTTP_ADAPTER, HttpApplicationAdapter, MetadataScanner, RouteHandler, RouteResolver } from './helpers'; @Module({ @@ -11,33 +11,40 @@ import { HTTP_ADAPTER, HttpApplicationAdapter, MetadataScanner, RouteHandler, Ro ], }) export class HttpModule { - static create(adapter: ClassConstructor) { + static create( + adapter: ClassConstructor | InstanceType>, + ) { return { module: HttpModule, providers: [{ provide: HTTP_ADAPTER, - useClass: adapter, + ...(adapter instanceof HttpApplicationAdapter ? { useValue: adapter } : { useClass: adapter }), }], } as ModuleWithProviders; } constructor( + @Inject(APP_SERVER) private server: Server, @Inject(HTTP_ADAPTER) private adapter: HttpApplicationAdapter, private routeResolver: RouteResolver, - ) { } + ) { + this.adapter.attachServer(this.server); + } close() { - this.adapter.close(); + return this.adapter.close(); } getHttpServer() { - return this.adapter.server; + return this.server; } async listen(port?: number) { await this.routeResolver.resolve(); - return this.adapter.listen(port); + await this.adapter.listen(); + + return this.server.listen(port); } set(setting: string, value: unknown) { diff --git a/server/src/platforms/http/index.ts b/server/src/platforms/http/index.ts index ab56021..e1724fd 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 * from './http.module'; -export { RouteMetadata } from './types'; +export { AdapterRoute, RouteMetadata } from './types'; diff --git a/server/src/platforms/http/types.ts b/server/src/platforms/http/types.ts index 9c25397..45bf9c6 100644 --- a/server/src/platforms/http/types.ts +++ b/server/src/platforms/http/types.ts @@ -9,7 +9,7 @@ export interface RouteMetadata extends MethodMetadata { template?: string; } -export interface Route { +export interface AdapterRoute { handler: Handler; type: string; url: string; diff --git a/server/src/platforms/koa/koa-adapter.ts b/server/src/platforms/koa/koa-adapter.ts index bfb9d81..8daabc1 100644 --- a/server/src/platforms/koa/koa-adapter.ts +++ b/server/src/platforms/koa/koa-adapter.ts @@ -1,17 +1,20 @@ -import { Server } from 'http'; import * as Koa from 'koa'; import * as koaMount from 'koa-mount'; import * as KoaRouter from 'koa-router'; import * as koaStatic from 'koa-static'; -import { HttpApplicationAdapter, ParameterType } from '../http/helpers'; -import { Route } from '../http/types'; +import { Server } from '../../core'; +import { AdapterRoute, HttpApplicationAdapter, ParameterType } from '../http'; export class KoaAdapter implements HttpApplicationAdapter { - server?: Server; type = 'koa'; + private server: Server; - constructor(public app: Koa = new Koa()) { } + constructor(public app = new Koa()) { } + + attachServer(server: Server): void { + this.server = server; + } close() { this.server?.close(); @@ -37,8 +40,8 @@ export class KoaAdapter implements HttpApplicationAdapter { return response.headerSent; } - listen(port: number) { - this.server = this.app.listen(port); + listen() { + this.server.on('request', this.app.callback()); } async render(response: Koa.Response, template: string, message: object) { @@ -65,7 +68,7 @@ export class KoaAdapter implements HttpApplicationAdapter { response.ctx.body = message; } - routes(routes: Route[]) { + routes(routes: AdapterRoute[]) { const router = new KoaRouter(); for (const route of routes) {