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

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 +125 -39
  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 +59 -27
  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 +292 -96
  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 +112 -39
  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 +51 -19
  38. package/dist/esm/src/model.js.map +1 -1
  39. package/dist/esm/src/module.js +6 -4
  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 +291 -97
  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 +83 -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 +298 -77
  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
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * You should have received a copy of the MIT License along with this program.
9
9
  */
10
- import { asyncOperation, ClassType, CompilerContext, getClassName, isClass, isObject, urlJoin } from '@deepkit/core';
10
+ import { asyncOperation, ClassType, CompilerContext, getClassName, isArray, isClass, isObject, urlJoin } from '@deepkit/core';
11
11
  import {
12
12
  assertType,
13
13
  entity,
@@ -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,
@@ -28,8 +29,7 @@ import {
28
29
  } from '@deepkit/type';
29
30
  // @ts-ignore
30
31
  import formidable from 'formidable';
31
- import querystring from 'querystring';
32
- import { httpClass } from './decorator';
32
+ import { HttpAction, httpClass, HttpController, HttpDecorator } from './decorator';
33
33
  import { BodyValidationError, getRegExp, HttpRequest, HttpRequestQuery, HttpRequestResolvedParameters, ValidatedBody } from './model';
34
34
  import { InjectorContext, InjectorModule, TagRegistry } from '@deepkit/injector';
35
35
  import { Logger, LoggerInterface } from '@deepkit/logger';
@@ -39,6 +39,7 @@ import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware';
39
39
 
40
40
  //@ts-ignore
41
41
  import qs from 'qs';
42
+ import { HtmlResponse, JSONResponse, Response } from './http.js';
42
43
 
43
44
  export type RouteParameterResolverForInjector = ((injector: InjectorContext) => any[] | Promise<any[]>);
44
45
 
@@ -57,7 +58,7 @@ export class UploadedFile {
57
58
  size!: number;
58
59
 
59
60
  /**
60
- * The path this file is being written to.
61
+ * The local path this file is being written to. Will be deleted when request is handled.
61
62
  */
62
63
  path!: string;
63
64
 
@@ -83,15 +84,19 @@ export class UploadedFile {
83
84
  // hash!: string | 'sha1' | 'md5' | 'sha256' | null;
84
85
  }
85
86
 
86
- export interface RouteControllerAction {
87
+ export interface RouteFunctionControllerAction {
88
+ type: 'function';
87
89
  //if not set, the root module is used
88
90
  module?: InjectorModule<any>;
89
- controller: ClassType;
90
- methodName: string;
91
+ fn: (...args: any[]) => any;
91
92
  }
92
93
 
93
- function getRouterControllerActionName(action: RouteControllerAction): string {
94
- return `${getClassName(action.controller)}.${action.methodName}`;
94
+ export interface RouteClassControllerAction {
95
+ type: 'controller';
96
+ //if not set, the root module is used
97
+ module?: InjectorModule<any>;
98
+ controller: ClassType;
99
+ methodName: string;
95
100
  }
96
101
 
97
102
  export class RouteConfig {
@@ -115,7 +120,7 @@ export class RouteConfig {
115
120
 
116
121
  resolverForToken: Map<any, ClassType> = new Map();
117
122
 
118
- middlewares: { config: HttpMiddlewareConfig, module: InjectorModule<any> }[] = [];
123
+ middlewares: { config: HttpMiddlewareConfig, module?: InjectorModule<any> }[] = [];
119
124
 
120
125
  resolverForParameterName: Map<string, ClassType> = new Map();
121
126
 
@@ -128,11 +133,17 @@ export class RouteConfig {
128
133
  public readonly name: string,
129
134
  public readonly httpMethods: string[],
130
135
  public readonly path: string,
131
- public readonly action: RouteControllerAction,
136
+ public readonly action: RouteClassControllerAction | RouteFunctionControllerAction,
132
137
  public internal: boolean = false,
133
138
  ) {
134
139
  }
135
140
 
141
+ getReflectionFunction(): ReflectionFunction {
142
+ return this.action.type === 'controller' ?
143
+ ReflectionClass.from(this.action.controller).getMethod(this.action.methodName)
144
+ : ReflectionFunction.from(this.action.fn);
145
+ }
146
+
136
147
  getSchemaForResponse(statusCode: number): Type | undefined {
137
148
  if (!this.responses.length) return;
138
149
  for (const response of this.responses) {
@@ -195,7 +206,7 @@ class ParsedRouteParameter {
195
206
  getType(): Type {
196
207
  if (this.bodyValidation) {
197
208
  assertType(this.parameter.type, ReflectionKind.class);
198
- const valueType = findMember('value', this.parameter.type);
209
+ const valueType = findMember('value', this.parameter.type.types);
199
210
  if (!valueType || valueType.kind !== ReflectionKind.property) throw new Error(`No property value found at ${stringifyType(this.parameter.type)}`);
200
211
  return valueType.type as Type;
201
212
  }
@@ -231,16 +242,20 @@ function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, param
231
242
  const parameterNames: { [name: string]: number } = {};
232
243
  let path = routeConfig.getFullPath();
233
244
 
234
- const method = ReflectionClass.from(routeConfig.action.controller).getMethod(routeConfig.action.methodName);
245
+ const fn = routeConfig.getReflectionFunction();
235
246
 
236
247
  let argumentIndex = 0;
237
248
  path = path.replace(/:(\w+)/g, (a, name) => {
238
249
  parameterNames[name] = argumentIndex;
239
250
  argumentIndex++;
240
- const parameter = method.getParameterOrUndefined(name);
251
+ const parameter = fn.getParameterOrUndefined(name);
241
252
  if (parameter) {
242
253
  const regExp = getRegExp(parameter.type);
243
- if (regExp) return '(' + regExp + ')';
254
+ if (regExp instanceof RegExp) {
255
+ return '(' + regExp.source + ')';
256
+ } else if (regExp) {
257
+ return '(' + regExp + ')';
258
+ }
244
259
  }
245
260
  return String.raw`([^/]+)`;
246
261
  });
@@ -249,10 +264,9 @@ function parseRoutePathToRegex(routeConfig: RouteConfig): { regex: string, param
249
264
  }
250
265
 
251
266
  export function parseRouteControllerAction(routeConfig: RouteConfig): ParsedRoute {
252
- const schema = ReflectionClass.from(routeConfig.action.controller);
253
267
  const parsedRoute = new ParsedRoute(routeConfig);
254
268
 
255
- const methodArgumentProperties = schema.getMethodParameters(routeConfig.action.methodName);
269
+ const methodArgumentProperties = routeConfig.getReflectionFunction().getParameters();
256
270
  const parsedPath = parseRoutePathToRegex(routeConfig);
257
271
  parsedRoute.regex = parsedPath.regex;
258
272
  parsedRoute.pathParameterNames = parsedPath.parameterNames;
@@ -302,11 +316,11 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
302
316
  const middlewareConfigs = middlewares.filter((v) => {
303
317
  if (!(v.config instanceof HttpMiddlewareConfig)) return false;
304
318
 
305
- if (v.config.controllers.length && !v.config.controllers.includes(routeConfig.action.controller)) {
319
+ if (v.config.controllers.length && routeConfig.action.type === 'controller' && !v.config.controllers.includes(routeConfig.action.controller)) {
306
320
  return false;
307
321
  }
308
322
 
309
- if (v.config.excludeControllers.length && v.config.excludeControllers.includes(routeConfig.action.controller)) {
323
+ if (v.config.excludeControllers.length && routeConfig.action.type === 'controller' && v.config.excludeControllers.includes(routeConfig.action.controller)) {
310
324
  return false;
311
325
  }
312
326
 
@@ -320,14 +334,20 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
320
334
  if (v.config.selfModule && v.module.id !== routeConfig.module?.id) return false;
321
335
 
322
336
  if (v.config.routeNames.length) {
337
+ let found: boolean = false;
323
338
  for (const name of v.config.routeNames) {
324
339
  if (name.includes('*')) {
325
340
  const regex = new RegExp('^' + name.replace(/\*/g, '.*') + '$');
326
- if (!regex.test(routeConfig.name)) return false;
327
- } else if (name !== routeConfig.name) {
328
- return false;
341
+ if (regex.test(routeConfig.name)) {
342
+ found = true;
343
+ break;
344
+ }
345
+ } else if (name === routeConfig.name) {
346
+ found = true;
347
+ break;
329
348
  }
330
349
  }
350
+ if (!found) return false;
331
351
  }
332
352
 
333
353
  if (v.config.excludeRouteNames.length) {
@@ -366,17 +386,244 @@ function filterMiddlewaresForRoute(middlewareRawConfigs: MiddlewareRegistryEntry
366
386
  return middlewareConfigs;
367
387
  }
368
388
 
369
- export class Router {
370
- protected fn?: (request: HttpRequest) => ResolvedController | undefined;
371
- protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
389
+ export interface HttpRouterFunctionOptions {
390
+ path: string;
391
+ name?: string;
392
+ methods?: string[];
393
+ description?: string;
394
+ category?: string;
395
+ groups?: string[];
396
+
397
+ /**
398
+ * An arbitrary data container the user can use to store app specific settings/values.
399
+ */
400
+ data?: Record<any, any>;
401
+
402
+ baseUrl?: string;
403
+ middlewares?: (() => HttpMiddlewareConfig)[];
404
+
405
+ serializer?: Serializer;
406
+ serializationOptions?: SerializationOptions;
407
+
408
+ resolverForToken?: Map<any, ClassType>;
409
+ resolverForParameterName?: Map<string, ClassType>;
410
+
411
+ responses?: { statusCode: number, description: string, type?: Type }[];
412
+ }
413
+
414
+ function convertOptions(methods: string[], pathOrOptions: string | HttpRouterFunctionOptions, defaultOptions: Partial<HttpRouterFunctionOptions>): HttpRouterFunctionOptions {
415
+ const options = 'string' === typeof pathOrOptions ? { path: pathOrOptions } : pathOrOptions;
416
+ if (options.methods) return options;
417
+ return { ...options, methods };
418
+ }
419
+
420
+ /**
421
+ * Annotated types like HTMLResponse/JSONResponse are not used for serialization.
422
+ */
423
+ function filterValidReturnType(type: Type): Type | undefined {
424
+ if (type.kind === ReflectionKind.class && (type.classType === HtmlResponse || type.classType === JSONResponse || type.classType === Response)) return;
425
+ return type;
426
+ }
427
+
428
+ export abstract class HttpRouterRegistryFunctionRegistrar {
429
+ protected defaultOptions: Partial<HttpRouterFunctionOptions> = {};
430
+
431
+ abstract addRoute(routeConfig: RouteConfig): void;
432
+
433
+ /**
434
+ * Returns a new registrar object with default options that apply to each registered route through this registrar.
435
+ *
436
+ * ```typescript
437
+ * const registry: HttpRouterRegistry = ...;
438
+ *
439
+ * const secretRegistry = registry.forOptions({groups: ['secret']});
440
+ *
441
+ * secretRegistry.get('/admin/groups', () => {
442
+ * });
443
+ *
444
+ * secretRegistry.get('/admin/users', () => {
445
+ * });
446
+ * ```
447
+ */
448
+ forOptions(options: Partial<HttpRouterFunctionOptions>): HttpRouterRegistryFunctionRegistrar {
449
+ const that = this;
450
+ return new class extends HttpRouterRegistryFunctionRegistrar {
451
+ defaultOptions = options;
452
+
453
+ addRoute(routeConfig: RouteConfig) {
454
+ that.addRoute(routeConfig);
455
+ }
456
+ };
457
+ }
458
+
459
+ public any(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
460
+ this.register(convertOptions([], pathOrOptions, this.defaultOptions), callback);
461
+ }
462
+
463
+ public add(decorator: HttpDecorator, callback: (...args: any[]) => any) {
464
+ const data = decorator(Object, '_');
465
+ const action = isArray(data) ? data.find(v => v instanceof HttpAction) : undefined;
466
+ if (!action) throw new Error('No HttpAction available');
467
+
468
+ const fn = ReflectionFunction.from(callback);
469
+ const routeConfig = createRouteConfigFromHttpAction({
470
+ type: 'function',
471
+ fn: callback,
472
+ }, action);
473
+ routeConfig.returnType = filterValidReturnType(fn.getReturnType());
474
+ this.addRoute(routeConfig);
475
+ }
476
+
477
+ public get(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
478
+ this.register(convertOptions(['GET'], pathOrOptions, this.defaultOptions), callback);
479
+ }
480
+
481
+ public post(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
482
+ this.register(convertOptions(['POST'], pathOrOptions, this.defaultOptions), callback);
483
+ }
484
+
485
+ public put(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
486
+ this.register(convertOptions(['PUT'], pathOrOptions, this.defaultOptions), callback);
487
+ }
488
+
489
+ public patch(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
490
+ this.register(convertOptions(['PATCH'], pathOrOptions, this.defaultOptions), callback);
491
+ }
492
+
493
+ public delete(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
494
+ this.register(convertOptions(['DELETE'], pathOrOptions, this.defaultOptions), callback);
495
+ }
496
+
497
+ public options(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
498
+ this.register(convertOptions(['OPTIONS'], pathOrOptions, this.defaultOptions), callback);
499
+ }
500
+
501
+ public trace(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
502
+ this.register(convertOptions(['TRACE'], pathOrOptions, this.defaultOptions), callback);
503
+ }
504
+
505
+ public head(pathOrOptions: string | HttpRouterFunctionOptions, callback: (...args: any[]) => any) {
506
+ this.register(convertOptions(['HEAD'], pathOrOptions, this.defaultOptions), callback);
507
+ }
508
+
509
+ private register(options: HttpRouterFunctionOptions, callback: (...args: any[]) => any, module?: InjectorModule<any>) {
510
+ const fn = ReflectionFunction.from(callback);
511
+
512
+ const routeConfig = new RouteConfig(options.name || '', options.methods || [], options.path, {
513
+ type: 'function',
514
+ fn: callback,
515
+ });
516
+ routeConfig.module = module;
517
+ if (options.responses) routeConfig.responses = options.responses;
518
+ if (options.description) routeConfig.description = options.description;
519
+ if (options.category) routeConfig.category = options.category;
520
+ if (options.groups) routeConfig.groups = options.groups;
521
+ if (options.data) routeConfig.data = new Map(Object.entries(options.data));
522
+ if (options.baseUrl) routeConfig.baseUrl = options.baseUrl;
523
+ if (options.middlewares) {
524
+ routeConfig.middlewares = options.middlewares.map(v => {
525
+ return { config: v(), module };
526
+ });
527
+ }
528
+
529
+ if (options.resolverForToken) {
530
+ for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
531
+ }
532
+
533
+ if (options.resolverForToken) {
534
+ for (const item of options.resolverForToken) routeConfig.resolverForToken.set(...item);
535
+ }
536
+
537
+ if (options.resolverForParameterName) {
538
+ for (const item of options.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
539
+ }
540
+
541
+ routeConfig.serializer = options.serializer;
542
+ routeConfig.serializationOptions = options.serializationOptions;
543
+
544
+ routeConfig.returnType = filterValidReturnType(fn.getReturnType());
545
+ this.addRoute(routeConfig);
546
+ }
547
+ }
548
+
549
+ function createRouteConfigFromHttpAction(routeAction: RouteClassControllerAction | RouteFunctionControllerAction, action: HttpAction, module?: InjectorModule<any>, controller?: HttpController) {
550
+ const routeConfig = new RouteConfig(action.name, action.httpMethods, action.path, routeAction);
551
+ routeConfig.responses = action.responses;
552
+ routeConfig.description = action.description;
553
+ routeConfig.category = action.category;
554
+ routeConfig.groups = action.groups;
555
+ routeConfig.data = new Map(action.data);
556
+ if (controller) {
557
+ routeConfig.baseUrl = controller.baseUrl;
558
+
559
+ routeConfig.middlewares = controller.middlewares.map(v => {
560
+ return { config: v(), module };
561
+ });
562
+ routeConfig.resolverForToken = new Map(controller.resolverForToken);
563
+ routeConfig.resolverForParameterName = new Map(controller.resolverForParameterName);
564
+ }
372
565
 
566
+ routeConfig.middlewares.push(...action.middlewares.map(v => {
567
+ return { config: v(), module };
568
+ }));
569
+
570
+ for (const item of action.resolverForToken) routeConfig.resolverForToken.set(...item);
571
+
572
+ for (const item of action.resolverForParameterName) routeConfig.resolverForParameterName.set(...item);
573
+
574
+ routeConfig.serializer = action.serializer;
575
+ routeConfig.serializationOptions = action.serializationOptions;
576
+ return routeConfig;
577
+ }
578
+
579
+ export class HttpRouterRegistry extends HttpRouterRegistryFunctionRegistrar {
373
580
  protected routes: RouteConfig[] = [];
581
+ private buildId: number = 1;
582
+
583
+ public getBuildId(): number {
584
+ return this.buildId;
585
+ }
586
+
587
+ public getRoutes(): RouteConfig[] {
588
+ return this.routes;
589
+ }
590
+
591
+ public addRouteForController(controller: ClassType, module: InjectorModule<any>) {
592
+ const controllerData = httpClass._fetch(controller);
593
+ if (!controllerData) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
594
+ const schema = ReflectionClass.from(controller);
595
+
596
+ for (const action of controllerData.getActions()) {
597
+ const routeAction: RouteClassControllerAction = {
598
+ type: 'controller',
599
+ controller,
600
+ module,
601
+ methodName: action.methodName
602
+ };
603
+ const routeConfig = createRouteConfigFromHttpAction(routeAction, action, module, controllerData);
604
+
605
+ routeConfig.module = module;
374
606
 
375
- private parseBody(req: HttpRequest, files: { [name: string]: UploadedFile }) {
607
+ if (schema.hasMethod(action.methodName)) routeConfig.returnType = filterValidReturnType(schema.getMethod(action.methodName).getReturnType());
608
+ this.addRoute(routeConfig);
609
+ }
610
+ }
611
+
612
+ public addRoute(routeConfig: RouteConfig) {
613
+ this.routes.push(routeConfig);
614
+ this.buildId++;
615
+ }
616
+ }
617
+
618
+ export class HttpRouter {
619
+ protected fn?: (request: HttpRequest) => ResolvedController | undefined;
620
+ protected buildId: number = 0;
621
+ protected resolveFn?: (name: string, parameters: { [name: string]: any }) => string;
622
+
623
+ private parseBody(req: HttpRequest, foundFiles: { [name: string]: UploadedFile }) {
376
624
  const form = formidable({
377
625
  multiples: true,
378
626
  hash: 'sha1',
379
- enabledPlugins: ['octetstream', 'querystring', 'json'],
380
627
  });
381
628
  return asyncOperation((resolve, reject) => {
382
629
  if (req.body) {
@@ -386,7 +633,17 @@ export class Router {
386
633
  if (err) {
387
634
  reject(err);
388
635
  } else {
389
- const body = req.body = { ...fields, ...files };
636
+ for (const [name, file] of Object.entries(files) as any) {
637
+ if (file.size === 0) continue;
638
+ foundFiles[name] = {
639
+ size: file.size,
640
+ path: file.filepath,
641
+ name: file.originalFilename,
642
+ type: file.mimetype,
643
+ lastModifiedDate: file.lastModifiedDate,
644
+ }
645
+ }
646
+ const body = req.body = { ...fields, ...foundFiles };
390
647
  resolve(body);
391
648
  }
392
649
  });
@@ -398,6 +655,7 @@ export class Router {
398
655
  private logger: LoggerInterface,
399
656
  tagRegistry: TagRegistry,
400
657
  private middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry,
658
+ private registry: HttpRouterRegistry = new HttpRouterRegistry,
401
659
  ) {
402
660
  for (const controller of controllers.controllers) {
403
661
  this.addRouteForController(controller.controller, controller.module);
@@ -405,7 +663,7 @@ export class Router {
405
663
  }
406
664
 
407
665
  getRoutes(): RouteConfig[] {
408
- return this.routes;
666
+ return this.registry.getRoutes();
409
667
  }
410
668
 
411
669
  static forControllers(
@@ -413,7 +671,7 @@ export class Router {
413
671
  tagRegistry: TagRegistry = new TagRegistry(),
414
672
  middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry(),
415
673
  module: InjectorModule<any> = new InjectorModule()
416
- ): Router {
674
+ ): HttpRouter {
417
675
  return new this(new HttpControllers(controllers.map(v => {
418
676
  return isClass(v) ? { controller: v, module } : v;
419
677
  })), new Logger([], []), tagRegistry, middlewareRegistry);
@@ -423,7 +681,8 @@ export class Router {
423
681
  const routeConfigVar = compiler.reserveVariable('routeConfigVar', routeConfig);
424
682
  const parsedRoute = parseRouteControllerAction(routeConfig);
425
683
  const path = routeConfig.getFullPath();
426
- const prefix = path.substr(0, path.indexOf(':'));
684
+ const pathParamStarter = path.indexOf(':')
685
+ const prefix = path.substr(0, pathParamStarter);
427
686
 
428
687
  const regexVar = compiler.reserveVariable('regex', new RegExp('^' + parsedRoute.regex + '$'));
429
688
  const setParameters: string[] = [];
@@ -432,7 +691,7 @@ export class Router {
432
691
  let bodyValidationErrorHandling = `if (bodyErrors.length) throw ValidationError.from(bodyErrors);`;
433
692
 
434
693
  let enableParseBody = false;
435
- const hasParameters = parsedRoute.getParameters().length > 0;
694
+ const hasParameters = pathParamStarter !== -1 || parsedRoute.getParameters().length > 0;
436
695
  let requiresAsyncParameters = false;
437
696
  let setParametersFromPath = '';
438
697
 
@@ -516,7 +775,7 @@ export class Router {
516
775
  ${parameterResolverFoundVar} = true;
517
776
  parameters.${parameter.parameter.name} = await ${instance}.resolve({
518
777
  token: ${injectorTokenVar},
519
- routeConfig: ${routeConfigVar},
778
+ route: ${routeConfigVar},
520
779
  request: request,
521
780
  name: ${JSON.stringify(parameter.parameter.name)},
522
781
  value: parameters.${parameter.parameter.name},
@@ -640,60 +899,22 @@ export class Router {
640
899
  }
641
900
 
642
901
  public addRoute(routeConfig: RouteConfig) {
643
- this.routes.push(routeConfig);
644
- this.fn = undefined;
902
+ this.registry.addRoute(routeConfig);
645
903
  }
646
904
 
647
905
  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
- }
906
+ this.registry.addRouteForController(controller, module);
687
907
  }
688
908
 
689
909
  protected build(): (request: HttpRequest) => ResolvedController | undefined {
910
+ this.buildId = this.registry.getBuildId();
690
911
  const compiler = new CompilerContext;
691
912
  compiler.context.set('ValidationError', ValidationError);
692
913
  compiler.context.set('qs', qs);
693
914
 
694
915
  const code: string[] = [];
695
916
 
696
- for (const route of this.routes) {
917
+ for (const route of this.getRoutes()) {
697
918
  code.push(this.getRouteCode(compiler, route));
698
919
  }
699
920
 
@@ -713,7 +934,7 @@ export class Router {
713
934
  const compiler = new CompilerContext;
714
935
  const code: string[] = [];
715
936
 
716
- for (const route of this.routes) {
937
+ for (const route of this.getRoutes()) {
717
938
  code.push(this.getRouteUrlResolveCode(compiler, route));
718
939
  }
719
940
 
@@ -734,7 +955,7 @@ export class Router {
734
955
  }
735
956
 
736
957
  public resolveRequest(request: HttpRequest): ResolvedController | undefined {
737
- if (!this.fn) {
958
+ if (!this.fn || this.buildId !== this.registry.getBuildId()) {
738
959
  this.fn = this.build();
739
960
  }
740
961
 
@@ -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);