@deepkit/http 1.0.1-alpha.16 → 1.0.1-alpha.160

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/CHANGELOG.md +236 -0
  2. package/dist/cjs/index.d.ts +14 -10
  3. package/dist/cjs/index.js +19 -11
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/src/controllers.d.ts +10 -3
  6. package/dist/cjs/src/controllers.js +7 -3
  7. package/dist/cjs/src/controllers.js.map +1 -1
  8. package/dist/cjs/src/decorator.d.ts +144 -70
  9. package/dist/cjs/src/decorator.js +277 -102
  10. package/dist/cjs/src/decorator.js.map +1 -1
  11. package/dist/cjs/src/filter.d.ts +49 -0
  12. package/dist/cjs/src/filter.js +215 -0
  13. package/dist/cjs/src/filter.js.map +1 -0
  14. package/dist/cjs/src/http-app.d.ts +8 -0
  15. package/dist/cjs/src/http-app.js +16 -0
  16. package/dist/cjs/src/http-app.js.map +1 -0
  17. package/dist/cjs/src/http.d.ts +131 -32
  18. package/dist/cjs/src/http.js +523 -122
  19. package/dist/cjs/src/http.js.map +1 -1
  20. package/dist/cjs/src/kernel.d.ts +50 -9
  21. package/dist/cjs/src/kernel.js +109 -98
  22. package/dist/cjs/src/kernel.js.map +1 -1
  23. package/dist/cjs/src/logger.d.ts +3 -3
  24. package/dist/cjs/src/logger.js +9 -12
  25. package/dist/cjs/src/logger.js.map +1 -1
  26. package/dist/cjs/src/middleware.d.ts +61 -0
  27. package/dist/cjs/src/middleware.js +95 -0
  28. package/dist/cjs/src/middleware.js.map +1 -0
  29. package/dist/cjs/src/model.d.ts +270 -4
  30. package/dist/cjs/src/model.js +401 -3
  31. package/dist/cjs/src/model.js.map +1 -1
  32. package/dist/cjs/src/module.config.d.ts +149 -4
  33. package/dist/cjs/src/module.config.js +11 -6
  34. package/dist/cjs/src/module.config.js.map +1 -1
  35. package/dist/cjs/src/module.d.ts +14 -32
  36. package/dist/cjs/src/module.js +122 -7
  37. package/dist/cjs/src/module.js.map +1 -1
  38. package/dist/cjs/src/request-parser.d.ts +37 -0
  39. package/dist/cjs/src/request-parser.js +382 -0
  40. package/dist/cjs/src/request-parser.js.map +1 -0
  41. package/dist/cjs/src/router.d.ts +174 -75
  42. package/dist/cjs/src/router.js +469 -288
  43. package/dist/cjs/src/router.js.map +1 -1
  44. package/dist/cjs/src/static-serving.d.ts +32 -4
  45. package/dist/cjs/src/static-serving.js +115 -71
  46. package/dist/cjs/src/static-serving.js.map +1 -1
  47. package/dist/cjs/src/utils.js +2 -2
  48. package/dist/cjs/src/utils.js.map +1 -1
  49. package/dist/cjs/tsconfig.tsbuildinfo +1 -2916
  50. package/dist/esm/index.d.ts +14 -10
  51. package/dist/esm/index.js +14 -10
  52. package/dist/esm/index.js.map +1 -1
  53. package/dist/esm/src/controllers.d.ts +10 -3
  54. package/dist/esm/src/controllers.js +7 -3
  55. package/dist/esm/src/controllers.js.map +1 -1
  56. package/dist/esm/src/decorator.d.ts +144 -70
  57. package/dist/esm/src/decorator.js +272 -99
  58. package/dist/esm/src/decorator.js.map +1 -1
  59. package/dist/esm/src/filter.d.ts +49 -0
  60. package/dist/esm/src/filter.js +209 -0
  61. package/dist/esm/src/filter.js.map +1 -0
  62. package/dist/esm/src/http-app.d.ts +8 -0
  63. package/dist/esm/src/http-app.js +12 -0
  64. package/dist/esm/src/http-app.js.map +1 -0
  65. package/dist/esm/src/http.d.ts +131 -32
  66. package/dist/esm/src/http.js +504 -121
  67. package/dist/esm/src/http.js.map +1 -1
  68. package/dist/esm/src/kernel.d.ts +50 -9
  69. package/dist/esm/src/kernel.js +110 -100
  70. package/dist/esm/src/kernel.js.map +1 -1
  71. package/dist/esm/src/logger.d.ts +3 -3
  72. package/dist/esm/src/logger.js +7 -11
  73. package/dist/esm/src/logger.js.map +1 -1
  74. package/dist/esm/src/middleware.d.ts +61 -0
  75. package/dist/esm/src/middleware.js +90 -0
  76. package/dist/esm/src/middleware.js.map +1 -0
  77. package/dist/esm/src/model.d.ts +270 -4
  78. package/dist/esm/src/model.js +357 -1
  79. package/dist/esm/src/model.js.map +1 -1
  80. package/dist/esm/src/module.config.d.ts +149 -4
  81. package/dist/esm/src/module.config.js +9 -5
  82. package/dist/esm/src/module.config.js.map +1 -1
  83. package/dist/esm/src/module.d.ts +14 -32
  84. package/dist/esm/src/module.js +121 -7
  85. package/dist/esm/src/module.js.map +1 -1
  86. package/dist/esm/src/request-parser.d.ts +37 -0
  87. package/dist/esm/src/request-parser.js +372 -0
  88. package/dist/esm/src/request-parser.js.map +1 -0
  89. package/dist/esm/src/router.d.ts +174 -75
  90. package/dist/esm/src/router.js +463 -285
  91. package/dist/esm/src/router.js.map +1 -1
  92. package/dist/esm/src/static-serving.d.ts +32 -4
  93. package/dist/esm/src/static-serving.js +107 -65
  94. package/dist/esm/src/static-serving.js.map +1 -1
  95. package/dist/esm/src/utils.js +1 -0
  96. package/dist/esm/src/utils.js.map +1 -1
  97. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -2916
  98. package/index.ts +14 -10
  99. package/package.json +30 -19
  100. package/src/controllers.ts +4 -3
  101. package/src/decorator.ts +242 -104
  102. package/src/filter.ts +224 -0
  103. package/src/http-app.ts +11 -0
  104. package/src/http.ts +501 -100
  105. package/src/kernel.ts +128 -80
  106. package/src/logger.ts +4 -6
  107. package/src/middleware.ts +116 -0
  108. package/src/model.ts +516 -5
  109. package/src/module.config.ts +168 -5
  110. package/src/module.ts +120 -7
  111. package/src/request-parser.ts +430 -0
  112. package/src/router.ts +546 -317
  113. package/src/static-serving.ts +120 -50
  114. package/src/utils.ts +0 -2
  115. package/tsconfig.esm.json +7 -7
  116. package/tsconfig.json +18 -11
  117. package/tsconfig.spec.json +8 -0
  118. package/dist/cjs/tests/router.spec.d.ts +0 -1
  119. package/dist/cjs/tests/router.spec.js +0 -481
  120. package/dist/cjs/tests/router.spec.js.map +0 -1
  121. package/dist/esm/tests/router.spec.d.ts +0 -1
  122. package/dist/esm/tests/router.spec.js +0 -479
  123. package/dist/esm/tests/router.spec.js.map +0 -1
  124. package/tests/router.spec.ts +0 -347
