@deepkit/http 1.0.1-alpha.71 → 1.0.1-alpha.72

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 (121) hide show
  1. package/dist/cjs/src/decorator.d.ts +8 -6
  2. package/dist/cjs/src/decorator.js +49 -12
  3. package/dist/cjs/src/decorator.js.map +1 -1
  4. package/dist/cjs/src/filter.d.ts +3 -3
  5. package/dist/cjs/src/filter.js +3 -3
  6. package/dist/cjs/src/filter.js.map +1 -1
  7. package/dist/cjs/src/http.d.ts +49 -11
  8. package/dist/cjs/src/http.js +129 -38
  9. package/dist/cjs/src/http.js.map +1 -1
  10. package/dist/cjs/src/kernel.d.ts +3 -3
  11. package/dist/cjs/src/kernel.js +1 -1
  12. package/dist/cjs/src/kernel.js.map +1 -1
  13. package/dist/cjs/src/model.d.ts +11 -3
  14. package/dist/cjs/src/model.js +58 -26
  15. package/dist/cjs/src/model.js.map +1 -1
  16. package/dist/cjs/src/module.js +5 -3
  17. package/dist/cjs/src/module.js.map +1 -1
  18. package/dist/cjs/src/router.d.ts +84 -11
  19. package/dist/cjs/src/router.js +280 -94
  20. package/dist/cjs/src/router.js.map +1 -1
  21. package/dist/cjs/src/static-serving.js +15 -8
  22. package/dist/cjs/src/static-serving.js.map +1 -1
  23. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  24. package/dist/esm/src/decorator.d.ts +8 -6
  25. package/dist/esm/src/decorator.js +47 -11
  26. package/dist/esm/src/decorator.js.map +1 -1
  27. package/dist/esm/src/filter.d.ts +3 -3
  28. package/dist/esm/src/filter.js +4 -4
  29. package/dist/esm/src/filter.js.map +1 -1
  30. package/dist/esm/src/http.d.ts +49 -11
  31. package/dist/esm/src/http.js +102 -24
  32. package/dist/esm/src/http.js.map +1 -1
  33. package/dist/esm/src/kernel.d.ts +3 -3
  34. package/dist/esm/src/kernel.js +2 -2
  35. package/dist/esm/src/kernel.js.map +1 -1
  36. package/dist/esm/src/model.d.ts +11 -3
  37. package/dist/esm/src/model.js +47 -15
  38. package/dist/esm/src/model.js.map +1 -1
  39. package/dist/esm/src/module.js +5 -3
  40. package/dist/esm/src/module.js.map +1 -1
  41. package/dist/esm/src/router.d.ts +84 -11
  42. package/dist/esm/src/router.js +278 -94
  43. package/dist/esm/src/router.js.map +1 -1
  44. package/dist/esm/src/static-serving.js +16 -9
  45. package/dist/esm/src/static-serving.js.map +1 -1
  46. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  47. package/package.json +17 -14
  48. package/src/decorator.ts +9 -6
  49. package/src/filter.ts +4 -4
  50. package/src/http.ts +88 -15
  51. package/src/kernel.ts +2 -2
  52. package/src/model.ts +35 -6
  53. package/src/module.ts +5 -3
  54. package/src/router.ts +286 -73
  55. package/src/static-serving.ts +6 -3
  56. package/coverage/clover.xml +0 -1086
  57. package/coverage/coverage-final.json +0 -12
  58. package/coverage/lcov-report/base.css +0 -224
  59. package/coverage/lcov-report/block-navigation.js +0 -79
  60. package/coverage/lcov-report/favicon.png +0 -0
  61. package/coverage/lcov-report/index.html +0 -126
  62. package/coverage/lcov-report/prettify.css +0 -1
  63. package/coverage/lcov-report/prettify.js +0 -2
  64. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  65. package/coverage/lcov-report/sorter.js +0 -170
  66. package/coverage/lcov-report/src/controllers.ts.html +0 -113
  67. package/coverage/lcov-report/src/decorator.ts.html +0 -1280
  68. package/coverage/lcov-report/src/filter.ts.html +0 -734
  69. package/coverage/lcov-report/src/http.ts.html +0 -2192
  70. package/coverage/lcov-report/src/index.html +0 -246
  71. package/coverage/lcov-report/src/kernel.ts.html +0 -260
  72. package/coverage/lcov-report/src/middleware.ts.html +0 -428
  73. package/coverage/lcov-report/src/model.ts.html +0 -937
  74. package/coverage/lcov-report/src/module.config.ts.html +0 -89
  75. package/coverage/lcov-report/src/module.ts.html +0 -245
  76. package/coverage/lcov-report/src/router.ts.html +0 -2318
  77. package/coverage/lcov-report/tests/index.html +0 -111
  78. package/coverage/lcov-report/tests/utils.ts.html +0 -176
  79. package/coverage/lcov.info +0 -2225
  80. package/dist/cjs/tests/filter.spec.d.ts +0 -1
  81. package/dist/cjs/tests/filter.spec.js +0 -251
  82. package/dist/cjs/tests/filter.spec.js.map +0 -1
  83. package/dist/cjs/tests/middleware.spec.d.ts +0 -1
  84. package/dist/cjs/tests/middleware.spec.js +0 -471
  85. package/dist/cjs/tests/middleware.spec.js.map +0 -1
  86. package/dist/cjs/tests/model.spec.d.ts +0 -1
  87. package/dist/cjs/tests/model.spec.js +0 -23
  88. package/dist/cjs/tests/model.spec.js.map +0 -1
  89. package/dist/cjs/tests/module.spec.d.ts +0 -1
  90. package/dist/cjs/tests/module.spec.js +0 -46
  91. package/dist/cjs/tests/module.spec.js.map +0 -1
  92. package/dist/cjs/tests/router.spec.d.ts +0 -1
  93. package/dist/cjs/tests/router.spec.js +0 -987
  94. package/dist/cjs/tests/router.spec.js.map +0 -1
  95. package/dist/cjs/tests/utils.d.ts +0 -8
  96. package/dist/cjs/tests/utils.js +0 -30
  97. package/dist/cjs/tests/utils.js.map +0 -1
  98. package/dist/esm/tests/filter.spec.d.ts +0 -1
  99. package/dist/esm/tests/filter.spec.js +0 -249
  100. package/dist/esm/tests/filter.spec.js.map +0 -1
  101. package/dist/esm/tests/middleware.spec.d.ts +0 -1
  102. package/dist/esm/tests/middleware.spec.js +0 -469
  103. package/dist/esm/tests/middleware.spec.js.map +0 -1
  104. package/dist/esm/tests/model.spec.d.ts +0 -1
  105. package/dist/esm/tests/model.spec.js +0 -21
  106. package/dist/esm/tests/model.spec.js.map +0 -1
  107. package/dist/esm/tests/module.spec.d.ts +0 -1
  108. package/dist/esm/tests/module.spec.js +0 -44
  109. package/dist/esm/tests/module.spec.js.map +0 -1
  110. package/dist/esm/tests/router.spec.d.ts +0 -1
  111. package/dist/esm/tests/router.spec.js +0 -985
  112. package/dist/esm/tests/router.spec.js.map +0 -1
  113. package/dist/esm/tests/utils.d.ts +0 -8
  114. package/dist/esm/tests/utils.js +0 -26
  115. package/dist/esm/tests/utils.js.map +0 -1
  116. package/tests/filter.spec.ts +0 -181
  117. package/tests/middleware.spec.ts +0 -440
  118. package/tests/model.spec.ts +0 -26
  119. package/tests/module.spec.ts +0 -32
  120. package/tests/router.spec.ts +0 -781
  121. package/tests/utils.ts +0 -32
