@zerooneit/expressive-tea
Version:
A REST API over Express and Typescript
288 lines (287 loc) • 12.3 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.View = exports.Middleware = exports.Param = exports.Delete = exports.Patch = exports.Put = exports.Post = exports.Get = exports.Route = void 0;
const express_1 = require("express");
const lodash_1 = require("lodash");
const MetaData_1 = require("../classes/MetaData");
const RequestExceptions_1 = require("../exceptions/RequestExceptions");
const decorators_1 = require("../helpers/decorators");
const server_1 = require("../helpers/server");
const constants_1 = require("../libs/constants");
/**
* @module Decorators/Router
*/
/**
* Route Controller Decorator assign a router point into the module mount-point placeholder and is used just to create
* a bucket to contains all the endpoints defined by method of the Controller. If mount-point is not defined automati-
* cally this must considerate mounted on root, but is mandatory define the Controller with this decorator in order
* to allow Expressive Tea Setting up the Controller as part of a Module.
*
* @decorator {ClassDecorator} Route - Assign a route to controller endpoints.
* @summary Generate a Placeholder endpoint root for controller routes.
* @param {string} mountpoint Register the url part to mount the Controller.
* @example
* {REPLACE-AT}Route('/)
* class Example {}
*/
function Route(mountpoint = '/') {
return (Route) => {
return class ExpressiveTeaRoute extends Route {
constructor(...args) {
super(...args);
const handlers = MetaData_1.default.get(constants_1.ROUTER_HANDLERS_KEY, this) || [];
this.router = (0, express_1.Router)();
this.mountpoint = mountpoint;
(0, lodash_1.each)(handlers, h => {
const middlewares = h.handler.$middlewares || [];
this.router[h.verb](h.route, ...middlewares, this.__registerHandler(h));
});
}
__mount(parent) {
const rootMiddlewares = MetaData_1.default.get(constants_1.ROUTER_MIDDLEWARES_KEY, this) || [];
parent.use(this.mountpoint, ...rootMiddlewares, this.router);
return this;
}
__registerHandler(options) {
const self = this;
const decoratedArguments = MetaData_1.default.get(constants_1.ARGUMENTS_KEY, options.target, options.propertyKey);
const annotations = MetaData_1.default.get(constants_1.ROUTER_ANNOTATIONS_KEY, options.target, options.propertyKey);
return async function exec(request, response, next) {
try {
let isNextUsed = false;
const nextWrapper = () => (error) => {
next(error);
isNextUsed = true;
};
// TODO: Must be Depecrated in prior version.
const result = await options.handler.apply(self, (0, server_1.mapArguments)(decoratedArguments, request, response, nextWrapper()));
if (!response.headersSent && !isNextUsed) {
(0, server_1.autoResponse)(request, response, annotations, result);
}
}
catch (e) {
if (e instanceof RequestExceptions_1.GenericRequestException) {
return next(e);
}
next(new RequestExceptions_1.GenericRequestException(e.message || 'System Error'));
}
};
}
};
};
}
exports.Route = Route;
/**
* Define a GET Endpoint response over the controller router and can define in which route should be responds and
* by default this is responding to everything on the controller root path.
* @decorator {MethodDecorator} Get - Create an GET Response over the designed route.
* @summary Define a GET Controller Endpoint on Controller.
* @param {string} route URL Part to mount create a handler.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}Get() // This Response to all GET Requests for controller route.
* methodError() {}
*
* {REPLACE-AT}Get('/data') // This Response to "/data" GET Requests for controller route.
* methodData() {}
* }
*
*/
function Get(route = '*') {
return (0, server_1.generateRoute)(route, 'get');
}
exports.Get = Get;
/**
* Define a POST Endpoint response over the controller router and can define in which route should be responds and
* by default this is responding to everything on the controller root path.
* @decorator {MethodDecorator} POST - Create an POST Response over the designed route.
* @summary Define a POST Controller Endpoint on Controller.
* @param {string} route URL Part to mount create a handler.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}Post() // This Response to all GET Requests for controller route.
* methodError() {}
*
* {REPLACE-AT}Post('/data') // This Response to "/data" GET Requests for controller route.
* methodData() {}
* }
*
*/
function Post(route = '*') {
return (0, server_1.generateRoute)(route, 'post');
}
exports.Post = Post;
/**
* Define a PUT Endpoint response over the controller router and can define in which route should be responds and
* by default this is responding to everything on the controller root path.
* @decorator {MethodDecorator} Put - Create an PUT Response over the designed route.
* @summary Define a PUT Controller Endpoint on Controller.
* @param {string} route URL Part to mount create a handler.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}Put() // This Response to all PUT Requests for controller route.
* methodError() {}
*
* {REPLACE-AT}Put('/data') // This Response to "/data" PUT Requests for controller route.
* methodData() {}
* }
*
*/
function Put(route = '*') {
return (0, server_1.generateRoute)(route, 'put');
}
exports.Put = Put;
/**
* Define a PATCH Endpoint response over the controller router and can define in which route should be responds and
* by default this is responding to everything on the controller root path.
* @decorator {MethodDecorator} Patch - Create an PATCH Response over the designed route.
* @summary Define a PATCH Controller Endpoint on Controller.
* @param {string} route URL Part to mount create a handler.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}Patch() // This Response to all PATCH Requests for controller route.
* methodError() {}
*
* {REPLACE-AT}Patch('/data') // This Response to "/data" PATCH Requests for controller route.
* methodData() {}
* }
*
*/
function Patch(route = '*') {
return (0, server_1.generateRoute)(route, 'patch');
}
exports.Patch = Patch;
/**
* Define a DELETE Endpoint response over the controller router and can define in which route should be responds and
* by default this is responding to everything on the controller root path.
* @decorator {MethodDecorator} Delete - Create an DELETE Response over the designed route.
* @summary Define a DELETE Controller Endpoint on Controller.
* @param {string} route URL Part to mount create a handler.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}Delete() // This Response to all DELETE Requests for controller route.
* methodError() {}
*
* {REPLACE-AT}Delete('/data') // This Response to "/data" DELETE Requests for controller route.
* methodData() {}
* }
*
*/
function Delete(route = '*') {
return (0, server_1.generateRoute)(route, 'delete');
}
exports.Delete = Delete;
/**
* Define a Route path parameter transformation method as mentioned on Express this is just call by controller endpoints
* route paths and it helps to define logic on each route path parameters. Example, you can require transform userId
* parameter and added to request object.
*
* The method to response this must follow the Express callback notation (Request, Response, Next, paramValue) which
* also must be use Next method to allow continue the process flow from Express.
* @decorator {MethodDecorator} Param - Create a transformation middleware for router parameters.
* @summary Define a Parameter transformation on router path..
* @param {string} route URL Part to mount create a handler.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}Param('userId') // This Response to all GET Requests for controller route.
* async methodError(req, res, next, param, id) {
* const user = await User.find(id)
* try {
* if(!user) throw new Error('User not Found');
*
* req.user = user;
* next();
* } catch(e) {
* next(e);
* }
* }
*
* {REPLACE-AT}Get('/:userId') // This Response to "/:userId" GET Requests for controller route.
* methodData(req, res) {
* res.json(req.user);
* }
* }
*
*/
function Param(route = '*') {
return (0, server_1.generateRoute)(route, 'param');
}
exports.Param = Param;
/**
* Middleware Decorator is following the middleware functionality inheritance from Express framework itself and follow
* the same rules, that execute any code before response the request and can change the current request or response
* object, and requires use next callback to pass control to next middleware.
*
* This define the middlewares at Controller and Controller methods level, for application levels please see the
* Plug Decorator which can be useful to declare at that level..
* @decorator {MethodDecorator|ClassDecorator} Middleware - Attach a middleware definition on the root route or endpoint
* routes.
* @summary Assign a middleware to the Controller or Method route path flow.
* @example
* {REPLACE-AT}Middleware((req, res, next) {
* req.controllerName = 'Example';
* next();
* })
* class ExampleController {
* {REPLACE-AT}Get('/someResponse')
* {REPLACE-AT}Middleware((req, res, next) {
* req.methodName = 'someResponse';
* next();
* })
* someRespone(req, res) {
* res.send(`${req.controllerName}:${req.methodName}`);
* }
* }
*
* @param {Function} middleware Register a middleware over router.
*/
function Middleware(middleware) {
return (target, property, descriptor) => {
if (!property) {
rootMiddleware(target, middleware);
}
else {
routeMiddleware(target, descriptor, middleware);
}
};
}
exports.Middleware = Middleware;
/**
* Will generate a GET route to respond rendering a view template setting up before; the controller method <b>MUST</b>
* return an object in order to fulfilled the local variables into the view.
* @decorator {MethodDecorator} View - Create a Vew Response over the designed route.
* @summary Define a View response endpoint on Controller.
* @param {string} viewName - View render layout name defined by configuration.
* @param {string} route - URL Part to mount create a handler.
* @return {object} Controller must return and object as local variables for the view.
* @example
* {REPLACE-AT}Route('/')
* class GenericController {
* {REPLACE-AT}View('login', '/view') // This Response to all GET Requests for controller route.
* methodError() {}
* }
*
*/
function View(viewName, route) {
route = route || `/${viewName}`;
return (target, propertyKey, descriptor) => {
(0, decorators_1.addAnnotation)('view', target, propertyKey, viewName);
(0, server_1.router)('get', route, target, descriptor.value, propertyKey);
};
}
exports.View = View;
function rootMiddleware(target, middleware) {
const existedRoutesHandlers = MetaData_1.default.get(constants_1.ROUTER_MIDDLEWARES_KEY, target) || [];
existedRoutesHandlers.unshift(middleware);
MetaData_1.default.set(constants_1.ROUTER_MIDDLEWARES_KEY, existedRoutesHandlers, target);
}
function routeMiddleware(target, descriptor, middleware) {
descriptor.value.$middlewares = descriptor.value.$middlewares || [];
descriptor.value.$middlewares.unshift(middleware);
}
;