package/src/router.ts CHANGED
@@ -7,138 +7,189 @@
7
7
  *
8
8
  * You should have received a copy of the MIT License along with this program.
9
9
  */
10
+ import { ClassType, CompilerContext, getClassName, isArray, isClass, urlJoin } from '@deepkit/core';
11
+ import {
12
+ entity,
13
+ ReflectionClass,
14
+ ReflectionFunction,
15
+ ReflectionKind,
16
+ ReflectionParameter,
17
+ SerializationOptions,
18
+ serializer,
19
+ Serializer,
20
+ Type,
21
+ ValidationError,
22
+ } from '@deepkit/type';
23
+ import { getActions, HttpAction, httpClass, HttpController, HttpDecorator } from './decorator.js';
24
+ import { HttpRequest, HttpRequestPositionedParameters, HttpRequestQuery, HttpRequestResolvedParameters } from './model.js';
25
+ import { InjectorContext, InjectorModule } from '@deepkit/injector';
26
+ import { Logger, LoggerInterface } from '@deepkit/logger';
27
+ import { HttpControllers } from './controllers.js';
28
+ import { MiddlewareRegistry, MiddlewareRegistryEntry } from '@deepkit/app';
29
+ import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware.js';
30
+
31
+ //@ts-ignore
32
+ import qs from 'qs';
33
+ import { HtmlResponse, JSONResponse, Response } from './http.js';
34
+ import { getRequestParserCodeForParameters, ParameterForRequestParser, parseRoutePathToRegex } from './request-parser.js';
35
+ import { HttpConfig } from './module.config.js';
36
+
37
+ export type RouteParameterResolverForInjector = ((injector: InjectorContext) => HttpRequestPositionedParameters | Promise<HttpRequestPositionedParameters>);
38
+
39
+ interface ResolvedController {
40
+ parameters: RouteParameterResolverForInjector;
41
+ routeConfig: RouteConfig;
42
+ uploadedFiles: { [name: string]: UploadedFile };
43
+ middlewares?: (injector: InjectorContext) => { fn: HttpMiddlewareFn, timeout?: number }[];
44
+ }
10
45
 
