UNPKG

9.05 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const cors_1 = tslib_1.__importDefault(require("@koa/cors"));
5const core_1 = require("@plumjs/core");
6const validator_1 = require("@plumjs/validator");
7const debug_1 = tslib_1.__importDefault(require("debug"));
8const fs_1 = require("fs");
9const koa_1 = tslib_1.__importDefault(require("koa"));
10const koa_bodyparser_1 = tslib_1.__importDefault(require("koa-bodyparser"));
11const path_1 = require("path");
12const binder_1 = require("./binder");
13const router_1 = require("./router");
14const log = debug_1.default("plum:app");
15/* ------------------------------------------------------------------------------- */
16/* ----------------------------------- HELPERS ----------------------------------- */
17/* ------------------------------------------------------------------------------- */
18function extractDecorators(route) {
19 const classDecorator = route.controller.decorators.filter(x => x.name == "Middleware");
20 const methodDecorator = route.action.decorators.filter(x => x.name == "Middleware");
21 const extract = (d) => d.map(x => x.value).flatten();
22 return extract(classDecorator)
23 .concat(extract(methodDecorator))
24 .reverse();
25}
26exports.extractDecorators = extractDecorators;
27/* ------------------------------------------------------------------------------- */
28/* ------------------------------- INVOCATIONS ----------------------------------- */
29/* ------------------------------------------------------------------------------- */
30class MiddlewareInvocation {
31 constructor(middleware, context, next) {
32 this.middleware = middleware;
33 this.context = context;
34 this.next = next;
35 }
36 proceed() {
37 return this.middleware.execute(this.next);
38 }
39}
40exports.MiddlewareInvocation = MiddlewareInvocation;
41class ActionInvocation {
42 constructor(context) {
43 this.context = context;
44 }
45 async proceed() {
46 const { request, route, config } = this.context;
47 const controller = config.dependencyResolver.resolve(route.controller.object);
48 //bind parameters
49 const parameters = binder_1.bindParameter(request, route.action, config.converters);
50 //check validation
51 if (config.validator) {
52 const param = (i) => route.action.parameters[i];
53 const validate = (value, i) => config.validator(value, param(i));
54 const result = await Promise.all(parameters.map((value, index) => validate(value, index)));
55 const issues = result.flatten();
56 log(`[Action Invocation] Validation result ${core_1.b(issues)}`);
57 if (issues.length > 0)
58 throw new core_1.ValidationError(issues);
59 }
60 const result = controller[route.action.name].apply(controller, parameters);
61 const awaitedResult = await Promise.resolve(result);
62 const status = config.responseStatus && config.responseStatus[route.method] || 200;
63 if (awaitedResult instanceof core_1.ActionResult) {
64 awaitedResult.status = awaitedResult.status || status;
65 log(`[Action Invocation] ActionResult value - Method: ${core_1.b(route.method)} Status config: ${core_1.b(config.responseStatus)} Status: ${core_1.b(result.status)} `);
66 return awaitedResult;
67 }
68 else {
69 log(`[Action Invocation] Raw value - Method: ${route.method} Status config: ${core_1.b(config.responseStatus)} Status: ${core_1.b(status)} `);
70 return new core_1.ActionResult(awaitedResult, status);
71 }
72 }
73}
74exports.ActionInvocation = ActionInvocation;
75function pipe(middleware, context, invocation) {
76 return middleware.reverse().reduce((prev, cur) => new MiddlewareInvocation(cur, context, prev), invocation);
77}
78exports.pipe = pipe;
79/* ------------------------------------------------------------------------------- */
80/* -------------------------------- FACITLITIES ---------------------------------- */
81/* ------------------------------------------------------------------------------- */
82class ValidationMiddleware {
83 async execute(invocation) {
84 try {
85 return await invocation.proceed();
86 }
87 catch (e) {
88 if (e instanceof core_1.ValidationError) {
89 return new core_1.ActionResult(e.issues, e.status);
90 }
91 else
92 throw e;
93 }
94 }
95}
96/**
97 * Preset configuration for building web api. This facility contains:
98 *
99 * body parser: koa-bodyparser
100 *
101 * cors: @koa/cors
102 */
103class WebApiFacility {
104 constructor(opt) {
105 this.opt = opt;
106 }
107 async setup(app) {
108 app.koa.use(koa_bodyparser_1.default(this.opt && this.opt.bodyParser));
109 app.koa.use(cors_1.default(this.opt && this.opt.cors));
110 if (this.opt && this.opt.controller)
111 app.set({ controller: this.opt.controller });
112 app.set({
113 validator: (value, meta) => validator_1.validate(value, meta.decorators, [meta.name])
114 });
115 app.use(new ValidationMiddleware());
116 }
117}
118exports.WebApiFacility = WebApiFacility;
119/**
120 * Preset configuration for building restful style api. This facility contains:
121 *
122 * body parser: koa-bodyparser
123 *
124 * cors: @koa/cors
125 *
126 * default response status: { get: 200, post: 201, put: 204, delete: 204 }
127 */
128class RestfulApiFacility extends WebApiFacility {
129 async setup(app) {
130 super.setup(app);
131 app.set({ responseStatus: { post: 201, put: 204, delete: 204 } });
132 }
133}
134exports.RestfulApiFacility = RestfulApiFacility;
135/* ------------------------------------------------------------------------------- */
136/* --------------------------- MAIN APPLICATION ---------------------------------- */
137/* ------------------------------------------------------------------------------- */
138async function requestHandler(ctx) {
139 const controllerMiddleware = extractDecorators(ctx.route);
140 const pipeline = pipe(controllerMiddleware, ctx, new ActionInvocation(ctx));
141 const result = await pipeline.proceed();
142 result.execute(ctx);
143 log(`[Request Handler] ${core_1.b(ctx.path)} -> ${core_1.b(ctx.route.controller.name)}.${core_1.b(ctx.route.action.name)}`);
144 log(`[Request Handler] Request Query: ${core_1.b(ctx.query)}`);
145 log(`[Request Handler] Request Header: ${core_1.b(ctx.headers)}`);
146 log(`[Request Handler] Request Body: ${core_1.b(result.body)}`);
147}
148class Plumier {
149 constructor() {
150 this.koa = new koa_1.default();
151 this.config = Object.assign({}, core_1.DefaultConfiguration, { middleware: [], facilities: [] });
152 }
153 use(option) {
154 if (typeof option === "function") {
155 this.koa.use(option);
156 }
157 else {
158 this.koa.use(core_1.MiddlewareUtil.toKoa(option));
159 }
160 return this;
161 }
162 set(config) {
163 if (core_1.hasKeyOf(config, "setup"))
164 this.config.facilities.push(config);
165 else
166 Object.assign(this.config, config);
167 return this;
168 }
169 async initialize() {
170 try {
171 let routes = [];
172 await Promise.all(this.config.facilities.map(x => x.setup(this)));
173 const executionPath = path_1.dirname(module.parent.parent.filename);
174 log(`[Initialize] execution path ${executionPath}`);
175 if (typeof this.config.controller === "string") {
176 const path = path_1.isAbsolute(this.config.controller) ? this.config.controller :
177 path_1.join(executionPath, this.config.controller);
178 if (!fs_1.existsSync(path))
179 throw new Error(core_1.errorMessage.ControllerPathNotFound.format(path));
180 routes = router_1.transformModule(path, [this.config.fileExtension]);
181 }
182 else if (Array.isArray(this.config.controller)) {
183 log(`[Initialize] Controller ${core_1.b(this.config.controller)}`);
184 routes = this.config.controller.map(x => router_1.transformController(x))
185 .flatten();
186 }
187 else {
188 log(`[Initialize] Controller ${core_1.b(this.config.controller)}`);
189 routes = router_1.transformController(this.config.controller);
190 }
191 log(`[Initialize] Routes ${core_1.b(routes)}`);
192 if (this.config.mode === "debug")
193 router_1.printAnalysis(router_1.analyzeRoutes(routes));
194 this.koa.use(async (ctx, next) => {
195 try {
196 await next();
197 }
198 catch (e) {
199 if (e instanceof core_1.HttpStatusError)
200 ctx.throw(e.status, e);
201 else
202 ctx.throw(500, e);
203 }
204 });
205 this.koa.use(router_1.router(routes, this.config, requestHandler));
206 return this.koa;
207 }
208 catch (e) {
209 throw e;
210 }
211 }
212}
213exports.Plumier = Plumier;