UNPKG

14.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const tslib_1 = require("tslib");
4const tinspector_1 = tslib_1.__importStar(require("tinspector"));
5const common_1 = require("./common");
6/* ------------------------------------------------------------------------------- */
7/* -------------------------------- HELPERS -------------------------------------- */
8/* ------------------------------------------------------------------------------- */
9var MiddlewareUtil;
10(function (MiddlewareUtil) {
11 function fromKoa(middleware) {
12 return {
13 execute: async (x) => {
14 await middleware(x.context, async () => {
15 const nextResult = await x.proceed();
16 await nextResult.execute(x.context);
17 });
18 return ActionResult.fromContext(x.context);
19 }
20 };
21 }
22 MiddlewareUtil.fromKoa = fromKoa;
23})(MiddlewareUtil = exports.MiddlewareUtil || (exports.MiddlewareUtil = {}));
24/* ------------------------------------------------------------------------------- */
25/* -------------------------------- CLASSES -------------------------------------- */
26/* ------------------------------------------------------------------------------- */
27class ActionResult {
28 constructor(body, status) {
29 this.body = body;
30 this.status = status;
31 this.headers = {};
32 }
33 static fromContext(ctx) {
34 return new ActionResult(ctx.body, ctx.status);
35 }
36 setHeader(key, value) {
37 this.headers[key] = value;
38 return this;
39 }
40 setStatus(status) {
41 this.status = status;
42 return this;
43 }
44 async execute(ctx) {
45 Object.keys(this.headers).forEach(x => {
46 ctx.set(x, this.headers[x]);
47 });
48 if (this.body)
49 ctx.body = this.body;
50 if (this.status)
51 ctx.status = this.status;
52 }
53}
54exports.ActionResult = ActionResult;
55class HttpStatusError extends Error {
56 constructor(status, message) {
57 super(message);
58 this.status = status;
59 Object.setPrototypeOf(this, HttpStatusError.prototype);
60 }
61}
62exports.HttpStatusError = HttpStatusError;
63class ConversionError extends HttpStatusError {
64 constructor(issues) {
65 super(400);
66 this.issues = issues;
67 Object.setPrototypeOf(this, ConversionError.prototype);
68 }
69}
70exports.ConversionError = ConversionError;
71class ValidationError extends HttpStatusError {
72 constructor(issues) {
73 super(422);
74 this.issues = issues;
75 Object.setPrototypeOf(this, ValidationError.prototype);
76 }
77}
78exports.ValidationError = ValidationError;
79class DefaultDependencyResolver {
80 resolve(type) {
81 return new type();
82 }
83}
84exports.DefaultDependencyResolver = DefaultDependencyResolver;
85/* ------------------------------------------------------------------------------- */
86/* ----------------------------- DECORATORS -------------------------------------- */
87/* ------------------------------------------------------------------------------- */
88var bind;
89(function (bind) {
90 /**
91 * Bind Koa Context
92 *
93 * method(@bind.ctx() ctx:any) {}
94 *
95 * Use dot separated string to access child property
96 *
97 * method(@bind.ctx("state.user") ctx:User) {}
98 * method(@bind.ctx("request.headers.ip") ip:string) {}
99 * method(@bind.ctx("body[0].id") id:string) {}
100 *
101 * @param part part of context, use dot separator to access child property
102 */
103 function ctx(part) {
104 return tinspector_1.decorateParameter({
105 type: "ParameterBinding",
106 process: ctx => part ? common_1.getChildValue(ctx, part) : ctx
107 });
108 }
109 bind.ctx = ctx;
110 /**
111 * Bind Koa request to parameter
112 *
113 * method(@bind.request() req:Request){}
114 *
115 * If parameter provided, part of request property will be bound
116 *
117 * method(@bind.request("method") httpMethod:string){}
118 * method(@bind.request("status") status:number){}
119 *
120 * @param part part of request ex: body, method, query etc
121 */
122 function request(part) {
123 return ctx(["request", part].join("."));
124 }
125 bind.request = request;
126 /**
127 * Bind request body to parameter
128 *
129 * method(@bind.body() body:AnimalDto){}
130 *
131 * If parameter provided, part of body property will be bound
132 *
133 * method(@bind.body("name") name:string){}
134 * method(@bind.body("age") age:number){}
135 */
136 function body(part) {
137 return ctx(["request", "body", part].join("."));
138 }
139 bind.body = body;
140 /**
141 * Bind request header to parameter
142 *
143 * method(@bind.header() header:any){}
144 *
145 * If parameter provided, part of header property will be bound
146 *
147 * method(@bind.header("accept") accept:string){}
148 * method(@bind.header("cookie") age:any){}
149 */
150 function header(key) {
151 return ctx(["request", "headers", key].join("."));
152 }
153 bind.header = header;
154 /**
155 * Bind request query object to parameter
156 *
157 * method(@bind.query() query:any){}
158 *
159 * If parameter provided, part of query property will be bound
160 *
161 * method(@bind.query("id") id:string){}
162 * method(@bind.query("type") type:string){}
163 */
164 function query(name) {
165 return ctx(["request", "query", name].join("."));
166 }
167 bind.query = query;
168 /**
169 * Bind current login user to parameter
170 *
171 * method(@bind.user() user:User){}
172 */
173 function user() {
174 return ctx("state.user");
175 }
176 bind.user = user;
177 /**
178 * Bind file parser for multi part file upload. This function required `FileUploadFacility`
179 ```
180 @route.post()
181 async method(@bind.file() file:FileParser){
182 const info = await file.parse()
183 }
184 ```
185 */
186 function file() {
187 return tinspector_1.decorateParameter({
188 type: "ParameterBinding",
189 process: ctx => {
190 if (!ctx.config.fileParser)
191 throw new Error("No file parser found in configuration");
192 return ctx.config.fileParser(ctx);
193 }
194 });
195 }
196 bind.file = file;
197 /**
198 * Bind custom part of Koa context into parameter
199 * example:
200 *
201 * method(@bind.custom(ctx => ctx.request.body) user:User){}
202 * @param process callback function to process the Koa context
203 */
204 function custom(process) {
205 return tinspector_1.decorateParameter({ type: "ParameterBinding", process });
206 }
207 bind.custom = custom;
208})(bind = exports.bind || (exports.bind = {}));
209class RouteDecoratorImpl {
210 decorateRoute(method, url) { return tinspector_1.decorateMethod({ name: "Route", method, url }); }
211 /**
212 * Mark method as POST method http handler
213 ```
214 class AnimalController{
215 @route.post()
216 method(id:number){}
217 }
218 //result: POST /animal/method?id=<number>
219 ```
220 * Override method name with absolute url
221 ```
222 class AnimalController{
223 @route.post("/beast/:id")
224 method(id:number){}
225 }
226 //result: POST /beast/:id
227 ```
228 * Override method name with relative url
229 ```
230 class AnimalController{
231 @route.post("get")
232 method(id:number){}
233 }
234 //result: POST /animal/get?id=<number>
235 ```
236 * @param url url override
237 */
238 post(url) { return this.decorateRoute("post", url); }
239 /**
240 * Mark method as GET method http handler
241 ```
242 class AnimalController{
243 @route.get()
244 method(id:number){}
245 }
246 //result: GET /animal/method?id=<number>
247 ```
248 * Override method name with absolute url
249 ```
250 class AnimalController{
251 @route.get("/beast/:id")
252 method(id:number){}
253 }
254 //result: GET /beast/:id
255 ```
256 * Override method name with relative url
257 ```
258 class AnimalController{
259 @route.get("get")
260 method(id:number){}
261 }
262 //result: GET /animal/get?id=<number>
263 ```
264 * @param url url override
265 */
266 get(url) { return this.decorateRoute("get", url); }
267 /**
268 * Mark method as PUT method http handler
269 ```
270 class AnimalController{
271 @route.put()
272 method(id:number){}
273 }
274 //result: PUT /animal/method?id=<number>
275 ```
276 * Override method name with absolute url
277 ```
278 class AnimalController{
279 @route.put("/beast/:id")
280 method(id:number){}
281 }
282 //result: PUT /beast/:id
283 ```
284 * Override method name with relative url
285 ```
286 class AnimalController{
287 @route.put("get")
288 method(id:number){}
289 }
290 //result: PUT /animal/get?id=<number>
291 ```
292 * @param url url override
293 */
294 put(url) { return this.decorateRoute("put", url); }
295 /**
296 * Mark method as DELETE method http handler
297 ```
298 class AnimalController{
299 @route.delete()
300 method(id:number){}
301 }
302 //result: DELETE /animal/method?id=<number>
303 ```
304 * Override method name with absolute url
305 ```
306 class AnimalController{
307 @route.delete("/beast/:id")
308 method(id:number){}
309 }
310 //result: DELETE /beast/:id
311 ```
312 * Override method name with relative url
313 ```
314 class AnimalController{
315 @route.delete("get")
316 method(id:number){}
317 }
318 //result: DELETE /animal/get?id=<number>
319 ```
320 * @param url url override
321 */
322 delete(url) { return this.decorateRoute("delete", url); }
323 /**
324 * Override controller name on route generation
325 ```
326 @route.root("/beast")
327 class AnimalController{
328 @route.get()
329 method(id:number){}
330 }
331 //result: GET /beast/method?id=<number>
332 ```
333 * Parameterized root, useful for nested Restful resource
334 ```
335 @route.root("/beast/:type/bunny")
336 class AnimalController{
337 @route.get(":id")
338 method(type:string, id:number){}
339 }
340 //result: GET /beast/:type/bunny/:id
341 ```
342 * @param url url override
343 */
344 root(url) { return tinspector_1.decorateClass({ name: "Root", url }); }
345 /**
346 * Ignore method from route generation
347 ```
348 class AnimalController{
349 @route.get()
350 method(id:number){}
351 @route.ignore()
352 otherMethod(type:string, id:number){}
353 }
354 //result: GET /animal/method?id=<number>
355 //otherMethod not generated
356 ```
357 */
358 ignore() { return tinspector_1.decorateMethod({ name: "Ignore" }); }
359}
360exports.RouteDecoratorImpl = RouteDecoratorImpl;
361exports.route = new RouteDecoratorImpl();
362var middleware;
363(function (middleware_1) {
364 function use(...middleware) {
365 const mdw = middleware.map(x => typeof x == "function" ? MiddlewareUtil.fromKoa(x) : x).reverse();
366 const value = { name: "Middleware", value: mdw };
367 return tinspector_1.decorate(value, ["Class", "Method"]);
368 }
369 middleware_1.use = use;
370})(middleware = exports.middleware || (exports.middleware = {}));
371function domain() { return tinspector_1.default.parameterProperties(); }
372exports.domain = domain;
373class AuthDecoratorImpl {
374 /**
375 * Authorize controller/action to public
376 */
377 public() {
378 return tinspector_1.decorate((...args) => {
379 if (args.length === 3 && typeof args[2] === "number")
380 throw new Error(errorMessage.PublicNotInParameter);
381 return { type: "authorize:public", value: [] };
382 }, ["Class", "Parameter", "Method"]);
383 }
384 /**
385 * Authorize controller/action accessible by sepecific role
386 * @param roles List of roles allowed
387 */
388 role(...roles) {
389 return tinspector_1.mergeDecorator(tinspector_1.decorate({ type: "authorize:role", value: roles }, ["Class", "Parameter", "Method"]), (...args) => {
390 if (args.length === 3 && typeof args[2] === "number")
391 tinspector_1.decorateParameter({ type: "ValidatorDecorator", validator: "optional" })(args[0], args[1], args[2]);
392 });
393 }
394}
395exports.AuthDecoratorImpl = AuthDecoratorImpl;
396exports.authorize = new AuthDecoratorImpl();
397/* ------------------------------------------------------------------------------- */
398/* -------------------------------- CONSTANTS ------------------------------------ */
399/* ------------------------------------------------------------------------------- */
400exports.DefaultConfiguration = {
401 mode: "debug",
402 controller: "./controller",
403 dependencyResolver: new DefaultDependencyResolver()
404};
405var errorMessage;
406(function (errorMessage) {
407 //PLUM1XXX User configuration error
408 errorMessage.RouteDoesNotHaveBackingParam = "PLUM1000: Route parameters ({0}) doesn't have appropriate backing parameter";
409 errorMessage.ActionDoesNotHaveTypeInfo = "PLUM1001: Parameter binding skipped because action doesn't have @route decorator";
410 errorMessage.DuplicateRouteFound = "PLUM1003: Duplicate route found in {0}";
411 errorMessage.ControllerPathNotFound = "PLUM1004: Controller file or directory {0} not found";
412 errorMessage.ModelWithoutTypeInformation = "PLUM1005: Parameter binding skipped because {0} doesn't have @domain() decorator";
413 errorMessage.ArrayWithoutTypeInformation = "PLUM1006: Parameter binding skipped because array field without @array() decorator found in ({0})";
414 errorMessage.ModelNotFound = "PLUM1007: Domain model not found, no class decorated with @domain() on provided classes";
415 errorMessage.ModelPathNotFound = "PLUM1007: Domain model not found, no class decorated with @domain() on path {0}";
416 errorMessage.PublicNotInParameter = "PLUM1008: @authorize.public() can not be applied to parameter";
417 //PLUM2XXX internal app error
418 errorMessage.UnableToInstantiateModel = `PLUM2000: Unable to instantiate model {0}. Domain model should be instantiable using default constructor`;
419 //End user error (no error code)
420 errorMessage.UnableToConvertValue = `Unable to convert "{0}" into {1}`;
421 errorMessage.FileSizeExceeded = "File {0} size exceeded the maximum size";
422 errorMessage.NumberOfFilesExceeded = "Number of files exceeded the maximum allowed";
423})(errorMessage = exports.errorMessage || (exports.errorMessage = {}));