Skip to content

Commit

Permalink
fix(common): Allow a serverless usage
Browse files Browse the repository at this point in the history
  • Loading branch information
Romakita committed Nov 19, 2021
1 parent 3576fc7 commit e323476
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 48 deletions.
2 changes: 1 addition & 1 deletion .nycrc
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@
"lines": 99.55,
"statements": 99.52,
"functions": 99.17,
"branches": 89.36
"branches": 89.3
}
40 changes: 39 additions & 1 deletion packages/platform/common/src/builder/PlatformBuilder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BeforeListen,
BeforeRoutesInit,
Controller,
Injectable,
InjectorService,
Module,
normalizePath,
Expand All @@ -32,8 +33,12 @@ describe("PlatformBuilder", () => {
}
];

static create(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
return PlatformBuilder.build<PlatformCustom>(this, module, settings);
}

static async bootstrap(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
return PlatformBuilder.build<PlatformCustom>(this, module, settings).bootstrap(module, settings);
return PlatformBuilder.build<PlatformCustom>(this, module, settings).bootstrap();
}

async loadStatics(): Promise<void> {
Expand Down Expand Up @@ -146,6 +151,39 @@ describe("PlatformBuilder", () => {
expect(server.injector.emit).to.have.been.calledWithExactly("$onDestroy");
});
});

describe("callback()", () => {
it("should return the callback", async () => {
// WHEN
const server = await PlatformCustom.bootstrap(ServerModule, {
httpPort: false,
httpsPort: false
});

expect(server.callback()).to.deep.eq(server.app.raw);

server.callback({} as any, {} as any);
});
});

