1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const cors_1 = tslib_1.__importDefault(require("@koa/cors"));
|
5 | const core_1 = require("@plumjs/core");
|
6 | const validator_1 = require("@plumjs/validator");
|
7 | const debug_1 = tslib_1.__importDefault(require("debug"));
|
8 | const fs_1 = require("fs");
|
9 | const koa_1 = tslib_1.__importDefault(require("koa"));
|
10 | const koa_bodyparser_1 = tslib_1.__importDefault(require("koa-bodyparser"));
|
11 | const path_1 = require("path");
|
12 | const binder_1 = require("./binder");
|
13 | const router_1 = require("./router");
|
14 | const log = debug_1.default("plum:app");
|
15 |
|
16 |
|
17 |
|
18 | function 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 | }
|
26 | exports.extractDecorators = extractDecorators;
|
27 |
|
28 |
|
29 |
|
30 | class 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 | }
|
40 | exports.MiddlewareInvocation = MiddlewareInvocation;
|
41 | class 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 |
|
49 | const parameters = binder_1.bindParameter(request, route.action, config.converters);
|
50 |
|
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 | }
|
74 | exports.ActionInvocation = ActionInvocation;
|
75 | function pipe(middleware, context, invocation) {
|
76 | return middleware.reverse().reduce((prev, cur) => new MiddlewareInvocation(cur, context, prev), invocation);
|
77 | }
|
78 | exports.pipe = pipe;
|
79 |
|
80 |
|
81 |
|
82 | class 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 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 | class 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 | }
|
118 | exports.WebApiFacility = WebApiFacility;
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 | class RestfulApiFacility extends WebApiFacility {
|
129 | async setup(app) {
|
130 | super.setup(app);
|
131 | app.set({ responseStatus: { post: 201, put: 204, delete: 204 } });
|
132 | }
|
133 | }
|
134 | exports.RestfulApiFacility = RestfulApiFacility;
|
135 |
|
136 |
|
137 |
|
138 | async 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 | }
|
148 | class 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 | }
|
213 | exports.Plumier = Plumier;
|