UNPKG

express-decorated-router

Version:

Define Express routes using TypeScript decorators

268 lines (267 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const debug = require("debug"); const express_1 = require("express"); const forEach = require("lodash/forEach"); const isEmpty = require("lodash/isEmpty"); const ParentControllerError_1 = require("./errors/ParentControllerError"); const UnregisteredControllerError_1 = require("./errors/UnregisteredControllerError"); /** key = controller class */ const routeMap = new Map(); /** key = controller class */ const controllerMap = new Map(); /** key = controller class */ const controllerMiddlewareMap = new Map(); /** key = class method */ const routeMiddlewareMap = new Map(); /** key = controller class */ const routerMap = new Map(); /** key = child, value = parent */ const parentMap = new Map(); /** Debug logger */ const log = debug('express-decorated-router'); /** Public interface for the express-decorated-router library */ class ExpressDecoratedRouter { /** * Register a {@link Controller @Controller} decoration * @internal * @hidden * @param clazz Controller class * @param root Controller root path * @param opts Options passed to {@link Router express.Router()} */ static addController(clazz, root, opts) { controllerMap.set(clazz, { root, opts }); log('Decorating class %s as a controller with root %s and options %o', clazz.name, root, opts); } /** * Register a {@link ControllerMiddleware @ControllerMiddleware} decoration * @internal * @hidden * @param clazz Controller class * @param middleware Middleware handlers */ static addControllerMiddleware(clazz, middleware) { controllerMiddlewareMap.set(clazz, middleware); log('Adding %d middleware functions to controller %s', middleware.length, clazz.name); } /** * Register a {@link Parent @Parent} decoration * @internal * @hidden * @param child Child controller * @param parent Parent controller */ static addParent(child, parent) { log('Setting %s as the parent controller of %s', parent.name, child.name); parentMap.set(child, parent); } /** * Register a route decoration * @internal * @hidden * @param clazz Controller class * @param httpMethod The HTTP method * @param path The URL path * @param handler The request handler */ static addRoute(clazz, httpMethod, path, handler) { log('Adding %s %s route to controller %s', httpMethod.toUpperCase(), path, clazz.name); let routeSpec = routeMap.get(clazz); /* istanbul ignore else */ if (!routeSpec) { log('Route spec object does not exist - creating'); routeSpec = {}; routeMap.set(clazz, routeSpec); } else { log('Route spec object already exists'); } let httpMethodSpec = routeSpec[httpMethod]; /* istanbul ignore else */ if (!httpMethodSpec) { log('Http spec map does not exist - creating'); httpMethodSpec = new Map(); routeSpec[httpMethod] = httpMethodSpec; } else { log('Http spec map already exists'); } httpMethodSpec.set(path, handler); } /** * Register a {@link RouteMiddleware @RouteMiddleware} decoration * @internal * @hidden * @param route The decorated method * @param middleware The middleware functions */ static addRouteMiddleware(route, middleware) { routeMiddlewareMap.set(route, middleware); log('Adding %s middleware functions to handler %s', middleware.length, route.name); } /** * Apply routes to the Express application. You should call reset() after calling this. * @param app The Express application * @throws {ParentControllerError} If the input of a @Parent decoration has not been decorated with @Controller * @throws {UnregisteredControllerError} If a class decorated with @Parent was not annotated with @Controller */ static applyRoutes(app) { log('Applying routes to Express app'); for (const controllerMapEntry of controllerMap.entries()) { ExpressDecoratedRouter.processController(app, controllerMapEntry[0], controllerMapEntry[1]); } for (const parentMapEntry of parentMap.entries()) { ExpressDecoratedRouter.processParents(parentMapEntry[0], parentMapEntry[1]); } return ExpressDecoratedRouter; } /** * Reset the library, freeing resources. You should call this method after calling applyRoutes() */ static reset() { log('Resetting route map'); routeMap.clear(); log('Resetting controller map'); controllerMap.clear(); log('Resetting controller middleware map'); controllerMiddlewareMap.clear(); log('Resetting route middleware map'); routeMiddlewareMap.clear(); log('Resetting router map'); routerMap.clear(); log('Resetting parent map'); parentMap.clear(); return ExpressDecoratedRouter; } /** * Process a Controller decoration * @internal * @hidden * @param app The Express app * @param controller The controller class * @param controllerSpec The controller specification */ static processController(app, controller, controllerSpec) { log('Resolved controller as %s, controller spec as %o', controller.name, controllerSpec); if (!routeMap.has(controller)) { log('Controller %s has no routes - skipping', controller.name); return; } const routeSpec = routeMap.get(controller); if (isEmpty(routeSpec)) { log('Controller %s has an empty route spec - skipping', controller.name); return; } const router = express_1.Router(controllerSpec.opts); ExpressDecoratedRouter.processControllerMiddleware(router, controller); log('Parsing route specs for controller %s', controller.name); forEach(routeSpec, (httpMethodSpec, httpMethod) => { ExpressDecoratedRouter.processRouteSpec(router, controller, httpMethod, httpMethodSpec); }); log('Adding controller %s with root %s and options %o to app', controller.name, controllerSpec.root, controllerSpec.opts); routerMap.set(controller, router); if (!parentMap.has(controller)) { app.use(controllerSpec.root, router); } } /** * Process a ControllerMiddleware decoration * @internal * @hidden * @param router The Express router this will get applied to * @param controller The controller class */ static processControllerMiddleware(router, controller) { if (controllerMiddlewareMap.has(controller)) { const controllerMiddleware = controllerMiddlewareMap.get(controller); log('Controller %s has %d middleware functions assigned', controller.name, controllerMiddleware.length); router.use(controllerMiddleware); } else { log('Controller %s has no middleware functions assigned', controller.name); } } /** * Process a HTTP method spec * @internal * @hidden * @param router The Express router this will get applied to * @param pathParams The URL * @param requestHandler The request handler * @param httpMethod The HTTP method used */ static processHttpMethodSpec(router, pathParams, requestHandler, httpMethod) { log('Method %s resolved to path %s', requestHandler.name, pathParams); ExpressDecoratedRouter.processRouteMiddleware(router, pathParams, routeMiddlewareMap.get(requestHandler)); router[httpMethod](pathParams, requestHandler); } /** * Process a Parent decoration * @internal * @hidden * @param child Child controller * @param parent Parent controller * @throws {ParentControllerError} If the parent controller has not been registered * @throws {UnregisteredControllerError} If the child controller hasn't been registered */ static processParents(child, parent) { log('Processing parent %s of child %s', parent.name, child.name); const parentRouter = routerMap.get(parent); if (parentRouter) { log('Parent router found'); const childRouter = routerMap.get(child); if (childRouter) { log('Child router found'); const childSpec = controllerMap.get(child); const parentMiddleware = controllerMiddlewareMap.get(parent); if (parentMiddleware && parentMiddleware.length) { log('Parent router %s has %d middleware applied. Transferring to %s', parent.name, parentMiddleware.length, child.name); childRouter.use(parentMiddleware); } parentRouter.use(childSpec.root, childRouter); } else { throw new UnregisteredControllerError_1.UnregisteredControllerError(child); } } else { throw new ParentControllerError_1.ParentControllerError(child, parent); } } /** * Process a @RouteMiddleware decoration * @internal * @hidden * @param router The Express router this will get applied to * @param pathParams The URL * @param routeMiddleware The middleware to apply */ static processRouteMiddleware(router, pathParams, routeMiddleware) { if (routeMiddleware && routeMiddleware.length) { log('And has %d middleware functions', routeMiddleware.length); router.use(pathParams, routeMiddleware); } else { log('And has no middleware functions'); } } /** * Process a route specification * @internal * @hidden * @param router The Express router this will get applied to * @param controller The controller class * @param httpMethod The HTTP method used * @param httpMethodSpec The HTTP method specification */ static processRouteSpec(router, controller, httpMethod, httpMethodSpec) { log('Parsing %s routes for controller %s', httpMethod.toUpperCase(), controller.name); for (const httpMethodSpecEntry of httpMethodSpec.entries()) { const pathParams = httpMethodSpecEntry[0]; const requestHandler = httpMethodSpecEntry[1]; ExpressDecoratedRouter.processHttpMethodSpec(router, pathParams, requestHandler, httpMethod); } } } exports.ExpressDecoratedRouter = ExpressDecoratedRouter;