describe("useProvider()", () => {
it("should add provider", async () => {
// WHEN
const server = PlatformCustom.create(ServerModule, {
httpPort: false,
httpsPort: false
});

@Injectable()
class Token {}

server.useProvider(Token, {});

await server.bootstrap();

expect(server.injector.get(Token)).to.be.instanceof(Token);
});
});
describe("addComponents", () => {
it("should add components", async () => {
// GIVEN
Expand Down
76 changes: 44 additions & 32 deletions packages/platform/common/src/builder/PlatformBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {nameOf, toMap, Type} from "@tsed/core";
import {colors, Container, createContainer, InjectorService, IProvider, ProviderScope, setLoggerLevel} from "@tsed/di";
import {colors, createContainer, InjectorService, IProvider, ProviderScope, setLoggerLevel} from "@tsed/di";
import {importProviders} from "@tsed/components-scan";
import {getMiddlewaresForHook} from "@tsed/platform-middlewares";
import {GlobalAcceptMimesMiddleware} from "../middlewares";
Expand All @@ -22,6 +22,16 @@ import {PlatformStaticsSettings} from "../config/interfaces/PlatformStaticsSetti
import {getStaticsOptions} from "../utils/getStaticsOptions";
import {Route} from "../interfaces/Route";
import {getConfiguration} from "../utils/getConfiguration";
import {IncomingMessage, ServerResponse} from "http";

const DEFAULT_PROVIDERS = [
{provide: PlatformHandler},
{provide: PlatformResponse},
{provide: PlatformRequest},
{provide: PlatformRouter},
{provide: PlatformApplication},
{provide: Platform}
];

/**
* @ignore
Expand All @@ -34,38 +44,36 @@ export interface PlatformType<T = any> extends Type<T> {
* @ignore
*/
export interface PlatformBootstrap {
create(module: Type<any>, settings?: Partial<TsED.Configuration>): PlatformBuilder;

bootstrap(module: Type<any>, settings?: Partial<TsED.Configuration>): Promise<PlatformBuilder>;
}

export interface PlatformBuilderOptions {
name: string;
providers: IProvider[];
settings: any;
module: Type<any>;
}

/**
* @platform
*/
export abstract class PlatformBuilder<App = TsED.Application, Router = TsED.Router> {
static currentPlatform: Type<PlatformBuilder<any, any>> & PlatformBootstrap;

readonly name: string = "";
protected startedAt = new Date();
protected current = new Date();
protected locals: Container;

#injector: InjectorService;
#providers: Map<Type, IProvider>;
#rootModule: Type<any>;

constructor({name, providers, settings, module}: {name: string; providers: IProvider[]; settings: any; module: Type<any>}) {
constructor({name, providers, settings, module}: PlatformBuilderOptions) {
this.name = name;
this.#providers = toMap<any, IProvider>(providers, "provide");

this.locals = new Container();

this.#rootModule = module;
const configuration = getConfiguration(settings, module);

this.useProvider(PlatformHandler, this.#providers.get(PlatformHandler))
.useProvider(PlatformResponse, this.#providers.get(PlatformResponse))
.useProvider(PlatformRequest, this.#providers.get(PlatformRequest))
.useProvider(PlatformRouter, this.#providers.get(PlatformRouter))
.useProvider(PlatformApplication, this.#providers.get(PlatformApplication))
.useProvider(Platform, this.#providers.get(Platform));
this.createInjector(module, providers, settings);
}

get injector(): InjectorService {
Expand Down Expand Up @@ -130,6 +138,16 @@ export abstract class PlatformBuilder<App = TsED.Application, Router = TsED.Rout
});
}

callback(): (req: IncomingMessage, res: ServerResponse) => void;
callback(req: IncomingMessage, res: ServerResponse): void;
callback(req?: IncomingMessage, res?: ServerResponse) {
if (req && res) {
return this.app.callback()(req, res);
}

return this.app.callback();
}

log(...data: any[]) {
return !this.disableBootstrapLog && this.logger.info(...data, this.diff());
}
Expand Down Expand Up @@ -250,11 +268,15 @@ export abstract class PlatformBuilder<App = TsED.Application, Router = TsED.Rout
}

useProvider(token: Type<any>, settings?: Partial<IProvider>) {
this.locals.addProvider(token, settings);
this.injector.addProvider(token, settings);

return this;
}

async bootstrap() {
return this.runLifecycle();
}

protected diff() {
const ms = colors.yellow(`+${new Date().getTime() - this.current.getTime()}ms`);
this.current = new Date();
Expand Down Expand Up @@ -290,17 +312,6 @@ export abstract class PlatformBuilder<App = TsED.Application, Router = TsED.Rout
return this;
}

protected async bootstrap(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
this.createInjector(module, {
...settings,
PLATFORM_NAME: this.name
});

await this.runLifecycle();

return this;
}

protected async listenServers(): Promise<void> {
await Promise.all([listenHttpServer(this.injector), listenHttpsServer(this.injector)]);
}
Expand Down Expand Up @@ -342,15 +353,16 @@ export abstract class PlatformBuilder<App = TsED.Application, Router = TsED.Rout
await this.callHook("$afterRoutesInit");
}

protected createInjector(module: Type<any>, settings: any) {
// configure locals providers
this.#rootModule = module;
protected createInjector(module: Type<any>, providers: IProvider[], settings: any) {
const configuration = getConfiguration(settings, module);
configuration.PLATFORM_NAME = this.name;

this.#injector = createInjector(configuration);

this.locals.forEach((provider) => {
this.injector.addProvider(provider.token, provider);
providers = [...DEFAULT_PROVIDERS, ...providers];

toMap<any, IProvider>(providers, "provide").forEach((provider, token) => {
this.injector.addProvider(token, provider);
});

createPlatformApplication(this.injector);
Expand Down
3 changes: 2 additions & 1 deletion packages/platform/common/src/services/PlatformTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ export class PlatformTest extends DITest {
}

// @ts-ignore
instance = await PlatformBuilder.build(platform).bootstrap(mod, DITest.configure(settings));
settings = DITest.configure(settings);
instance = await PlatformBuilder.build(platform, mod, settings).bootstrap();

if (!settings.listen) {
await instance.callHook("$beforeListen");
Expand Down
6 changes: 4 additions & 2 deletions packages/platform/platform-aws/src/components/PlatformAws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ export class PlatformAws {
// istanbul ignore next
PlatformBuilder.currentPlatform = settings.platform || PlatformBuilder.currentPlatform;

this.promise = PlatformBuilder.currentPlatform.bootstrap(module, {...settings, disableComponentScan: true}).then(PlatformAws.onInit);
const platform = PlatformBuilder.currentPlatform.create(module, settings);

this.promise = platform.bootstrap().then(PlatformAws.onInit);

return PlatformAws;
}
Expand All @@ -66,7 +68,7 @@ export class PlatformAws {
await platform.callHook("$beforeListen");

// create Aws server
PlatformAws.awsServer = createServer(platform.app.callback(), PlatformAws.onListen, binaryMimeTypes);
PlatformAws.awsServer = createServer(platform.callback(), PlatformAws.onListen, binaryMimeTypes);
}

protected static async onListen() {
Expand Down
5 changes: 3 additions & 2 deletions packages/platform/platform-express/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
},
"scripts": {
"build": "tsc --build tsconfig.compile.json",
"start": "ts-node -r tsconfig-paths/register test/app/index.ts"
"start": "ts-node -r tsconfig-paths/register test/app/index.ts",
"start:emulate": "ts-node -r tsconfig-paths/register test/app/emulate.ts"
},
"dependencies": {
"express": "^4.17.1",
Expand Down Expand Up @@ -79,4 +80,4 @@
"@types/multer": "^1.4.5",
"body-parser": "1.19.0"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ describe("PlatformExpress", () => {
await PlatformExpress.bootstrap(Test);

expect(PlatformExpress.build).to.have.been.calledWithExactly(PlatformExpress, Test, {});
expect(platform.bootstrap).to.have.been.calledWithExactly(Test, {});
expect(platform.bootstrap).to.have.been.calledWithExactly();
expect(PlatformExpress.providers).to.deep.equal([
{
provide: PlatformApplication,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,27 @@ export class PlatformExpress extends PlatformBuilder<Express.Application, Expres
}
];

/**
* Create new serverless application. In this mode, the component scan are disabled.
* @param module
* @param settings
*/
static create(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
return this.build<PlatformExpress>(PlatformExpress, module, {
httpsPort: false,
httpPort: false,
...settings,
disableComponentsScan: true
});
}

/**
* Bootstrap a server application
* @param module
* @param settings
*/
static async bootstrap(module: Type<any>, settings: Partial<TsED.Configuration> = {}): Promise<PlatformExpress> {
return this.build<PlatformExpress>(PlatformExpress, module, settings).bootstrap(module, settings);
return this.build<PlatformExpress>(PlatformExpress, module, settings).bootstrap();
}

protected useRouter(): this {
Expand Down
27 changes: 27 additions & 0 deletions packages/platform/platform-express/test/app/emulate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import http from "http";
import {PlatformExpress} from "@tsed/platform-express";
import {Server} from "./Server";
import {Controller} from "@tsed/di";
import {Get} from "@tsed/schema";
import {QueryParams} from "@tsed/platform-params";

@Controller("/hello")
class HelloWorld {
@Get("/")
get(@QueryParams("q") query: string[]) {
return { test: "Hello world" };
}
}

const platform = PlatformExpress.create(Server, {
mount: { "/rest": [HelloWorld] }
});

const promise = platform.bootstrap()

const server = http.createServer(async (req, res) => {
await promise;
platform.callback(req, res);
});

server.listen(3002);
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe("PlatformKoa", () => {
await PlatformKoa.bootstrap(Test);

expect(PlatformKoa.build).to.have.been.calledWithExactly(PlatformKoa, Test, {});
expect(platform.bootstrap).to.have.been.calledWithExactly(Test, {});
expect(platform.bootstrap).to.have.been.calledWithExactly();
expect(PlatformKoa.providers).to.deep.equal([
{
provide: PlatformResponse,
Expand Down
29 changes: 23 additions & 6 deletions packages/platform/platform-koa/src/components/PlatformKoa.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import KoaRouter from "@koa/router";
import {
createContext,
IProvider,
PlatformApplication,
PlatformBuilder,
PlatformBuilderOptions,
PlatformExceptions,
PlatformHandler,
PlatformRequest,
Expand All @@ -14,6 +16,7 @@ import {Type} from "@tsed/core";
import Koa, {Context, Next} from "koa";
import {resourceNotFoundMiddleware} from "../middlewares/resourceNotFoundMiddleware";
import {PlatformKoaApplication, PlatformKoaHandler, PlatformKoaRequest, PlatformKoaResponse, PlatformKoaRouter} from "../services";
import {getConfiguration} from "@tsed/common/src/utils/getConfiguration";

/**
* @platform
Expand Down Expand Up @@ -43,12 +46,8 @@ export class PlatformKoa extends PlatformBuilder<Koa, KoaRouter> {
}
];

static async bootstrap(module: Type<any>, settings: Partial<TsED.Configuration> = {}): Promise<PlatformKoa> {
return this.build<PlatformKoa>(PlatformKoa, module, settings).bootstrap(module, settings);
}

protected createInjector(module: Type<any>, settings: any) {
super.createInjector(module, settings);
constructor(options: PlatformBuilderOptions) {
super(options);

const listener: any = (error: any, ctx: Koa.Context) => {
this.injector.get<PlatformExceptions>(PlatformExceptions)?.catch(error, ctx.request.$ctx);
Expand All @@ -58,6 +57,24 @@ export class PlatformKoa extends PlatformBuilder<Koa, KoaRouter> {
this.app.getApp().on("error", listener);
}

/**
* Create new serverless application. In this mode, the component scan are disabled.
* @param module
* @param settings
*/
static create(module: Type<any>, settings: Partial<TsED.Configuration> = {}) {
return this.build<PlatformKoa>(PlatformKoa, module, {
httpsPort: false,
httpPort: false,
...settings,
disableComponentsScan: true
});
}

static async bootstrap(module: Type<any>, settings: Partial<TsED.Configuration> = {}): Promise<PlatformKoa> {
return this.build<PlatformKoa>(PlatformKoa, module, settings).bootstrap();
}

protected useRouter(): this {
this.app.getApp().use(resourceNotFoundMiddleware).use(this.app.getRouter().routes()).use(this.app.getRouter().allowedMethods());

Expand Down

0 comments on commit e323476

Please sign in to comment.