1 | ;
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const tslib_1 = require("tslib");
|
4 | const tinspector_1 = tslib_1.__importStar(require("tinspector"));
|
5 | const common_1 = require("./common");
|
6 | /* ------------------------------------------------------------------------------- */
|
7 | /* -------------------------------- HELPERS -------------------------------------- */
|
8 | /* ------------------------------------------------------------------------------- */
|
9 | var 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 | /* ------------------------------------------------------------------------------- */
|
27 | class 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 | }
|
54 | exports.ActionResult = ActionResult;
|
55 | class HttpStatusError extends Error {
|
56 | constructor(status, message) {
|
57 | super(message);
|
58 | this.status = status;
|
59 | Object.setPrototypeOf(this, HttpStatusError.prototype);
|
60 | }
|
61 | }
|
62 | exports.HttpStatusError = HttpStatusError;
|
63 | class ConversionError extends HttpStatusError {
|
64 | constructor(issues) {
|
65 | super(400);
|
66 | this.issues = issues;
|
67 | Object.setPrototypeOf(this, ConversionError.prototype);
|
68 | }
|
69 | }
|
70 | exports.ConversionError = ConversionError;
|
71 | class ValidationError extends HttpStatusError {
|
72 | constructor(issues) {
|
73 | super(422);
|
74 | this.issues = issues;
|
75 | Object.setPrototypeOf(this, ValidationError.prototype);
|
76 | }
|
77 | }
|
78 | exports.ValidationError = ValidationError;
|
79 | class DefaultDependencyResolver {
|
80 | resolve(type) {
|
81 | return new type();
|
82 | }
|
83 | }
|
84 | exports.DefaultDependencyResolver = DefaultDependencyResolver;
|
85 | /* ------------------------------------------------------------------------------- */
|
86 | /* ----------------------------- DECORATORS -------------------------------------- */
|
87 | /* ------------------------------------------------------------------------------- */
|
88 | var 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 = {}));
|
209 | class 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 | }
|
360 | exports.RouteDecoratorImpl = RouteDecoratorImpl;
|
361 | exports.route = new RouteDecoratorImpl();
|
362 | var 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 = {}));
|
371 | function domain() { return tinspector_1.default.parameterProperties(); }
|
372 | exports.domain = domain;
|
373 | class 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 | }
|
395 | exports.AuthDecoratorImpl = AuthDecoratorImpl;
|
396 | exports.authorize = new AuthDecoratorImpl();
|
397 | /* ------------------------------------------------------------------------------- */
|
398 | /* -------------------------------- CONSTANTS ------------------------------------ */
|
399 | /* ------------------------------------------------------------------------------- */
|
400 | exports.DefaultConfiguration = {
|
401 | mode: "debug",
|
402 | controller: "./controller",
|
403 | dependencyResolver: new DefaultDependencyResolver()
|
404 | };
|
405 | var 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 = {}));
|