UNPKG

lux-framework

Version:

Build scalable, Node.js-powered REST APIs with almost no code.

178 lines (140 loc) 3.96 kB
// @flow import { FreezeableSet, freezeProps, deepFreezeProps } from '../../freezeable'; import type Controller from '../../controller'; import type { Request, Response, Request$method } from '../../server'; import { FINAL_HANDLER, createAction } from './action'; import { paramsFor, defaultParamsFor, validateResourceId } from './params'; import getStaticPath from './utils/get-static-path'; import getDynamicSegments from './utils/get-dynamic-segments'; import type { Action } from './action'; // eslint-disable-line max-len, no-duplicate-imports import type { ParameterGroup } from './params'; // eslint-disable-line max-len, no-duplicate-imports import type { Route$opts } from './interfaces'; /** * @private */ class Route extends FreezeableSet<Action<any>> { type: string; path: string; action: string; params: ParameterGroup; method: Request$method; controller: Controller; staticPath: string; defaultParams: Object; dynamicSegments: Array<string>; constructor({ type, path, action, method, controller }: Route$opts) { const dynamicSegments = getDynamicSegments(path); if (action && controller) { const handler = Reflect.get(controller, action); if (typeof handler === 'function') { const params = paramsFor({ type, method, controller, dynamicSegments }); const staticPath = getStaticPath(path, dynamicSegments); const defaultParams = defaultParamsFor({ type, controller }); super(createAction(type, handler, controller)); Object.assign(this, { type, path, params, action, method, controller, staticPath, defaultParams, dynamicSegments }); freezeProps(this, true, 'type', 'path' ); freezeProps(this, false, 'action', 'params', 'method', 'controller', 'staticPath' ); deepFreezeProps(this, false, 'defaultParams', 'dynamicSegments' ); } else { const { constructor: { name: controllerName } } = controller; throw new TypeError( `Handler for ${controllerName}#${action} is not a function.` ); } } else { throw new TypeError( 'Arguments `controller` and `action` must not be undefined' ); } this.freeze(); } parseParams(params: Array<string>): Object { return params.reduce((result, value, idx) => { const key = this.dynamicSegments[idx]; if (key) { return { ...result, [key]: Number.parseInt(value, 10) }; } return result; }, {}); } async execHandlers(req: Request, res: Response): Promise<any> { let calledFinal = false; let data; for (const handler of this) { // eslint-disable-next-line no-await-in-loop data = await handler(req, res, data); if (handler.name === FINAL_HANDLER) { calledFinal = true; } if (!calledFinal && typeof data !== 'undefined') { break; } } return data; } async visit(req: Request, res: Response): Promise<any> { const { defaultParams } = this; let params = { ...req.params, ...this.parseParams(req.url.params) }; if (req.method !== 'OPTIONS') { params = this.params.validate(params); } Object.assign(req, { params, defaultParams }); if (this.type === 'member' && req.method === 'PATCH') { validateResourceId(req); } return this.execHandlers(req, res); } } export default Route; export { DYNAMIC_PATTERN } from './constants'; export type { Action } from './action'; export type { Route$opts, Route$type } from './interfaces';