11
- import { asyncOperation, ClassType, CompilerContext } from '@deepkit/core';
12
- import { getClassSchema, getPropertyXtoClassFunction, jitValidateProperty, jsonSerializer, PropertySchema, ValidationFailed, ValidationFailedItem } from '@deepkit/type';
13
- // @ts-ignore
14
- import formidable from 'formidable';
15
- import { IncomingMessage } from 'http';
16
- import { join } from 'path';
17
- import querystring from 'querystring';
18
- import { httpClass } from './decorator';
19
- import { HttpRequest, HttpRequestQuery, HttpRequestResolvedParameters } from './model';
20
- import { BasicInjector, injectable, NormalizedProvider, Tag } from '@deepkit/injector';
21
- import { Logger } from '@deepkit/logger';
22
- import { TagProviders } from '@deepkit/app';
23
- import { HttpControllers } from './controllers';
24
-
25
- export type RouteParameterResolverForInjector = ((injector: BasicInjector) => any[] | Promise<any[]>);
26
- type ResolvedController = { parameters: RouteParameterResolverForInjector, routeConfig: RouteConfig };
27
-
28
-
29
- function parseBody(form: any, req: IncomingMessage) {
30
- return asyncOperation((resolve, reject) => {
31
- form.parse(req, (err: any, fields: any, files: any) => {
32
- if (err) {
33
- reject(err);
34
- } else {
35
- resolve({ fields, files });
36
- }
37
- });
38
- });
46
+ export const UploadedFileSymbol = Symbol('UploadedFile');
47
+
48
+ @entity.name('@deepkit/UploadedFile')
49
+ export class UploadedFile {
50
+ /**
51
+ * Validator to ensure the file was provided by the framework, and not the user spoofing it.
52
+ */
53
+ validator!: typeof UploadedFileSymbol | null;
54
+
55
+ /**
56
+ * The size of the uploaded file in bytes.
57
+ */
58
+ size!: number;
59
+
60
+ /**
61
+ * The local path this file is being written to. Will be deleted when request is handled.
62
+ */
63
+ path!: string;
64
+
65
+ /**
66
+ * The name this file had according to the uploading client.
67
+ */
68
+ name!: string | null;
69
+
70
+ /**
71
+ * The mime type of this file, according to the uploading client.
72
+ */
73
+ type!: string | null;
74
+
75
+ /**
76
+ * A Date object (or `null`) containing the time this file was last written to.
77
+ * Mostly here for compatibility with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/).
78
+ */
79
+ lastModifiedDate!: Date | null;
80
+
81
+ // /**
82
+ // * If `options.hash` calculation was set, you can read the hex digest out of this var.
83
+ // */
84
+ // hash!: string | 'sha1' | 'md5' | 'sha256' | null;
39
85
  }
40
86
 
41
- export interface RouteControllerAction {
87
+ serializer.typeGuards.getRegistry(1).registerClass(UploadedFile, (type, state) => {
88
+ state.setContext({ UploadedFileSymbol });
89
+ state.addSetterAndReportErrorIfInvalid('uploadSecurity', 'Not an uploaded file', `typeof ${state.accessor} === 'object' && ${state.accessor} !== null && ${state.accessor}.validator === UploadedFileSymbol`);
90
+ });
91
+
92
+ export interface RouteFunctionControllerAction {
93
+ type: 'function';
94
+ //if not set, the root module is used
95
+ module?: InjectorModule<any>;
96
+ fn: (...args: any[]) => any;
97
+ }
98
+
99
+ export interface RouteClassControllerAction {
100
+ type: 'controller';
101
+ //if not set, the root module is used
102
+ module?: InjectorModule<any>;
42
103
  controller: ClassType;
43
104
  methodName: string;
44
105
  }
45
106
 
46
- export interface RouteParameterConfig {
47
- type?: 'body' | 'query';
48
- /**
49
- * undefined = propertyName, '' === root, else given path
50
- */
51
- typePath?: string;
52
-
53
- optional: boolean;
54
- name: string;
107
+ export function getRouteActionLabel(action: RouteClassControllerAction | RouteFunctionControllerAction): string {
108
+ if (action.type === 'controller') return `${getClassName(action.controller)}.${action.methodName}`;
109
+ return action.fn.name;
55
110
  }
56
111
 
57
112
  export class RouteConfig {
58
113
  public baseUrl: string = '';
59
- public parameterRegularExpressions: { [name: string]: any } = {};
60
- public throws: { errorType: ClassType, message?: string }[] = [];
114
+
115
+ public responses: { statusCode: number, description: string, type?: Type }[] = [];
116
+
61
117
  public description: string = '';
62
118
  public groups: string[] = [];
63
119
  public category: string = '';
64
120
 
121
+ public returnType?: Type;
122
+
123
+ public serializationOptions?: SerializationOptions;
124
+ public serializer?: Serializer;
125
+
126
+ /**
127
+ * When assigned defines where this route came from.
128
+ */
129
+ public module?: InjectorModule<any>;
130
+
131
+ resolverForToken: Map<any, ClassType> = new Map();
132
+
133
+ middlewares: { config: HttpMiddlewareConfig, module?: InjectorModule<any> }[] = [];
134
+
135
+ resolverForParameterName: Map<string, ClassType> = new Map();
136
+
65
137
  /**
66
138
  * An arbitrary data container the user can use to store app specific settings/values.
67
139
  */
68
140
  data = new Map<any, any>();
69
141
 
70
- public parameters: {
71
- [name: string]: RouteParameterConfig
72
- } = {};
73
-
74
142
  constructor(
75
143
  public readonly name: string,
76
- public readonly httpMethod: string,
144
+ public readonly httpMethods: string[],
77
145
  public readonly path: string,
78
- public readonly action: RouteControllerAction,
79
- ) {
80
- }
81
-
82
- getFullPath(): string {
83
- let path = this.baseUrl ? join(this.baseUrl, this.path) : this.path;
84
- if (!path.startsWith('/')) path = '/' + path;
85
- return path;
86
- }
87
- }
88
-
89
- /**
90
- * When this class is injected into a route, then validation errors are not automatically thrown (using onHttpControllerValidationError event),
91
- * but injected to the route itself. The user is then responsible to handle the errors.
92
- *
93
- * Note: The body parameter is still passed, however it might contain now invalid data. The BodyValidation tells what data is invalid.
94
- */
95
- export class BodyValidation {
96
- constructor(
97
- public readonly errors: ValidationFailedItem[] = []
146
+ public readonly action: RouteClassControllerAction | RouteFunctionControllerAction,
147
+ public internal: boolean = false,
98
148
  ) {
99
149
  }
100
150
 
101
- hasErrors(prefix?: string): boolean {
102
- return this.getErrors(prefix).length > 0;
151
+ getReflectionFunction(): ReflectionFunction {
152
+ return this.action.type === 'controller' ?
153
+ ReflectionClass.from(this.action.controller).getMethod(this.action.methodName)
154
+ : ReflectionFunction.from(this.action.fn);
103
155
  }
104
156
 
105
- getErrors(prefix?: string): ValidationFailedItem[] {
106
- if (prefix) return this.errors.filter(v => v.path.startsWith(prefix));
107
-
108
- return this.errors;
109
- }
110
-
111
- getErrorsForPath(path: string): ValidationFailedItem[] {
112
- return this.errors.filter(v => v.path === path);
157
+ getSchemaForResponse(statusCode: number): Type | undefined {
158
+ if (!this.responses.length) return;
159
+ for (const response of this.responses) {
160
+ if (response.statusCode === statusCode) return response.type;
161
+ }
162
+ return;
113
163
  }
114
164
 
115
- getErrorMessageForPath(path: string): string {
116
- return this.getErrorsForPath(path).map(v => v.message).join(', ');
165
+ getFullPath(): string {
166
+ let path = this.baseUrl ? urlJoin(this.baseUrl, this.path) : this.path;
167
+ if (!path.startsWith('/')) path = '/' + path;
168
+ return path;
117
169
  }
118
170
  }
119
171
 
120
- class ParsedRoute {
172
+ export class ParsedRoute {
121
173
  public regex?: string;
122
- public customValidationErrorHandling?: ParsedRouteParameter;
123
174
 
124
175
  public pathParameterNames: { [name: string]: number } = {};
125
176
 
126
- protected parameters: ParsedRouteParameter[] = [];
177
+ protected parameters: ParameterForRequestParser[] = [];
127
178
 
128
179
  constructor(public routeConfig: RouteConfig) {
129
180
  }
130
181
 
131
- addParameter(property: PropertySchema, config?: RouteParameterConfig): ParsedRouteParameter {
132
- const parameter = new ParsedRouteParameter(property, config);
182
+ addParameter(property: ReflectionParameter): ParameterForRequestParser {
183
+ const parameter = new ParameterForRequestParser(property);
133
184
  this.parameters.push(parameter);
134
185
  return parameter;
135
186
  }
136
187
 
137
- getParameters(): ParsedRouteParameter[] {
188
+ getParameters(): ParameterForRequestParser[] {
138
189
  return this.parameters;
139
190
  }
140
191
 
141
- getParameter(name: string): ParsedRouteParameter {
192
+ getParameter(name: string): ParameterForRequestParser {
142
193
  for (const parameter of this.parameters) {
143
194
  if (parameter.getName() === name) return parameter;
144
195
  }
@@ -146,68 +197,16 @@ class ParsedRoute {
146
197
  }
147
198
  }
148
199
 
149
- class ParsedRouteParameter {
150
- regexPosition?: number;
151
-
152
- constructor(
153
- public property: PropertySchema,
154
- public config?: RouteParameterConfig,
155
- ) {
156
- }
157
-
158
- get body() {
159
- return this.config ? this.config.type === 'body' : false;
160
- }
161
-
162
- get query() {
163
- return this.config ? this.config.type === 'query' : false;
164
- }
165
-
166
- get typePath() {
167
- return this.config ? this.config.typePath : undefined;
168
- }
169
-
170
- getName() {
171
- return this.property.name;
172
- }
173
-
174
- isPartOfPath(): boolean {
175
- return this.regexPosition !== undefined;
176
- }
177
- }
178
-
179
- function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, parameterNames: { [name: string]: number } } {
180
- const parameterNames: { [name: string]: number } = {};
181
- let path = routeConfig.getFullPath();
182
-
183
- let argumentIndex = 0;
184
- path = path.replace(/:(\w+)/g, (a, name) => {
185
- parameterNames[name] = argumentIndex;
186
- argumentIndex++;
187
- return routeConfig.parameterRegularExpressions[name] ? '(' + routeConfig.parameterRegularExpressions[name] + ')' : String.raw`([^/]+)`;
188
- });
189
-
190
- return { regex: path, parameterNames };
191
- }
192
-
193
200
  export function parseRouteControllerAction(routeConfig: RouteConfig): ParsedRoute {
194
- const schema = getClassSchema(routeConfig.action.controller);
195
201
  const parsedRoute = new ParsedRoute(routeConfig);
196
202
 
197
- const methodArgumentProperties = schema.getMethodProperties(routeConfig.action.methodName);
198
- const parsedPath = parseRoutePathToRegex(routeConfig);
203
+ const parameters = routeConfig.getReflectionFunction().getParameters();
204
+ const parsedPath = parseRoutePathToRegex(routeConfig.getFullPath(), parameters);
199
205
  parsedRoute.regex = parsedPath.regex;
200
206
  parsedRoute.pathParameterNames = parsedPath.parameterNames;
201
207
 
202
- for (const property of methodArgumentProperties) {
203
- const decoratorData = routeConfig.parameters[property.name];
204
- const parsedParameter = parsedRoute.addParameter(property, decoratorData);
205
-
206
- if (decoratorData && decoratorData.optional) property.isOptional = true;
207
-
208
- if (property.type === 'class' && property.classType === BodyValidation) {
209
- parsedRoute.customValidationErrorHandling = parsedParameter;
210
- }
208
+ for (const property of parameters) {
209
+ const parsedParameter = parsedRoute.addParameter(property);
211
210
  parsedParameter.regexPosition = parsedPath.parameterNames[property.name];
212
211
  }
213
212
 
@@ -225,182 +224,433 @@ export interface RouteParameterResolver {
225
224
  }
226
225
 
227
226
  export interface RouteParameterResolverContext {
228
- classType: ClassType;
227
+ token: ClassType | string | symbol | any;
229
228
  route: RouteConfig;
230
229
  request: HttpRequest;
230
+
231
+ /**
232
+ * The parameter name (variable name).
233
+ */
234
+ name: any;
235
+
236
+ /**
237
+ * The raw parameter value from the path, if the parameter is defined in the path (e.g. /user/:name).
238
+ * If not in the path, you have to use `parameters.<name>` instead.
239
+ */
240
+ value: any;
241
+
231
242
  query: HttpRequestQuery;
232
243
  parameters: HttpRequestResolvedParameters;
244
+ type: ReflectionParameter;
233
245
  }
234
246
 
235
- export class RouteParameterResolverTag extends Tag<RouteParameterResolver> {
236
- public classTypes: ClassType[] = [];
247
+ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry[], routeConfig: RouteConfig, fullPath: string): {
248
+ config: HttpMiddlewareConfig,
249
+ module: InjectorModule<any>
250
+ }[] {
251
+ const middlewares = middlewareRawConfigs.slice(0);
252
+ middlewares.push(...routeConfig.middlewares as any[]);
237
253
 
238
- constructor(
239
- public provider: NormalizedProvider,
240
- ) {
241
- super(provider);
242
- if (!this.provider.scope) provider.scope = 'http';
254
+ const middlewareConfigs = middlewares.filter((v) => {
255
+ if (!(v.config instanceof HttpMiddlewareConfig)) return false;
256
+
257
+ if (v.config.controllers.length && routeConfig.action.type === 'controller' && !v.config.controllers.includes(routeConfig.action.controller)) {
258
+ return false;
259
+ }
260
+
261
+ if (v.config.excludeControllers.length && routeConfig.action.type === 'controller' && v.config.excludeControllers.includes(routeConfig.action.controller)) {
262
+ return false;
263
+ }
264
+
265
+ if (v.config.modules.length && (!routeConfig.module || !v.config.modules.includes(routeConfig.module))) {
266
+ if (!routeConfig.module) return false;
267
+ for (const module of v.config.modules) {
268
+ if (routeConfig.module.id !== module.id) return false;
269
+ }
270
+ }
271
+
272
+ if (v.config.selfModule && v.module.id !== routeConfig.module?.id) return false;
273
+
274
+ if (v.config.routeNames.length) {
275
+ let found: boolean = false;
276
+ for (const name of v.config.routeNames) {
277
+ if (name.includes('*')) {
278
+ const regex = new RegExp('^' + name.replace(/\*/g, '.*') + '$');
279
+ if (regex.test(routeConfig.name)) {
280
+ found = true;
281
+ break;
282
+ }
283
+ } else if (name === routeConfig.name) {
284
+ found = true;
285
+ break;
286
+ }
287
+ }
288
+ if (!found) return false;
289
+ }
290
+
291
+ if (v.config.excludeRouteNames.length) {
292
+ for (const name of v.config.excludeRouteNames) {
293
+ if (name.includes('*')) {
294
+ const regex = new RegExp('^' + name.replace(/\*/g, '.*') + '$');
295
+ if (regex.test(routeConfig.name)) return false;
296
+ } else if (name == routeConfig.name) {
297
+ return false;
298
+ }
299
+ }
300
+ }
301
+
302
+ for (const route of v.config.routes) {
303
+ if (route.httpMethod && !routeConfig.httpMethods.includes(route.httpMethod)) return false;
304
+
305
+ if (route.category && route.category !== routeConfig.category) return false;
306
+ if (route.excludeCategory && route.excludeCategory === routeConfig.category) return false;
307
+
308
+ if (route.group && !routeConfig.groups.includes(route.group)) return false;
309
+ if (route.excludeGroup && routeConfig.groups.includes(route.excludeGroup)) return false;
310
+
311
+ if (route.path || route.pathRegExp) {
312
+ if (!route.pathRegExp && route.path) route.pathRegExp = new RegExp('^' + route.path.replace(/\*/g, '.*') + '$');
313
+ if (route.pathRegExp && !route.pathRegExp.test(fullPath)) return false;
314
+ }
315
+ }
316
+
317
+ return true;
318
+ }) as { config: HttpMiddlewareConfig, module: any }[];
319
+
320
+ middlewareConfigs.sort((a, b) => {
321
+ return a.config.order - b.config.order;
322
+ });
323
+
324
+ return middlewareConfigs;
325
+ }
326
+
327
+ export interface HttpRouterFunctionOptions {
328
+ path: string;
329
+ name?: string;
330
+ methods?: string[];
331
+ description?: string;
332
+ category?: string;
333
+ groups?: string[];
334
+
335
+ /**
336
+ * An arbitrary data container the user can use to store app specific settings/values.
337
+ */
338
+ data?: Record<any, any>;
339
+
340
+ baseUrl?: string;
341
+ middlewares?: (() => HttpMiddlewareConfig)[];
342
+
343
+ serializer?: Serializer;
344
+ serializationOptions?: SerializationOptions;
345
+
346
+ resolverForToken?: Map<any, ClassType>;
347
+ resolverForParameterName?: Map<string, ClassType>;
348
+
349
+ responses?: { statusCode: number, description: string, type?: Type }[];
350
+ }
351
+
352
+ function convertOptions(methods: string[], pathOrOptions: string | HttpRouterFunctionOptions, defaultOptions: Partial<HttpRouterFunctionOptions>): HttpRouterFunctionOptions {
353
+ const options = 'string' === typeof pathOrOptions ? { path: pathOrOptions } : pathOrOptions;
354
+ if (options.methods) return options;
355
+ return { ...options, methods };
356
+ }
357
+
358
+ /**
359
+ * Annotated types like HTMLResponse/JSONResponse are not used for serialization.
360
+ */
361
+ function filterValidReturnType(type: Type): Type | undefined {
362
+ if (type.kind === ReflectionKind.class && (type.classType === HtmlResponse || type.classType === JSONResponse || type.classType === Response)) return;
363
+ return type;
364
+ }
365
+
366
+ export abstract class HttpRouterRegistryFunctionRegistrar {
367
+ protected defaultOptions: Partial<HttpRouterFunctionOptions> = {};
368
+
369
+ abstract addRoute(routeConfig: RouteConfig): void;
370
+
371
+ /**
372
+ * Returns a new registrar object with default options that apply to each registered route through this registrar.
373
+ *
374
+ * ```typescript
375
+ * const registry: HttpRouterRegistry = ...;
376
+ *
377
+ * const secretRegistry = registry.forOptions({groups: ['secret']});
378
+ *
379
+ * secretRegistry.get('/admin/groups', () => {
380
+ * });
381
+ *
382
+ * secretRegistry.get('/admin/users', () => {
383
+ * });
384
+ * ```
385
+ */
386
+ forOptions(options: Partial<HttpRouterFunctionOptions>): HttpRouterRegistryFunctionRegistrar {
387
+ const that = this;
388
+ return new class extends HttpRouterRegistryFunctionRegistrar {
389
+ defaultOptions = options;
390
+
391
+ addRoute(routeConfig: RouteConfig) {
392
+ that.addRoute(routeConfig);
393
+ }
394
+ };
395
+ }
396
+
397
+ public any(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
398
+ this.register(convertOptions([], pathOrOptions, this.defaultOptions), callback);
399
+ }
400
+
401
+ public add(decorator: HttpDecorator, callback: (...args: any[]) => any) {
402
+ const data = decorator(Object, '_');
403
+ const action = isArray(data) ? data.find(v => v instanceof HttpAction) : undefined;
404
+ if (!action) throw new Error('No HttpAction available');
405
+
406
+ const fn = ReflectionFunction.from(callback);
407
+ const routeConfig = createRouteConfigFromHttpAction({
408
+ type: 'function',
409
+ fn: callback,
410
+ }, action);
411
+ routeConfig.returnType = filterValidReturnType(fn.getReturnType());
412
+ this.addRoute(routeConfig);
243
413
  }
244
414
 
245
- forClassType(...classTypes: ClassType[]): this {
246
- this.classTypes = classTypes;
247
- return this;
415
+ public get(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
416
+ this.register(convertOptions(['GET'], pathOrOptions, this.defaultOptions), callback);
417
+ }
418
+
419
+ public post(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
420
+ this.register(convertOptions(['POST'], pathOrOptions, this.defaultOptions), callback);
421
+ }
422
+
423
+ public put(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
424
+ this.register(convertOptions(['PUT'], pathOrOptions, this.defaultOptions), callback);
425
+ }
426
+
427
+ public patch(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
428
+ this.register(convertOptions(['PATCH'], pathOrOptions, this.defaultOptions), callback);
429
+ }
430
+
431
+ public delete(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
432
+ this.register(convertOptions(['DELETE'], pathOrOptions, this.defaultOptions), callback);
433
+ }
434
+
435
+ public options(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
436
+ this.register(convertOptions(['OPTIONS'], pathOrOptions, this.defaultOptions), callback);
437
+ }
438
+
439
+ public trace(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
440
+ this.register(convertOptions(['TRACE'], pathOrOptions, this.defaultOptions), callback);
441
+ }
442
+
443
+ public head(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
444
+ this.register(convertOptions(['HEAD'], pathOrOptions, this.defaultOptions), callback);
445
+ }
446
+
447
+ private register(options: HttpRouterFunctionOptions, callback: (...args: any[]) => any, module?: InjectorModule<any>) {
448
+ const fn = ReflectionFunction.from(callback);
449
+
450
+ const routeConfig = new RouteConfig(options.name || '', options.methods || [], options.path, {
451
+ type: 'function',
452
+ fn: callback,
453
+ });
454
+ routeConfig.module = module;
455
+ if (options.responses) routeConfig.responses = options.responses;
456
+ if (options.description) routeConfig.description = options.description;
457
+ if (options.category) routeConfig.category = options.category;
458
+ if (options.groups) routeConfig.groups = options.groups;
459
+ if (options.data) routeConfig.data = new Map(Object.entries(options.data));
460
+ if (options.baseUrl) routeConfig.baseUrl = options.baseUrl;
461
+ if (options.middlewares) {
462
+ routeConfig.middlewares = options.middlewares.map(v => {
463
+ return { config: v(), module };
464
+ });
465
+ }
466
+
467
+ if (options.resolverForToken) {
468
+ for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
469
+ }
470
+
471
+ if (options.resolverForToken) {
472
+ for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
473
+ }
474
+
475
+ if (options.resolverForParameterName) {
476
+ for (const item of options.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
477
+ }
478
+
479
+ routeConfig.serializer = options.serializer;
480
+ routeConfig.serializationOptions = options.serializationOptions;
481
+
482
+ routeConfig.returnType = filterValidReturnType(fn.getReturnType());
483
+ this.addRoute(routeConfig);
248
484
  }
249
485
  }
250
486
 
251
- @injectable()
252
- export class Router {
253
- protected fn?: (request: HttpRequest) => ResolvedController | undefined;
254
- protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
487
+ function createRouteConfigFromHttpAction(routeAction: RouteClassControllerAction | RouteFunctionControllerAction, action: HttpAction, module?: InjectorModule<any>, controller?: HttpController) {
488
+ const routeConfig = new RouteConfig(action.name, action.httpMethods, action.path, routeAction);
489
+ routeConfig.responses = action.responses;
490
+ routeConfig.description = action.description;
491
+ routeConfig.category = action.category;
492
+ routeConfig.groups = action.groups;
493
+ routeConfig.data = new Map(action.data);
494
+ if (controller) {
495
+ routeConfig.baseUrl = controller.baseUrl;
496
+
497
+ routeConfig.middlewares = controller.middlewares.map(v => {
498
+ return { config: v(), module };
499
+ });
500
+ routeConfig.resolverForToken = new Map(controller.resolverForToken);
501
+ routeConfig.resolverForParameterName = new Map(controller.resolverForParameterName);
502
+ }
503
+
504
+ routeConfig.middlewares.push(...action.middlewares.map(v => {
505
+ return { config: v(), module };
506
+ }));
507
+
508
+ for (const item of action.resolverForToken) routeConfig.resolverForToken.set(...item);
509
+
510
+ for (const item of action.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
511
+
512
+ routeConfig.serializer = action.serializer;
513
+ routeConfig.serializationOptions = action.serializationOptions;
514
+ return routeConfig;
515
+ }
255
516
 
517
+ export class HttpRouterRegistry extends HttpRouterRegistryFunctionRegistrar {
256
518
  protected routes: RouteConfig[] = [];
519
+ private buildId: number = 1;
257
520
 
258
- protected parameterResolverTags: RouteParameterResolverTag[] = [];
521
+ public getBuildId(): number {
522
+ return this.buildId;
523
+ }
259
524
 
260
- //todo, move some settings to KernelConfig
261
- protected form = formidable({
262
- multiples: true,
263
- hash: 'sha1',
264
- enabledPlugins: ['octetstream', 'querystring', 'json'],
265
- });
525
+ public getRoutes(): RouteConfig[] {
526
+ return this.routes;
527
+ }
528
+
529
+ public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
530
+ const controllerData = httpClass._fetch(controller);
531
+ if (!controllerData) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
532
+ const schema = ReflectionClass.from(controller);
533
+
534
+ for (const action of getActions(controller)) {
535
+ const routeAction: RouteClassControllerAction = {
536
+ type: 'controller',
537
+ controller,
538
+ module,
539
+ methodName: action.methodName
540
+ };
541
+ const routeConfig = createRouteConfigFromHttpAction(routeAction, action, module, controllerData);
542
+
543
+ routeConfig.module = module;
544
+
545
+ if (schema.hasMethod(action.methodName)) routeConfig.returnType = filterValidReturnType(schema.getMethod(action.methodName).getReturnType());
546
+ this.addRoute(routeConfig);
547
+ }
548
+ }
549
+
550
+ public addRoute(routeConfig: RouteConfig) {
551
+ this.routes.push(routeConfig);
552
+ this.buildId++;
553
+ }
554
+ }
555
+
556
+ export class HttpRouter {
557
+ protected fn?: (request: HttpRequest) => ResolvedController | undefined;
558
+ protected buildId: number = 0;
559
+ protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
266
560
 
267
561
  constructor(
268
562
  controllers: HttpControllers,
269
- private logger: Logger,
270
- tagProviders: TagProviders,
563
+ private logger: LoggerInterface,
564
+ private config: HttpConfig,
565
+ private middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry,
566
+ private registry: HttpRouterRegistry = new HttpRouterRegistry,
271
567
  ) {
272
- this.parameterResolverTags = tagProviders.getProviders(RouteParameterResolverTag);
273
-
274
- for (const controller of controllers.controllers) this.addRouteForController(controller);
568
+ for (const controller of controllers.controllers) {
569
+ this.addRouteForController(controller.controller, controller.module);
570
+ }
275
571
  }
276
572
 
277
573
  getRoutes(): RouteConfig[] {
278
- return this.routes;
574
+ return this.registry.getRoutes();
279
575
  }
280
576
 
281
- static forControllers(controllers: ClassType[], tagProviders: TagProviders = new TagProviders()): Router {
282
- return new this(new HttpControllers(controllers), new Logger([], []), tagProviders);
577
+ static forControllers(
578
+ controllers: (ClassType | { module: InjectorModule<any>, controller: ClassType })[],
579
+ middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry(),
580
+ module: InjectorModule<any> = new InjectorModule(),
581
+ config: HttpConfig = new HttpConfig()
582
+ ): HttpRouter {
583
+ return new this(new HttpControllers(controllers.map(v => {
584
+ return isClass(v) ? { controller: v, module } : v;
585
+ })), new Logger([], []), config, middlewareRegistry);
283
586
  }
284
587
 
285
588
  protected getRouteCode(compiler: CompilerContext, routeConfig: RouteConfig): string {
286
- const routeConfigVar = compiler.reserveVariable('routeConfigVar', routeConfig);
287
589
  const parsedRoute = parseRouteControllerAction(routeConfig);
288
590
  const path = routeConfig.getFullPath();
289
- const prefix = path.substr(0, path.indexOf(':'));
591
+ const pathParamStarter = path.indexOf(':');
592
+ const prefix = path.substr(0, pathParamStarter);
290
593
 
291
594
  const regexVar = compiler.reserveVariable('regex', new RegExp('^' + parsedRoute.regex + '$'));
292
- const setParameters: string[] = [];
293
- const parameterNames: string[] = [];
294
- const parameterValidator: string[] = [];
295
- let bodyValidationErrorHandling = `if (bodyErrors.length) throw ValidationFailed.from(bodyErrors);`;
296
-
297
- let enableParseBody = false;
298
- const hasParameters = parsedRoute.getParameters().length > 0;
299
- let requiresAsyncParameters = false;
300
- let setParametersFromPath = '';
301
-
302
- params:
303
- for (const parameter of parsedRoute.getParameters()) {
304
- if (parameter.isPartOfPath()) {
305
- const converted = parameter.property.type === 'any' ? (v: any) => v : getPropertyXtoClassFunction(parameter.property, jsonSerializer);
306
- const validator = parameter.property.type === 'any' ? (v: any) => undefined : jitValidateProperty(parameter.property);
307
- const converterVar = compiler.reserveVariable('argumentConverter', converted);
308
- const validatorVar = compiler.reserveVariable('argumentValidator', validator);
309
-
310
- setParameters.push(`parameters.${parameter.property.name} = ${converterVar}(_match[${1 + (parameter.regexPosition || 0)}]);`);
311
- parameterValidator.push(`${validatorVar}(_match[1 + ${parameter.regexPosition}], ${JSON.stringify(parameter.getName())}, validationErrors);`);
312
- parameterNames.push(`parameters.${parameter.property.name}`);
313
- } else {
314
- if (parsedRoute.customValidationErrorHandling === parameter) {
315
- compiler.context.set('BodyValidation', BodyValidation);
316
- bodyValidationErrorHandling = '';
317
- setParameters.push(`parameters.${parameter.property.name} = new BodyValidation(bodyErrors);`);
318
- parameterNames.push(`parameters.${parameter.property.name}`);
319
- } else if (parameter.body) {
320
- const bodyVar = compiler.reserveVariable('body');
321
-
322
- const validatorVar = compiler.reserveVariable('argumentValidator', jitValidateProperty(parameter.property));
323
- const converterVar = compiler.reserveVariable('argumentConverter', getPropertyXtoClassFunction(parameter.property, jsonSerializer));
324
-
325
- enableParseBody = true;
326
- parameterValidator.push(`
327
- ${bodyVar} = ${converterVar}(bodyFields);
328
- ${validatorVar}(${bodyVar}, ${JSON.stringify(parameter.typePath || '')}, bodyErrors);
329
- `);
330
- setParameters.push(`parameters.${parameter.property.name} = ${bodyVar};`);
331
- parameterNames.push(`parameters.${parameter.property.name}`);
332
- } else if (parameter.query) {
333
- const converted = parameter.property.type === 'any' ? (v: any) => v : getPropertyXtoClassFunction(parameter.property, jsonSerializer);
334
- const validator = parameter.property.type === 'any' ? (v: any) => undefined : jitValidateProperty(parameter.property);
335
- const converterVar = compiler.reserveVariable('argumentConverter', converted);
336
- const validatorVar = compiler.reserveVariable('argumentValidator', validator);
337
-
338
- const queryPath = parameter.typePath === undefined ? parameter.property.name : parameter.typePath;
339
- const accessor = queryPath ? `['` + (queryPath.replace(/\./g, `']['`)) + `']` : '';
340
- const queryAccessor = queryPath ? `_query${accessor}` : '_query';
341
- setParameters.push(`parameters.${parameter.property.name} = ${converterVar}(${queryAccessor});`);
342
- parameterNames.push(`parameters.${parameter.property.name}`);
343
- parameterValidator.push(`${validatorVar}(${queryAccessor}, ${JSON.stringify(parameter.typePath)}, validationErrors);`);
595
+
596
+ const hasParameters = pathParamStarter !== -1 || parsedRoute.getParameters().length > 0;
597
+
598
+ const fullPath = routeConfig.getFullPath();
599
+ const middlewareConfigs = filterMiddlewaresForRoute(this.middlewareRegistry.configs, routeConfig, fullPath);
600
+
601
+ const parameters = [...parsedRoute.getParameters()];
602
+
603
+ //look through all http listeners and check if they need any http parameters.
604
+ //the router then parses
605
+
606
+ let matcher = `_path.startsWith(${JSON.stringify(prefix)}) && (_match = _path.match(${regexVar}))`;
607
+ if (!hasParameters) {
608
+ matcher = `_path === ${JSON.stringify(path)}`;
609
+ }
610
+
611
+ let middlewares = 'undefined';
612
+ if (middlewareConfigs.length) {
613
+ const middlewareItems: string[] = [];
614
+ for (const middlewareConfig of middlewareConfigs) {
615
+ const moduleVar = middlewareConfig.module ? ', ' + compiler.reserveVariable('module', middlewareConfig.module) : '';
616
+
617
+ for (const middleware of middlewareConfig.config.middlewares) {
618
+ if (isClass(middleware)) {
619
+ const classVar = compiler.reserveVariable('middlewareClassType', middleware);
620
+ middlewareItems.push(`{fn: function() {return _injector.get(${classVar}${moduleVar}).execute(...arguments) }, timeout: ${middlewareConfig.config.timeout}}`);
344
621
  } else {
345
- const classType = parameter.property.getResolvedClassType();
346
- const classTypeVar = compiler.reserveVariable('classType', classType);
347
-
348
- for (const resolverTag of this.parameterResolverTags) {
349
- if (resolverTag.classTypes.includes(classType)) {
350
- const resolverProvideTokenVar = compiler.reserveVariable('resolverProvideToken', resolverTag.provider.provide);
351
- requiresAsyncParameters = true;
352
- if (!setParametersFromPath) {
353
- for (const i in parsedRoute.pathParameterNames) {
354
- setParametersFromPath += `parameters.${i} = _match[${1 + parsedRoute.pathParameterNames[i]}];`;
355
- }
356
- }
357
- setParameters.push(`parameters.${parameter.property.name} = await _injector.get(${resolverProvideTokenVar}).resolve(
358
- {classType: ${classTypeVar}, routeConfig: ${routeConfigVar}, request: request, query: _query, parameters: parameters}
359
- );`);
360
- parameterNames.push(`parameters.${parameter.property.name}`);
361
- continue params;
362
- }
363
- }
364
-
365
- setParameters.push(`parameters.${parameter.property.name} = _injector.get(${classTypeVar});`);
366
- parameterNames.push(`parameters.${parameter.property.name}`);
622
+ middlewareItems.push(`{fn: ${compiler.reserveVariable('middlewareFn', middleware)}, timeout: ${middlewareConfig.config.timeout}}`);
367
623
  }
368
624
  }
369
625
  }
370
626
 
371
- let parseBodyLoading = '';
372
- if (enableParseBody) {
373
- const parseBodyVar = compiler.reserveVariable('parseBody', parseBody);
374
- const formVar = compiler.reserveVariable('form', this.form);
375
- parseBodyLoading = `const bodyFields = (await ${parseBodyVar}(${formVar}, request)).fields;`;
376
- requiresAsyncParameters = true;
627
+ middlewares = `
628
+ function(_injector) {
629
+ return [${middlewareItems.join(', ')}];
630
+ }
631
+ `;
377
632
  }
378
633
 
379
- let matcher = `_path.startsWith(${JSON.stringify(prefix)}) && (_match = _path.match(${regexVar}))`;
380
- if (!hasParameters) {
381
- matcher = `_path === ${JSON.stringify(path)}`;
382
- }
634
+ const parametersLoader = getRequestParserCodeForParameters(compiler, this.config.parser, parameters, {
635
+ module: routeConfig.module,
636
+ resolverForToken: routeConfig.resolverForToken,
637
+ resolverForParameterName: routeConfig.resolverForParameterName,
638
+ pathParameterNames: parsedRoute.pathParameterNames,
639
+ routeConfig
640
+ });
383
641
 
384
- let parameters = '() => []';
385
- if (setParameters.length) {
386
- parameters = `${requiresAsyncParameters ? 'async' : ''} function(_injector){
387
- const validationErrors = [];
388
- const bodyErrors = [];
389
- const parameters = {};
390
- ${setParametersFromPath}
391
- ${parseBodyLoading}
392
- ${parameterValidator.join('\n')}
393
- ${bodyValidationErrorHandling}
394
- ${setParameters.join('\n')}
395
- if (validationErrors.length) throw ValidationFailed.from(validationErrors);
396
- return [${parameterNames.join(',')}];
397
- }`;
642
+ let methodCheck = '';
643
+ if (routeConfig.httpMethods.length) {
644
+ methodCheck = '(' + routeConfig.httpMethods.map(v => {
645
+ return `_method === '${v.toUpperCase()}'`;
646
+ }).join(' || ') + ') && ';
398
647
  }
399
648
 
649
+ const routeConfigVar = compiler.reserveVariable('routeConfigVar', routeConfig);
400
650
  return `
401
- //=> ${path}
402
- if (_method === '${routeConfig.httpMethod.toLowerCase()}' && ${matcher}) {
403
- return {routeConfig: ${routeConfigVar}, parameters: ${parameters}};
651
+ //=> ${routeConfig.httpMethods.join(',')} ${path}
652
+ if (${methodCheck}${matcher}) {
653
+ return {routeConfig: ${routeConfigVar}, parameters: ${parametersLoader}, uploadedFiles: uploadedFiles, middlewares: ${middlewares}};
404
654
  }
405
655
  `;
406
656
  }
@@ -415,11 +665,11 @@ export class Router {
415
665
 
416
666
  const modify: string[] = [];
417
667
  for (const parameter of parsedRoute.getParameters()) {
418
- if (parameter.query) {
419
- const queryPath = parameter.typePath === undefined ? parameter.property.name : parameter.typePath;
668
+ if (parameter.query || parameter.queries) {
669
+ const queryPath = parameter.typePath === undefined && parameter.query ? parameter.parameter.name : parameter.typePath || '';
420
670
 
421
- if (parameter.property.type === 'class') {
422
- for (const property of parameter.property.getResolvedClassSchema().getProperties()) {
671
+ if (parameter.parameter.type.kind === ReflectionKind.class || parameter.parameter.type.kind === ReflectionKind.objectLiteral) {
672
+ for (const property of ReflectionClass.from(parameter.parameter.type).getProperties()) {
423
673
  const accessor = `parameters.${parameter.getName()}?.${property.name}`;
424
674
  const thisPath = queryPath ? queryPath + '.' + property.name : property.name;
425
675
  modify.push(`${accessor} !== undefined && query.push(${JSON.stringify(dotToUrlPath(thisPath))} + '=' + encodeURIComponent(${accessor}))`);
@@ -442,49 +692,34 @@ export class Router {
442
692
  }
443
693
 
444
694
  public addRoute(routeConfig: RouteConfig) {
445
- this.routes.push(routeConfig);
446
- this.fn = undefined;
695
+ this.registry.addRoute(routeConfig);
447
696
  }
448
697
 
449
- public addRouteForController(controller: ClassType) {
450
- const data = httpClass._fetch(controller);
451
- if (!data) return;
452
-
453
- for (const action of data.getActions()) {
454
- const routeConfig = new RouteConfig(action.name, action.httpMethod, action.path, {
455
- controller,
456
- methodName: action.methodName
457
- });
458
- routeConfig.parameterRegularExpressions = action.parameterRegularExpressions;
459
- routeConfig.throws = action.throws;
460
- routeConfig.description = action.description;
461
- routeConfig.category = action.category;
462
- routeConfig.groups = action.groups;
463
- routeConfig.data = new Map(action.data);
464
- routeConfig.baseUrl = data.baseUrl;
465
- routeConfig.parameters = { ...action.parameters };
466
- this.addRoute(routeConfig);
467
- }
698
+ public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
699
+ this.registry.addRouteForController(controller, module);
468
700
  }
469
701
 
470
- protected build(): any {
702
+ protected build(): (request: HttpRequest) => ResolvedController | undefined {
703
+ this.buildId = this.registry.getBuildId();
471
704
  const compiler = new CompilerContext;
472
- compiler.context.set('_match', null);
473
- compiler.context.set('ValidationFailed', ValidationFailed);
474
- compiler.context.set('parseQueryString', querystring.parse);
705
+ compiler.context.set('ValidationError', ValidationError);
706
+ compiler.context.set('qs', qs);
475
707
 
476
708
  const code: string[] = [];
477
709
 
478
- for (const route of this.routes) {
710
+ for (const route of this.getRoutes()) {
479
711
  code.push(this.getRouteCode(compiler, route));
480
712
  }
481
713
 
482
714
  return compiler.build(`
483
- const _method = request.getMethod().toLowerCase();
484
- const _url = request.getUrl();
715
+ let _match;
716
+ const _method = request.method || 'GET';
717
+ const _url = request.url || '/';
718
+ const _headers = request.headers || {};
485
719
  const _qPosition = _url.indexOf('?');
720
+ let uploadedFiles = {};
486
721
  const _path = _qPosition === -1 ? _url : _url.substr(0, _qPosition);
487
- const _query = _qPosition === -1 ? {} : parseQueryString(_url.substr(_qPosition + 1));
722
+ const _query = _qPosition === -1 ? {} : qs.parse(_url.substr(_qPosition + 1));
488
723
  ${code.join('\n')}
489
724
  `, 'request') as any;
490
725
  }
@@ -493,7 +728,7 @@ export class Router {
493
728
  const compiler = new CompilerContext;
494
729
  const code: string[] = [];
495
730
 
496
- for (const route of this.routes) {
731
+ for (const route of this.getRoutes()) {
497
732
  code.push(this.getRouteUrlResolveCode(compiler, route));
498
733
  }
499
734
 
@@ -514,21 +749,15 @@ export class Router {
514
749
  }
515
750
 
516
751
  public resolveRequest(request: HttpRequest): ResolvedController | undefined {
517
- if (!this.fn) {
752
+ if (!this.fn || this.buildId !== this.registry.getBuildId()) {
518
753
  this.fn = this.build();
519
754
  }
520
755
 
521
- return this.fn!(request);
756
+ return this.fn(request);
522
757
  }
523
758
 
524
759
  public resolve(method: string, url: string): ResolvedController | undefined {
525
- return this.resolveRequest({
526
- getUrl() {
527
- return url;
528
- },
529
- getMethod() {
530
- return method;
531
- },
532
- } as any);
760
+ method = method.toUpperCase();
761
+ return this.resolveRequest({ url, method } as any);
533
762
  }
534
763
  }