@tsed/common
Version:
A TypeScript Framework on top of Express
292 lines • 9.85 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlatformHandler = void 0;
const tslib_1 = require("tslib");
const core_1 = require("@tsed/core");
const di_1 = require("@tsed/di");
const mvc_1 = require("../../mvc");
const PlatformResponseFilter_1 = require("../../platform-response-filter/services/PlatformResponseFilter");
const HandlerContext_1 = require("../domain/HandlerContext");
const ParamValidationError_1 = require("../errors/ParamValidationError");
const createHandlerMetadata_1 = require("../utils/createHandlerMetadata");
const renderView_1 = require("../utils/renderView");
const setResponseHeaders_1 = require("../utils/setResponseHeaders");
function shouldBeSent(data) {
return Buffer.isBuffer(data) || core_1.isBoolean(data) || core_1.isNumber(data) || core_1.isString(data) || data === null;
}
function shouldBeSerialized(data) {
return !(core_1.isStream(data) || shouldBeSent(data) || data === undefined);
}
/**
* Platform Handler abstraction layer. Wrap original class method to a pure platform handler (Express, Koa, etc...).
* @platform
*/
let PlatformHandler = class PlatformHandler {
constructor(injector) {
this.injector = injector;
}
/**
* Create a native middleware based on the given metadata and return an instance of HandlerContext
* @param input
* @param options
*/
createHandler(input, options = {}) {
const metadata = this.createHandlerMetadata(input, options);
this.sortPipes(metadata);
return this.createRawHandler(metadata);
}
createCustomHandler(provider, propertyKey) {
const metadata = new mvc_1.HandlerMetadata({
token: provider.provide,
target: provider.useClass,
type: mvc_1.HandlerType.CUSTOM,
propertyKey
});
this.sortPipes(metadata);
return this.createRawHandler(metadata);
}
/**
* Create handler metadata
* @param obj
* @param routeOptions
*/
createHandlerMetadata(obj, routeOptions = {}) {
return createHandlerMetadata_1.createHandlerMetadata(this.injector, obj, routeOptions);
}
/**
* Get argument from parameter medata or handler context.
* @param type
* @param h
*/
getArg(type, h) {
const { $ctx, $ctx: { request, response } } = h;
switch (type) {
case mvc_1.ParamTypes.NODE_RESPONSE:
return $ctx.getRes();
case mvc_1.ParamTypes.NODE_REQUEST:
return $ctx.getReq();
case mvc_1.ParamTypes.FILES:
return h.getRequest().files;
case mvc_1.ParamTypes.RESPONSE:
return h.getResponse();
case mvc_1.ParamTypes.REQUEST:
return h.getRequest();
case mvc_1.ParamTypes.PLATFORM_RESPONSE:
return response;
case mvc_1.ParamTypes.PLATFORM_REQUEST:
return request;
case mvc_1.ParamTypes.NEXT_FN:
return h.next;
case mvc_1.ParamTypes.ERR:
return h.err;
case mvc_1.ParamTypes.$CTX: // tsed ctx
return $ctx;
case mvc_1.ParamTypes.ENDPOINT_INFO:
return $ctx.endpoint;
case mvc_1.ParamTypes.RESPONSE_DATA:
return $ctx.data;
case mvc_1.ParamTypes.BODY:
return request.body;
case mvc_1.ParamTypes.QUERY:
return request.query;
case mvc_1.ParamTypes.PATH:
return request.params;
case mvc_1.ParamTypes.HEADER:
return request.headers;
case mvc_1.ParamTypes.COOKIES:
return request.cookies;
case mvc_1.ParamTypes.SESSION:
return request.session;
case mvc_1.ParamTypes.LOCALS:
return response.locals;
default:
return h.request;
}
}
async onCtxRequest(requestOptions) {
const { metadata, $ctx } = requestOptions;
await metadata.handler($ctx);
return this.next(requestOptions);
}
/**
* Call handler when a request his handle
* @param requestOptions
*/
async onRequest(requestOptions) {
const { metadata, $ctx, err } = requestOptions;
try {
const h = new HandlerContext_1.HandlerContext({
$ctx,
metadata,
args: [],
err
});
h.args = await this.getArgs(h);
await h.callHandler();
if (h.status === HandlerContext_1.HandlerContextStatus.RESOLVED) {
// Can be canceled by the handler itself
return await this.onSuccess($ctx.data, requestOptions);
}
}
catch (er) {
return this.onError(er, requestOptions);
}
}
async onError(er, requestOptions) {
const { next, $ctx } = requestOptions;
$ctx.data = er;
if (!next) {
throw er;
}
return !$ctx.response.isHeadersSent() && next && next(er);
}
/**
* Manage success scenario
* @param data
* @param requestOptions
* @protected
*/
async onSuccess(data, requestOptions) {
const { metadata, $ctx, next } = requestOptions;
if ($ctx.request.isAborted() || $ctx.response.isDone()) {
return;
}
// set headers each times that an endpoint is called
if (metadata.type === mvc_1.HandlerType.ENDPOINT) {
this.setHeaders($ctx);
}
// call returned middleware
if (core_1.isFunction(data) && !core_1.isStream(data)) {
return this.callReturnedMiddleware(data, $ctx, next);
}
if (metadata.isFinal()) {
return this.flush(data, $ctx);
}
return this.next(requestOptions);
}
/**
* Call the returned middleware by the handler.
* @param middleware
* @param ctx
* @param next
* @protected
*/
callReturnedMiddleware(middleware, ctx, next) {
return middleware(ctx.getRequest(), ctx.getResponse(), next);
}
/**
* Send the response to the consumer.
* @param data
* @param ctx
* @protected
*/
async flush(data, ctx) {
const { response, endpoint } = ctx;
if (endpoint.view) {
data = await this.render(data, ctx);
}
else if (shouldBeSerialized(data)) {
data = this.injector.get(mvc_1.ConverterService).serialize(data, {
...endpoint.getResponseOptions(),
endpoint: true
});
}
if (!response.isDone()) {
const responseFilter = this.injector.get(PlatformResponseFilter_1.PlatformResponseFilter);
response.body(responseFilter.transform(data, ctx));
}
}
/**
* Render the view if the endpoint has a configured view.
* @param data
* @param ctx
* @protected
*/
async render(data, ctx) {
return renderView_1.renderView(data, ctx);
}
/**
* create Raw handler
* @param metadata
*/
createRawHandler(metadata) {
switch (metadata.type) {
case mvc_1.HandlerType.CUSTOM:
return (ctx) => this.onRequest({ metadata, $ctx: ctx });
case mvc_1.HandlerType.RAW_ERR_FN:
case mvc_1.HandlerType.RAW_FN:
return metadata.handler;
default:
case mvc_1.HandlerType.ENDPOINT:
case mvc_1.HandlerType.MIDDLEWARE:
return (request, response, next) => this.onRequest({ $ctx: request.$ctx, next, metadata });
}
}
/**
* Set response headers
* @param ctx
* @protected
*/
setHeaders(ctx) {
return setResponseHeaders_1.setResponseHeaders(ctx);
}
next(requestOptions) {
const { $ctx, next } = requestOptions;
return !$ctx.response.isDone() && next && next();
}
/**
* Return arguments to call handler
* @param h
*/
async getArgs(h) {
const { metadata: { parameters } } = h;
return Promise.all(parameters.map((param) => this.mapArg(param, h)));
}
/**
* Sort pipes before calling it
* @param metadata
*/
sortPipes(metadata) {
if (!metadata.injectable) {
return;
}
const get = (pipe) => {
return this.injector.getProvider(pipe).priority || 0;
};
metadata.parameters.forEach((param) => {
return (param.pipes = param.pipes.sort((p1, p2) => {
return get(p1) < get(p2) ? -1 : get(p1) > get(p2) ? 1 : 0;
}));
});
}
/**
* Map argument by calling pipe.
* @param metadata
* @param h
*/
async mapArg(metadata, h) {
const { injector } = h;
const value = this.getArg(metadata.paramType, h);
// istanbul ignore next
const handleError = async (cb) => {
try {
return await cb();
}
catch (er) {
throw ParamValidationError_1.ParamValidationError.from(metadata, er);
}
};
return metadata.pipes.reduce(async (value, pipe) => {
value = await value;
return handleError(() => injector.get(pipe).transform(value, metadata));
}, value);
}
};
PlatformHandler = tslib_1.__decorate([
di_1.Injectable({
scope: di_1.ProviderScope.SINGLETON
}),
tslib_1.__metadata("design:paramtypes", [di_1.InjectorService])
], PlatformHandler);
exports.PlatformHandler = PlatformHandler;
//# sourceMappingURL=PlatformHandler.js.map