@tsed/platform-http
Version:
A TypeScript Framework on top of Express
106 lines (105 loc) • 4.09 kB
JavaScript
import { AnyToPromiseStatus, catchAsyncError } from "@tsed/core";
import { inject, injectable, ProviderScope } from "@tsed/di";
import { $alter } from "@tsed/hooks";
import { PlatformExceptions } from "@tsed/platform-exceptions";
import { PlatformParams } from "@tsed/platform-params";
import { PlatformResponseFilter } from "@tsed/platform-response-filter";
import { PlatformHandlerMetadata, PlatformHandlerType, PlatformRouters, useResponseHandler } from "@tsed/platform-router";
import { AnyToPromiseWithCtx } from "../domain/AnyToPromiseWithCtx.js";
import { setResponseHeaders } from "../utils/setResponseHeaders.js";
import { PlatformAdapter } from "./PlatformAdapter.js";
/**
* Platform Handler abstraction layer. Wrap original class method to a pure platform handler (Express, Koa, etc...).
* @platform
*/
export class PlatformHandler {
constructor() {
this.responseFilter = inject(PlatformResponseFilter);
this.platformParams = inject(PlatformParams);
this.platformExceptions = inject(PlatformExceptions);
this.platformRouters = inject(PlatformRouters);
// configure the router module
this.platformRouters.hooks
.on("alterEndpointHandlers", (handlers, operationRoute) => {
handlers = $alter("$alterEndpointHandlers", handlers, [operationRoute]);
handlers.after.push(useResponseHandler(this.flush.bind(this)));
return handlers;
})
.on("alterHandler", (handlerMetadata) => {
const handler = handlerMetadata.isInjectable() ? this.createHandler(handlerMetadata) : handlerMetadata.handler;
handlerMetadata.compiledHandler = handler;
return inject(PlatformAdapter).mapHandler(handler, handlerMetadata);
});
}
createHandler(handlerMetadata) {
const handler = this.platformParams.compileHandler(handlerMetadata);
return ($ctx) => {
$ctx.handlerMetadata = handlerMetadata;
return this.onRequest(handler, $ctx);
};
}
/**
* @param provider
* @param propertyKey
*/
createCustomHandler(provider, propertyKey) {
const metadata = new PlatformHandlerMetadata({
provider,
type: PlatformHandlerType.CUSTOM,
propertyKey
});
return this.createHandler(metadata);
}
/**
* Call handler when a request his handle
*/
async onRequest(handler, $ctx) {
const { handlerMetadata } = $ctx;
if (handlerMetadata.type === PlatformHandlerType.CTX_FN) {
return handler({ $ctx });
}
const resolver = new AnyToPromiseWithCtx($ctx);
const response = await resolver.call(handler);
// Note: restore previous handler metadata (for OIDC)
$ctx.handlerMetadata = handlerMetadata;
if (response.state === AnyToPromiseStatus.RESOLVED && !$ctx.isDone()) {
return this.onResponse(response, $ctx);
}
}
onResponse({ status, data, headers }, $ctx) {
if (status) {
$ctx.response.status(status);
}
if (headers) {
$ctx.response.setHeaders(headers);
}
if (data !== undefined) {
$ctx.data = data;
}
$ctx.error = null;
// set headers each times that an endpoint is called
if ($ctx.handlerMetadata.isEndpoint()) {
setResponseHeaders($ctx);
}
}
/**
* Send the response to the consumer.
* @protected
* @param $ctx
*/
async flush($ctx) {
if (!$ctx.error) {
$ctx.error = await catchAsyncError(async () => {
const { response } = $ctx;
if (!$ctx.isDone()) {
const data = await this.responseFilter.transform($ctx.data, $ctx);
response.body(data);
}
});
}
if ($ctx.error) {
this.platformExceptions.catch($ctx.error, $ctx);
}
}
}
injectable(PlatformHandler).scope(ProviderScope.SINGLETON);