package/src/router.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  getValidatorFunction,
17
17
  metaAnnotation,
18
18
  ReflectionClass,
19
+ ReflectionFunction,
19
20
  ReflectionKind,
20
21
  ReflectionParameter,
21
22
  SerializationOptions,
@@ -29,7 +30,7 @@ import {
29
30
  // @ts-ignore
30
31
  import formidable from 'formidable';
31
32
  import querystring from 'querystring';
32
- import { httpClass } from './decorator';
33
+ import { HttpAction, httpClass, HttpController, HttpDecorator } from './decorator';
33
34
  import { BodyValidationError, getRegExp, HttpRequest, HttpRequestQuery, HttpRequestResolvedParameters, ValidatedBody } from './model';
34
35
  import { InjectorContext, InjectorModule, TagRegistry } from '@deepkit/injector';
35
36
  import { Logger, LoggerInterface } from '@deepkit/logger';
@@ -39,6 +40,7 @@ import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware';
39
40
 
40
41
  //@ts-ignore
41
42
  import qs from 'qs';
43
+ import { isArray } from '@deepkit/core';
42
44
 
43
45
  export type RouteParameterResolverForInjector = ((injector: InjectorContext) => any[] | Promise<any[]>);
44
46
 
@@ -57,7 +59,7 @@ export class UploadedFile {
57
59
  size!: number;
58
60
 
59
61
  /**
60
- * The path this file is being written to.
62
+ * The local path this file is being written to. Will be deleted when request is handled.
61
63
  */
62
64
  path!: string;
63
65
 
@@ -83,15 +85,19 @@ export class UploadedFile {
83
85
  // hash!: string | 'sha1' | 'md5' | 'sha256' | null;
84
86
  }
85
87
 
86
- export interface RouteControllerAction {
88
+ export interface RouteFunctionControllerAction {
89
+ type: 'function';
87
90
  //if not set, the root module is used
88
91
  module?: InjectorModule<any>;
89
- controller: ClassType;
90
- methodName: string;
92
+ fn: (...args: any[]) => any;
91
93
  }
92
94
 
93
- function getRouterControllerActionName(action: RouteControllerAction): string {
94
- return `${getClassName(action.controller)}.${action.methodName}`;
95
+ export interface RouteClassControllerAction {
96
+ type: 'controller';
97
+ //if not set, the root module is used
98
+ module?: InjectorModule<any>;
99
+ controller: ClassType;
100
+ methodName: string;
95
101
  }
96
102
 
97
103
  export class RouteConfig {
@@ -115,7 +121,7 @@ export class RouteConfig {
115
121
 
116
122
  resolverForToken: Map<any, ClassType> = new Map();
117
123
 
118
- middlewares: { config: HttpMiddlewareConfig, module: InjectorModule<any> }[] = [];
124
+ middlewares: { config: HttpMiddlewareConfig, module?: InjectorModule<any> }[] = [];
119
125
 
120
126
  resolverForParameterName: Map<string, ClassType> = new Map();
121
127
 
@@ -128,11 +134,17 @@ export class RouteConfig {
128
134
  public readonly name: string,
129
135
  public readonly httpMethods: string[],
130
136
  public readonly path: string,
131
- public readonly action: RouteControllerAction,
137
+ public readonly action: RouteClassControllerAction | RouteFunctionControllerAction,
132
138
  public internal: boolean = false,
133
139
  ) {
134
140
  }
135
141
 
142
+ getReflectionFunction(): ReflectionFunction {
143
+ return this.action.type === 'controller' ?
144
+ ReflectionClass.from(this.action.controller).getMethod(this.action.methodName)
145
+ : ReflectionFunction.from(this.action.fn);
146
+ }
147
+
136
148
  getSchemaForResponse(statusCode: number): Type | undefined {
137
149
  if (!this.responses.length) return;
138
150
  for (const response of this.responses) {
@@ -195,7 +207,7 @@ class ParsedRouteParameter {
195
207
  getType(): Type {
196
208
  if (this.bodyValidation) {
197
209
  assertType(this.parameter.type, ReflectionKind.class);
198
- const valueType = findMember('value', this.parameter.type);
210
+ const valueType = findMember('value', this.parameter.type.types);
199
211
  if (!valueType || valueType.kind !== ReflectionKind.property) throw new Error(`No property value found at ${stringifyType(this.parameter.type)}`);
200
212
  return valueType.type as Type;
201
213
  }
@@ -231,16 +243,20 @@ function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, param
231
243
  const parameterNames: { [name: string]: number } = {};
232
244
  let path = routeConfig.getFullPath();
233
245
 
234
- const method = ReflectionClass.from(routeConfig.action.controller).getMethod(routeConfig.action.methodName);
246
+ const fn = routeConfig.getReflectionFunction();
235
247
 
236
248
  let argumentIndex = 0;
237
249
  path = path.replace(/:(\w+)/g, (a, name) => {
238
250
  parameterNames[name] = argumentIndex;
239
251
  argumentIndex++;
240
- const parameter = method.getParameterOrUndefined(name);
252
+ const parameter = fn.getParameterOrUndefined(name);
241
253
  if (parameter) {
242
254
  const regExp = getRegExp(parameter.type);
243
- if (regExp) return '(' + regExp + ')';
255
+ if (regExp instanceof RegExp) {
256
+ return '(' + regExp.source + ')';
257
+ } else if (regExp) {
258
+ return '(' + regExp + ')';
259
+ }
244
260
  }
245
261
  return String.raw`([^/]+)`;
246
262
  });
@@ -249,10 +265,9 @@ function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, param
249
265
  }
250
266
 
251
267
  export function parseRouteControllerAction(routeConfig: RouteConfig): ParsedRoute {
252
- const schema = ReflectionClass.from(routeConfig.action.controller);
253
268
  const parsedRoute = new ParsedRoute(routeConfig);
254
269
 
255
- const methodArgumentProperties = schema.getMethodParameters(routeConfig.action.methodName);
270
+ const methodArgumentProperties = routeConfig.getReflectionFunction().getParameters();
256
271
  const parsedPath = parseRoutePathToRegex(routeConfig);
257
272
  parsedRoute.regex = parsedPath.regex;
258
273
  parsedRoute.pathParameterNames = parsedPath.parameterNames;
@@ -302,11 +317,11 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
302
317
  const middlewareConfigs = middlewares.filter((v) => {
303
318
  if (!(v.config instanceof HttpMiddlewareConfig)) return false;
304
319
 
305
- if (v.config.controllers.length && !v.config.controllers.includes(routeConfig.action.controller)) {
320
+ if (v.config.controllers.length && routeConfig.action.type === 'controller' && !v.config.controllers.includes(routeConfig.action.controller)) {
306
321
  return false;
307
322
  }
308
323
 
309
- if (v.config.excludeControllers.length && v.config.excludeControllers.includes(routeConfig.action.controller)) {
324
+ if (v.config.excludeControllers.length && routeConfig.action.type === 'controller' && v.config.excludeControllers.includes(routeConfig.action.controller)) {
310
325
  return false;
311
326
  }
312
327
 
@@ -320,14 +335,20 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
320
335
  if (v.config.selfModule && v.module.id !== routeConfig.module?.id) return false;
321
336
 
322
337
  if (v.config.routeNames.length) {
338
+ let found: boolean = false;
323
339
  for (const name of v.config.routeNames) {
324
340
  if (name.includes('*')) {
325
341
  const regex = new RegExp('^' + name.replace(/\*/g, '.*') + '$');
326
- if (!regex.test(routeConfig.name)) return false;
327
- } else if (name !== routeConfig.name) {
328
- return false;
342
+ if (regex.test(routeConfig.name)) {
343
+ found = true;
344
+ break;
345
+ }
346
+ } else if (name === routeConfig.name) {
347
+ found = true;
348
+ break;
329
349
  }
330
350
  }
351
+ if (!found) return false;
331
352
  }
332
353
 
333
354
  if (v.config.excludeRouteNames.length) {
@@ -366,17 +387,236 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
366
387
  return middlewareConfigs;
367
388
  }
368
389
 
369
- export class Router {
370
- protected fn?: (request: HttpRequest) => ResolvedController | undefined;
371
- protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
390
+ export interface HttpRouterFunctionOptions {
391
+ path: string;
392
+ name?: string;
393
+ methods?: string[];
394
+ description?: string;
395
+ category?: string;
396
+ groups?: string[];
397
+
398
+ /**
399
+ * An arbitrary data container the user can use to store app specific settings/values.
400
+ */
401
+ data?: Record<any, any>;
402
+
403
+ baseUrl?: string;
404
+ middlewares?: (() => HttpMiddlewareConfig)[];
405
+
406
+ serializer?: Serializer;
407
+ serializationOptions?: SerializationOptions;
408
+
409
+ resolverForToken?: Map<any, ClassType>;
410
+ resolverForParameterName?: Map<string, ClassType>;
411
+
412
+ responses?: { statusCode: number, description: string, type?: Type }[];
413
+ }
414
+
415
+ function convertOptions(methods: string[], pathOrOptions: string | HttpRouterFunctionOptions, defaultOptions: Partial<HttpRouterFunctionOptions>): HttpRouterFunctionOptions {
416
+ const options = 'string' === typeof pathOrOptions ? { path: pathOrOptions } : pathOrOptions;
417
+ if (options.methods) return options;
418
+ return { ...options, methods };
419
+ }
420
+
421
+ export abstract class HttpRouterRegistryFunctionRegistrar {
422
+ protected defaultOptions: Partial<HttpRouterFunctionOptions> = {};
423
+
424
+ abstract addRoute(routeConfig: RouteConfig): void;
425
+
426
+ /**
427
+ * Returns a new registrar object with default options that apply to each registered route through this registrar.
428
+ *
429
+ * ```typescript
430
+ * const registry: HttpRouterRegistry = ...;
431
+ *
432
+ * const secretRegistry = registry.forOptions({groups: ['secret']});
433
+ *
434
+ * secretRegistry.get('/admin/groups', () => {
435
+ * });
436
+ *
437
+ * secretRegistry.get('/admin/users', () => {
438
+ * });
439
+ * ```
440
+ */
441
+ forOptions(options: Partial<HttpRouterFunctionOptions>): HttpRouterRegistryFunctionRegistrar {
442
+ const that = this;
443
+ return new class extends HttpRouterRegistryFunctionRegistrar {
444
+ defaultOptions = options;
445
+
446
+ addRoute(routeConfig: RouteConfig) {
447
+ that.addRoute(routeConfig);
448
+ }
449
+ };
450
+ }
451
+
452
+ public any(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
453
+ this.register(convertOptions([], pathOrOptions, this.defaultOptions), callback);
454
+ }
455
+
456
+ public add(decorator: HttpDecorator, callback: (...args: any[]) => any) {
457
+ const data = decorator(Object, '_');
458
+ const action = isArray(data) ? data.find(v => v instanceof HttpAction) : undefined;
459
+ if (!action) throw new Error('No HttpAction available');
460
+
461
+ const fn = ReflectionFunction.from(callback);
462
+ const routeConfig = createRouteConfigFromHttpAction({
463
+ type: 'function',
464
+ fn: callback,
465
+ }, action);
466
+ routeConfig.returnType = fn.getReturnType();
467
+ this.addRoute(routeConfig);
468
+ }
469
+
470
+ public get(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
471
+ this.register(convertOptions(['GET'], pathOrOptions, this.defaultOptions), callback);
472
+ }
473
+
474
+ public post(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
475
+ this.register(convertOptions(['POST'], pathOrOptions, this.defaultOptions), callback);
476
+ }
477
+
478
+ public put(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
479
+ this.register(convertOptions(['PUT'], pathOrOptions, this.defaultOptions), callback);
480
+ }
481
+
482
+ public patch(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
483
+ this.register(convertOptions(['PATCH'], pathOrOptions, this.defaultOptions), callback);
484
+ }
485
+
486
+ public delete(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
487
+ this.register(convertOptions(['DELETE'], pathOrOptions, this.defaultOptions), callback);
488
+ }
489
+
490
+ public options(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
491
+ this.register(convertOptions(['OPTIONS'], pathOrOptions, this.defaultOptions), callback);
492
+ }
493
+
494
+ public trace(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
495
+ this.register(convertOptions(['TRACE'], pathOrOptions, this.defaultOptions), callback);
496
+ }
497
+
498
+ public head(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
499
+ this.register(convertOptions(['HEAD'], pathOrOptions, this.defaultOptions), callback);
500
+ }
501
+
502
+ private register(options: HttpRouterFunctionOptions, callback: (...args: any[]) => any, module?: InjectorModule<any>) {
503
+ const fn = ReflectionFunction.from(callback);
504
+
505
+ const routeConfig = new RouteConfig(options.name || '', options.methods || [], options.path, {
506
+ type: 'function',
507
+ fn: callback,
508
+ });
509
+ routeConfig.module = module;
510
+ if (options.responses) routeConfig.responses = options.responses;
511
+ if (options.description) routeConfig.description = options.description;
512
+ if (options.category) routeConfig.category = options.category;
513
+ if (options.groups) routeConfig.groups = options.groups;
514
+ if (options.data) routeConfig.data = new Map(Object.entries(options.data));
515
+ if (options.baseUrl) routeConfig.baseUrl = options.baseUrl;
516
+ if (options.middlewares) {
517
+ routeConfig.middlewares = options.middlewares.map(v => {
518
+ return { config: v(), module };
519
+ });
520
+ }
521
+
522
+ if (options.resolverForToken) {
523
+ for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
524
+ }
525
+
526
+ if (options.resolverForToken) {
527
+ for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
528
+ }
372
529
 
530
+ if (options.resolverForParameterName) {
531
+ for (const item of options.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
532
+ }
533
+
534
+ routeConfig.serializer = options.serializer;
535
+ routeConfig.serializationOptions = options.serializationOptions;
536
+
537
+ routeConfig.returnType = fn.getReturnType();
538
+ this.addRoute(routeConfig);
539
+ }
540
+ }
541
+
542
+ function createRouteConfigFromHttpAction(routeAction: RouteClassControllerAction | RouteFunctionControllerAction, action: HttpAction, module?: InjectorModule<any>, controller?: HttpController) {
543
+ const routeConfig = new RouteConfig(action.name, action.httpMethods, action.path, routeAction);
544
+ routeConfig.responses = action.responses;
545
+ routeConfig.description = action.description;
546
+ routeConfig.category = action.category;
547
+ routeConfig.groups = action.groups;
548
+ routeConfig.data = new Map(action.data);
549
+ if (controller) {
550
+ routeConfig.baseUrl = controller.baseUrl;
551
+
552
+ routeConfig.middlewares = controller.middlewares.map(v => {
553
+ return { config: v(), module };
554
+ });
555
+ routeConfig.resolverForToken = new Map(controller.resolverForToken);
556
+ routeConfig.resolverForParameterName = new Map(controller.resolverForParameterName);
557
+ }
558
+
559
+ routeConfig.middlewares.push(...action.middlewares.map(v => {
560
+ return { config: v(), module };
561
+ }));
562
+
563
+ for (const item of action.resolverForToken) routeConfig.resolverForToken.set(...item);
564
+
565
+ for (const item of action.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
566
+
567
+ routeConfig.serializer = action.serializer;
568
+ routeConfig.serializationOptions = action.serializationOptions;
569
+ return routeConfig;
570
+ }
571
+
572
+ export class HttpRouterRegistry extends HttpRouterRegistryFunctionRegistrar {
373
573
  protected routes: RouteConfig[] = [];
574
+ private buildId: number = 1;
575
+
576
+ public getBuildId(): number {
577
+ return this.buildId;
578
+ }
374
579
 
375
- private parseBody(req: HttpRequest, files: { [name: string]: UploadedFile }) {
580
+ public getRoutes(): RouteConfig[] {
581
+ return this.routes;
582
+ }
583
+
584
+ public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
585
+ const controllerData = httpClass._fetch(controller);
586
+ if (!controllerData) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
587
+ const schema = ReflectionClass.from(controller);
588
+
589
+ for (const action of controllerData.getActions()) {
590
+ const routeAction: RouteClassControllerAction = {
591
+ type: 'controller',
592
+ controller,
593
+ module,
594
+ methodName: action.methodName
595
+ };
596
+ const routeConfig = createRouteConfigFromHttpAction(routeAction, action, module, controllerData);
597
+
598
+ routeConfig.module = module;
599
+
600
+ if (schema.hasMethod(action.methodName)) routeConfig.returnType = schema.getMethod(action.methodName).getReturnType();
601
+ this.addRoute(routeConfig);
602
+ }
603
+ }
604
+
605
+ public addRoute(routeConfig: RouteConfig) {
606
+ this.routes.push(routeConfig);
607
+ this.buildId++;
608
+ }
609
+ }
610
+
611
+ export class HttpRouter {
612
+ protected fn?: (request: HttpRequest) => ResolvedController | undefined;
613
+ protected buildId: number = 0;
614
+ protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
615
+
616
+ private parseBody(req: HttpRequest, foundFiles: { [name: string]: UploadedFile }) {
376
617
  const form = formidable({
377
618
  multiples: true,
378
619
  hash: 'sha1',
379
- enabledPlugins: ['octetstream', 'querystring', 'json'],
380
620
  });
381
621
  return asyncOperation((resolve, reject) => {
382
622
  if (req.body) {
@@ -386,7 +626,17 @@ export class Router {
386
626
  if (err) {
387
627
  reject(err);
388
628
  } else {
389
- const body = req.body = { ...fields, ...files };
629
+ for (const [name, file] of Object.entries(files) as any) {
630
+ if (file.size === 0) continue;
631
+ foundFiles[name] = {
632
+ size: file.size,
633
+ path: file.filepath,
634
+ name: file.originalFilename,
635
+ type: file.mimetype,
636
+ lastModifiedDate: file.lastModifiedDate,
637
+ }
638
+ }
639
+ const body = req.body = { ...fields, ...foundFiles };
390
640
  resolve(body);
391
641
  }
392
642
  });
@@ -398,6 +648,7 @@ export class Router {
398
648
  private logger: LoggerInterface,
399
649
  tagRegistry: TagRegistry,
400
650
  private middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry,
651
+ private registry: HttpRouterRegistry = new HttpRouterRegistry,
401
652
  ) {
402
653
  for (const controller of controllers.controllers) {
403
654
  this.addRouteForController(controller.controller, controller.module);
@@ -405,7 +656,7 @@ export class Router {
405
656
  }
406
657
 
407
658
  getRoutes(): RouteConfig[] {
408
- return this.routes;
659
+ return this.registry.getRoutes();
409
660
  }
410
661
 
411
662
  static forControllers(
@@ -413,7 +664,7 @@ export class Router {
413
664
  tagRegistry: TagRegistry = new TagRegistry(),
414
665
  middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry(),
415
666
  module: InjectorModule<any> = new InjectorModule()
416
- ): Router {
667
+ ): HttpRouter {
417
668
  return new this(new HttpControllers(controllers.map(v => {
418
669
  return isClass(v) ? { controller: v, module } : v;
419
670
  })), new Logger([], []), tagRegistry, middlewareRegistry);
@@ -516,7 +767,7 @@ export class Router {
516
767
  ${parameterResolverFoundVar} = true;
517
768
  parameters.${parameter.parameter.name} = await ${instance}.resolve({
518
769
  token: ${injectorTokenVar},
519
- routeConfig: ${routeConfigVar},
770
+ route: ${routeConfigVar},
520
771
  request: request,
521
772
  name: ${JSON.stringify(parameter.parameter.name)},
522
773
  value: parameters.${parameter.parameter.name},
@@ -640,60 +891,22 @@ export class Router {
640
891
  }
641
892
 
642
893
  public addRoute(routeConfig: RouteConfig) {
643
- this.routes.push(routeConfig);
644
- this.fn = undefined;
894
+ this.registry.addRoute(routeConfig);
645
895
  }
646
896
 
647
897
  public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
648
- const data = httpClass._fetch(controller);
649
- if (!data) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
650
- const schema = ReflectionClass.from(controller);
651
-
652
- for (const action of data.getActions()) {
653
- const routeConfig = new RouteConfig(action.name, action.httpMethods, action.path, {
654
- controller,
655
- module,
656
- methodName: action.methodName
657
- });
658
- routeConfig.module = module;
659
- routeConfig.responses = action.responses;
660
- routeConfig.description = action.description;
661
- routeConfig.category = action.category;
662
- routeConfig.groups = action.groups;
663
- routeConfig.data = new Map(action.data);
664
- routeConfig.baseUrl = data.baseUrl;
665
-
666
- routeConfig.middlewares = data.middlewares.map(v => {
667
- return { config: v(), module };
668
- });
669
- routeConfig.middlewares.push(...action.middlewares.map(v => {
670
- return { config: v(), module };
671
- }));
672
-
673
- for (const item of action.resolverForToken) routeConfig.resolverForToken.set(...item);
674
-
675
- routeConfig.resolverForToken = new Map(data.resolverForToken);
676
- for (const item of action.resolverForToken) routeConfig.resolverForToken.set(...item);
677
-
678
- routeConfig.resolverForParameterName = new Map(data.resolverForParameterName);
679
- for (const item of action.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
680
-
681
- routeConfig.serializationOptions = action.serializationOptions;
682
- routeConfig.serializer = action.serializer;
683
- routeConfig.serializer = action.serializer;
684
- if (schema.hasMethod(action.methodName)) routeConfig.returnType = schema.getMethod(action.methodName).getReturnType();
685
- this.addRoute(routeConfig);
686
- }
898
+ this.registry.addRouteForController(controller, module);
687
899
  }
688
900
 
689
901
  protected build(): (request: HttpRequest) => ResolvedController | undefined {
902
+ this.buildId = this.registry.getBuildId();
690
903
  const compiler = new CompilerContext;
691
904
  compiler.context.set('ValidationError', ValidationError);
692
905
  compiler.context.set('qs', qs);
693
906
 
694
907
  const code: string[] = [];
695
908
 
696
- for (const route of this.routes) {
909
+ for (const route of this.getRoutes()) {
697
910
  code.push(this.getRouteCode(compiler, route));
698
911
  }
699
912
 
@@ -713,7 +926,7 @@ export class Router {
713
926
  const compiler = new CompilerContext;
714
927
  const code: string[] = [];
715
928
 
716
- for (const route of this.routes) {
929
+ for (const route of this.getRoutes()) {
717
930
  code.push(this.getRouteUrlResolveCode(compiler, route));
718
931
  }
719
932
 
@@ -734,7 +947,7 @@ export class Router {
734
947
  }
735
948
 
736
949
  public resolveRequest(request: HttpRequest): ResolvedController | undefined {
737
- if (!this.fn) {
950
+ if (!this.fn || this.buildId !== this.registry.getBuildId()) {
738
951
  this.fn = this.build();
739
952
  }
740
953
 
@@ -18,7 +18,7 @@ import { ClassType, urlJoin } from '@deepkit/core';
18
18
  import { HttpRequest, HttpResponse } from './model';
19
19
  import send from 'send';
20
20
  import { eventDispatcher } from '@deepkit/event';
21
- import { RouteConfig, Router } from './router';
21
+ import { RouteConfig, HttpRouter } from './router';
22
22
 
23
23
  export function serveStaticListener(module: AppModule<any>, path: string, localPath: string = path): ClassType {
24
24
  class HttpRequestStaticServingListener {
@@ -45,6 +45,7 @@ export function serveStaticListener(module: AppModule<any>, path: string, localP
45
45
  if (stat && stat.isFile()) {
46
46
  event.routeFound(
47
47
  new RouteConfig('static', ['GET'], event.url, {
48
+ type: 'controller',
48
49
  controller: HttpRequestStaticServingListener,
49
50
  module,
50
51
  methodName: 'serve'
@@ -128,21 +129,23 @@ export function registerStaticHttpController(module: AppModule<any>, options: St
128
129
  const path = normalizeDirectory(options.path);
129
130
 
130
131
  const route1 = new RouteConfig('static', ['GET'], path, {
132
+ type: 'controller',
131
133
  controller: StaticController,
132
134
  module,
133
135
  methodName: 'serveIndex'
134
136
  });
135
137
  route1.groups = groups;
136
- module.setupGlobalProvider<Router>().addRoute(route1);
138
+ module.setupGlobalProvider<HttpRouter>().addRoute(route1);
137
139
 
138
140
  if (path !== '/') {
139
141
  const route2 = new RouteConfig('static', ['GET'], path.slice(0, -1), {
142
+ type: 'controller',
140
143
  controller: StaticController,
141
144
  module,
142
145
  methodName: 'serveIndex'
143
146
  });
144
147
  route2.groups = groups;
145
- module.setupGlobalProvider<Router>().addRoute(route2);
148
+ module.setupGlobalProvider<HttpRouter>().addRoute(route2);
146
149
  }
147
150
 
148
151
  module.addProvider(StaticController);