@deepkit/http 1.0.1-alpha.98 → 1.0.1

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 (101) hide show
  1. package/CHANGELOG.md +240 -0
  2. package/dist/cjs/index.d.ts +1 -0
  3. package/dist/cjs/index.js +1 -0
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/src/controllers.js +1 -1
  6. package/dist/cjs/src/decorator.d.ts +7 -6
  7. package/dist/cjs/src/decorator.js +55 -42
  8. package/dist/cjs/src/decorator.js.map +1 -1
  9. package/dist/cjs/src/filter.js +4 -4
  10. package/dist/cjs/src/filter.js.map +1 -1
  11. package/dist/cjs/src/http-app.d.ts +8 -0
  12. package/dist/cjs/src/http-app.js +16 -0
  13. package/dist/cjs/src/http-app.js.map +1 -0
  14. package/dist/cjs/src/http.d.ts +2 -4
  15. package/dist/cjs/src/http.js +63 -51
  16. package/dist/cjs/src/http.js.map +1 -1
  17. package/dist/cjs/src/kernel.d.ts +43 -3
  18. package/dist/cjs/src/kernel.js +58 -8
  19. package/dist/cjs/src/kernel.js.map +1 -1
  20. package/dist/cjs/src/logger.js +2 -2
  21. package/dist/cjs/src/logger.js.map +1 -1
  22. package/dist/cjs/src/middleware.d.ts +2 -2
  23. package/dist/cjs/src/middleware.js +5 -6
  24. package/dist/cjs/src/middleware.js.map +1 -1
  25. package/dist/cjs/src/model.d.ts +154 -31
  26. package/dist/cjs/src/model.js +81 -41
  27. package/dist/cjs/src/model.js.map +1 -1
  28. package/dist/cjs/src/module.config.d.ts +67 -2
  29. package/dist/cjs/src/module.config.js +2 -2
  30. package/dist/cjs/src/module.config.js.map +1 -1
  31. package/dist/cjs/src/module.js +18 -13
  32. package/dist/cjs/src/module.js.map +1 -1
  33. package/dist/cjs/src/request-parser.d.ts +2 -1
  34. package/dist/cjs/src/request-parser.js +138 -60
  35. package/dist/cjs/src/request-parser.js.map +1 -1
  36. package/dist/cjs/src/router.d.ts +12 -6
  37. package/dist/cjs/src/router.js +40 -28
  38. package/dist/cjs/src/router.js.map +1 -1
  39. package/dist/cjs/src/static-serving.d.ts +4 -0
  40. package/dist/cjs/src/static-serving.js +44 -40
  41. package/dist/cjs/src/static-serving.js.map +1 -1
  42. package/dist/cjs/src/utils.js +1 -2
  43. package/dist/cjs/src/utils.js.map +1 -1
  44. package/dist/cjs/tsconfig.tsbuildinfo +1 -1
  45. package/dist/esm/index.d.ts +1 -0
  46. package/dist/esm/index.js +1 -0
  47. package/dist/esm/index.js.map +1 -1
  48. package/dist/esm/src/controllers.js +2 -2
  49. package/dist/esm/src/controllers.js.map +1 -1
  50. package/dist/esm/src/decorator.d.ts +7 -6
  51. package/dist/esm/src/decorator.js +66 -54
  52. package/dist/esm/src/decorator.js.map +1 -1
  53. package/dist/esm/src/filter.js +5 -5
  54. package/dist/esm/src/filter.js.map +1 -1
  55. package/dist/esm/src/http-app.d.ts +8 -0
  56. package/dist/esm/src/http-app.js +12 -0
  57. package/dist/esm/src/http-app.js.map +1 -0
  58. package/dist/esm/src/http.d.ts +2 -4
  59. package/dist/esm/src/http.js +65 -53
  60. package/dist/esm/src/http.js.map +1 -1
  61. package/dist/esm/src/kernel.d.ts +43 -3
  62. package/dist/esm/src/kernel.js +60 -10
  63. package/dist/esm/src/kernel.js.map +1 -1
  64. package/dist/esm/src/logger.js +2 -2
  65. package/dist/esm/src/logger.js.map +1 -1
  66. package/dist/esm/src/middleware.d.ts +2 -2
  67. package/dist/esm/src/middleware.js +6 -7
  68. package/dist/esm/src/middleware.js.map +1 -1
  69. package/dist/esm/src/model.d.ts +154 -31
  70. package/dist/esm/src/model.js +59 -31
  71. package/dist/esm/src/model.js.map +1 -1
  72. package/dist/esm/src/module.config.d.ts +67 -2
  73. package/dist/esm/src/module.config.js +2 -2
  74. package/dist/esm/src/module.config.js.map +1 -1
  75. package/dist/esm/src/module.js +20 -15
  76. package/dist/esm/src/module.js.map +1 -1
  77. package/dist/esm/src/request-parser.d.ts +2 -1
  78. package/dist/esm/src/request-parser.js +139 -61
  79. package/dist/esm/src/request-parser.js.map +1 -1
  80. package/dist/esm/src/router.d.ts +12 -6
  81. package/dist/esm/src/router.js +46 -36
  82. package/dist/esm/src/router.js.map +1 -1
  83. package/dist/esm/src/static-serving.d.ts +4 -0
  84. package/dist/esm/src/static-serving.js +41 -39
  85. package/dist/esm/src/static-serving.js.map +1 -1
  86. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  87. package/index.ts +1 -0
  88. package/package.json +23 -16
  89. package/src/decorator.ts +20 -2
  90. package/src/http-app.ts +11 -0
  91. package/src/http.ts +30 -10
  92. package/src/kernel.ts +81 -9
  93. package/src/middleware.ts +1 -1
  94. package/src/model.ts +175 -16
  95. package/src/module.config.ts +75 -1
  96. package/src/module.ts +20 -12
  97. package/src/request-parser.ts +147 -62
  98. package/src/router.ts +36 -10
  99. package/src/static-serving.ts +49 -38
  100. package/tsconfig.json +9 -3
  101. package/tsconfig.spec.json +8 -0
@@ -1,60 +1,67 @@
1
1
  import { asyncOperation, ClassType, CompilerContext, getClassName, isObject } from '@deepkit/core';
2
- import { InjectorModule } from '@deepkit/injector';
2
+ import { DependenciesUnmetError, InjectorModule } from '@deepkit/injector';
3
3
  import {
4
4
  assertType,
5
5
  findMember,
6
6
  getSerializeFunction,
7
7
  getValidatorFunction,
8
- metaAnnotation,
8
+ hasDefaultValue,
9
+ isOptional,
9
10
  ReflectionKind,
10
11
  ReflectionParameter,
11
12
  resolveReceiveType,
12
13
  serializer,
13
14
  stringifyType,
14
15
  Type,
16
+ typeAnnotation,
15
17
  typeToObject,
16
- ValidationError
18
+ ValidationError,
17
19
  } from '@deepkit/type';
18
20
  import { BodyValidationError, createRequestWithCachedBody, getRegExp, HttpRequest, ValidatedBody } from './model.js';
19
- import { RouteConfig, UploadedFile } from './router.js';
20
-
21
- //@ts-ignore
21
+ import { getRouteActionLabel, RouteConfig, UploadedFile, UploadedFileSymbol } from './router.js';
22
22
  import qs from 'qs';
23
-
24
- // @ts-ignore
25
- import formidable from 'formidable';
23
+ import formidable, { Fields, Files, Options } from 'formidable';
26
24
  import { HttpParserOptions } from './module.config.js';
27
25
 
28
-
29
26
  function parseBody(
30
27
  options: HttpParserOptions,
31
28
  req: HttpRequest, foundFiles: { [name: string]: UploadedFile }) {
32
- const form = formidable(Object.assign(options, {
29
+
30
+ const form = formidable(Object.assign(options as Options, {
33
31
  multiples: true,
34
32
  }));
35
- return asyncOperation((resolve, reject) => {
36
- function parseData(err: any, fields: any, files: any) {
37
- if (err) {
38
- reject(err);
39
- } else {
40
- for (const [name, file] of Object.entries(files) as any) {
41
- if (file.size === 0) continue;
42
- foundFiles[name] = {
43
- size: file.size,
44
- path: file.filepath,
45
- name: file.originalFilename,
46
- type: file.mimetype,
47
- lastModifiedDate: file.lastModifiedDate,
48
- };
49
- }
50
- const body = { ...fields, ...foundFiles };
51
- resolve(body);
33
+ return asyncOperation(async (resolve, reject) => {
34
+ function parseData(fields: Fields, files: Files) {
35
+ const fileEntries = Object.entries(files);
36
+
37
+ // formidable turns JSON arrays into numerically keyed objects, so we convert them back
38
+ if ('0' in fields && fileEntries.length === 0 && Object.keys(fields).every((key, idx) => parseInt(key) === idx)) {
39
+ return resolve(Object.values(fields));
52
40
  }
41
+
42
+ for (const [name, files] of fileEntries) {
43
+ if (!files) continue;
44
+ const file = files[0];
45
+ if (!file) continue;
46
+ if (!file.filepath || 'string' !== typeof file.filepath) continue;
47
+ if (!file.size || 'number' !== typeof file.size) continue;
48
+
49
+ foundFiles[name] = {
50
+ validator: UploadedFileSymbol,
51
+ size: file.size,
52
+ path: file.filepath,
53
+ name: file.originalFilename || null,
54
+ type: file.mimetype || null,
55
+ lastModifiedDate: null,
56
+ };
57
+ }
58
+ const body = { ...fields, ...foundFiles };
59
+ resolve(body);
53
60
  }
54
61
 
55
62
  if (req.body) {
56
- form.parse(createRequestWithCachedBody(req, req.body), parseData);
57
- return;
63
+ const [fields, files] = await form.parse(createRequestWithCachedBody(req, req.body));
64
+ parseData(fields, files);
58
65
  }
59
66
 
60
67
  const chunks: Buffer[] = [];
@@ -70,7 +77,8 @@ function parseBody(
70
77
  });
71
78
  req.once('error', () => req.off('data', read));
72
79
 
73
- form.parse(req, parseData);
80
+ const [fields, files] = await form.parse(req);
81
+ parseData(fields, files);
74
82
  });
75
83
  }
76
84
 
@@ -83,14 +91,23 @@ export class ParameterForRequestParser {
83
91
  }
84
92
 
85
93
  get body() {
86
- return metaAnnotation.getForName(this.parameter.type, 'httpBody') !== undefined;
94
+ return typeAnnotation.getType(this.parameter.type, 'httpBody') !== undefined;
95
+ }
96
+
97
+ get requestParser() {
98
+ return typeAnnotation.getType(this.parameter.type, 'httpRequestParser') !== undefined;
87
99
  }
88
100
 
89
101
  get bodyValidation() {
90
- return metaAnnotation.getForName(this.parameter.type, 'httpBodyValidation') !== undefined;
102
+ return typeAnnotation.getType(this.parameter.type, 'httpBodyValidation') !== undefined;
91
103
  }
92
104
 
93
105
  getType(): Type {
106
+ const parser = typeAnnotation.getType(this.parameter.type, 'httpRequestParser');
107
+ if (parser) {
108
+ return parser;
109
+ }
110
+
94
111
  if (this.bodyValidation) {
95
112
  assertType(this.parameter.type, ReflectionKind.class);
96
113
  const valueType = findMember('value', this.parameter.type.types);
@@ -101,22 +118,22 @@ export class ParameterForRequestParser {
101
118
  }
102
119
 
103
120
  get header() {
104
- return metaAnnotation.getForName(this.parameter.type, 'httpHeader') !== undefined;
121
+ return typeAnnotation.getType(this.parameter.type, 'httpHeader') !== undefined;
105
122
  }
106
123
 
107
124
  get query() {
108
- return metaAnnotation.getForName(this.parameter.type, 'httpQuery') !== undefined;
125
+ return typeAnnotation.getType(this.parameter.type, 'httpQuery') !== undefined;
109
126
  }
110
127
 
111
128
  get queries() {
112
- return metaAnnotation.getForName(this.parameter.type, 'httpQueries') !== undefined;
129
+ return typeAnnotation.getType(this.parameter.type, 'httpQueries') !== undefined;
113
130
  }
114
131
 
115
132
  get typePath(): string | undefined {
116
- const typeOptions = metaAnnotation.getForName(this.parameter.type, 'httpQueries') || metaAnnotation.getForName(this.parameter.type, 'httpQuery')
117
- || metaAnnotation.getForName(this.parameter.type, 'httpPath') || metaAnnotation.getForName(this.parameter.type, 'httpHeader');
133
+ const typeOptions = typeAnnotation.getType(this.parameter.type, 'httpQueries') || typeAnnotation.getType(this.parameter.type, 'httpQuery')
134
+ || typeAnnotation.getType(this.parameter.type, 'httpPath') || typeAnnotation.getType(this.parameter.type, 'httpHeader');
118
135
  if (!typeOptions) return;
119
- const options = typeToObject(typeOptions[0]);
136
+ const options = typeToObject(typeOptions);
120
137
  if (isObject(options)) return options.name;
121
138
  return;
122
139
  }
@@ -126,7 +143,7 @@ export class ParameterForRequestParser {
126
143
  }
127
144
 
128
145
  isPartOfPath(): boolean {
129
- return metaAnnotation.getForName(this.parameter.type, 'httpPath') !== undefined || this.regexPosition !== undefined;
146
+ return typeAnnotation.getType(this.parameter.type, 'httpPath') !== undefined || this.regexPosition !== undefined;
130
147
  }
131
148
  }
132
149
 
@@ -152,26 +169,38 @@ export function parseRoutePathToRegex(path: string, params: ReflectionParameter[
152
169
  return { regex: path, parameterNames };
153
170
  }
154
171
 
155
- export function buildRequestParser(parseOptions: HttpParserOptions, parameters: ReflectionParameter[], path?: string): (request: HttpRequest) => any[] {
172
+ function isTypeUnknown(type: Type): boolean {
173
+ if (type.id) return false; //if is has an id we treat it as nominal type
174
+ return type.kind === ReflectionKind.unknown || type.kind === ReflectionKind.any
175
+ || type.kind === ReflectionKind.never;
176
+ }
177
+
178
+ export function buildRequestParser(parseOptions: HttpParserOptions, parameters: ReflectionParameter[], routeConfig?: RouteConfig): (request: HttpRequest) => any[] {
156
179
  const compiler = new CompilerContext();
157
180
  const params = parameters.map(v => new ParameterForRequestParser(v));
158
181
 
159
182
  //todo: parse path
160
183
  let pathRegex = '';
161
- if (path) {
162
- const parsedPath = parseRoutePathToRegex(path, parameters);
184
+ let pathParameterNames: { [name: string]: number } = {};
185
+
186
+ if (routeConfig) {
187
+ const parsedPath = parseRoutePathToRegex(routeConfig.getFullPath(), parameters);
163
188
  pathRegex = parsedPath.regex;
189
+ pathParameterNames = parsedPath.parameterNames;
164
190
 
165
191
  for (const param of params) {
166
192
  param.regexPosition = parsedPath.parameterNames[param.parameter.name];
167
193
  }
168
194
  }
169
195
 
170
- const code = getRequestParserCodeForParameters(compiler, parseOptions, params, {});
196
+ const code = getRequestParserCodeForParameters(compiler, parseOptions, params, {
197
+ pathParameterNames,
198
+ routeConfig
199
+ });
171
200
  compiler.context.set('ValidationError', ValidationError);
172
201
  compiler.context.set('qs', qs);
173
202
 
174
- let needsQueryString = !!params.find(v => v.query || v.queries);
203
+ let needsQueryString = !!params.find(v => v.query || v.queries || v.requestParser);
175
204
  const query = needsQueryString ? '_qPosition === -1 ? {} : qs.parse(_url.substr(_qPosition + 1))' : '{}';
176
205
 
177
206
  const regexVar = compiler.reserveVariable('regex', new RegExp('^' + pathRegex + '$'));
@@ -199,8 +228,9 @@ export function getRequestParserCodeForParameters(
199
228
  resolverForToken?: Map<any, ClassType>,
200
229
  pathParameterNames?: { [name: string]: number },
201
230
  routeConfig?: RouteConfig,
202
- }
231
+ },
203
232
  ) {
233
+ compiler.set({ DependenciesUnmetError });
204
234
  let enableParseBody = false;
205
235
  let requiresAsyncParameters = false;
206
236
  const setParameters: string[] = [];
@@ -210,14 +240,11 @@ export function getRequestParserCodeForParameters(
210
240
  let bodyValidationErrorHandling = `if (bodyErrors.length) throw ValidationError.from(bodyErrors);`;
211
241
 
212
242
  for (const parameter of parameters) {
213
- if (parameter.body || parameter.bodyValidation) {
243
+ if (parameter.requestParser || parameter.body || parameter.bodyValidation) {
214
244
  const type = parameter.getType();
215
245
  const validatorVar = compiler.reserveVariable('argumentValidator', getValidatorFunction(undefined, type));
216
246
  const converterVar = compiler.reserveVariable('argumentConverter', getSerializeFunction(type, serializer.deserializeRegistry));
217
247
 
218
- enableParseBody = true;
219
- setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(bodyFields, {loosely: true});`);
220
- parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: bodyErrors});`);
221
248
  if (parameter.bodyValidation) {
222
249
  compiler.context.set('BodyValidation', ValidatedBody);
223
250
  compiler.context.set('BodyValidationError', BodyValidationError);
@@ -226,9 +253,44 @@ export function getRequestParserCodeForParameters(
226
253
  } else {
227
254
  parameterNames.push(`parameters.${parameter.parameter.name}`);
228
255
  }
256
+
257
+ if (parameter.requestParser) {
258
+ const parseOptionsVar = compiler.reserveVariable('parseOptions', parseOptions);
259
+ const parseBodyVar = compiler.reserveVariable('parseBody', parseBody);
260
+
261
+ let assignPathNames: string[] = [];
262
+ for (const [name, index] of Object.entries(config.pathParameterNames || {})) {
263
+ assignPathNames.push(`res.${name} = _match[${1 + index}];`);
264
+ }
265
+
266
+ setParameters.push(`parameters.${parameter.parameter.name} = async (options = {}) => {
267
+ let res = {};
268
+ if (options.withPath !== false) {
269
+ ${assignPathNames.join('\n')}
270
+ }
271
+ if (options.withHeader !== false) {
272
+ Object.assign(res, _headers);
273
+ }
274
+ if (options.withBody !== false) {
275
+ bodyFields = bodyFields || (await ${parseBodyVar}(${parseOptionsVar}, request, uploadedFiles));
276
+ Object.assign(res, bodyFields);
277
+ }
278
+ if (options.withQuery !== false) {
279
+ Object.assign(res, _query);
280
+ }
281
+ res = ${converterVar}(res, {loosely: true});
282
+ ${validatorVar}(res, {errors: bodyErrors});
283
+ if (bodyErrors.length) throw ValidationError.from(bodyErrors);
284
+ return res;
285
+ }`);
286
+ } else {
287
+ enableParseBody = true;
288
+ setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(bodyFields, {loosely: true});`);
289
+ parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: bodyErrors});`);
290
+ }
229
291
  } else if (parameter.query || parameter.queries || parameter.header) {
230
292
  const converted = getSerializeFunction(parameter.parameter.parameter, serializer.deserializeRegistry, undefined, parameter.getName());
231
- const validator = getValidatorFunction(undefined, parameter.parameter.parameter,);
293
+ const validator = getValidatorFunction(undefined, parameter.parameter.parameter);
232
294
  const converterVar = compiler.reserveVariable('argumentConverter', converted);
233
295
  const validatorVar = compiler.reserveVariable('argumentValidator', validator);
234
296
 
@@ -236,7 +298,12 @@ export function getRequestParserCodeForParameters(
236
298
  const accessor = queryPath ? `['` + (queryPath.replace(/\./g, `']['`)) + `']` : '';
237
299
  const queryAccessor = parameter.header ? `_headers${accessor}` : queryPath ? `_query${accessor}` : '_query';
238
300
 
239
- setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(${queryAccessor}, {loosely: true});`);
301
+ if (isOptional(parameter.parameter.parameter) || hasDefaultValue(parameter.parameter.parameter)) {
302
+ setParameters.push(`parameters.${parameter.parameter.name} = ${queryAccessor} === undefined ? undefined : ${converterVar}(${queryAccessor}, {loosely: true});`);
303
+ } else {
304
+ setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(${queryAccessor}, {loosely: true});`);
305
+ }
306
+
240
307
  parameterNames.push(`parameters.${parameter.parameter.name}`);
241
308
  parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: validationErrors}, ${JSON.stringify(parameter.typePath || parameter.getName())});`);
242
309
  } else {
@@ -256,13 +323,13 @@ export function getRequestParserCodeForParameters(
256
323
  }
257
324
  }
258
325
 
259
- const injectorToken = parameter.parameter.type.kind === ReflectionKind.class ? parameter.parameter.type.classType : undefined;
260
- const injectorTokenVar = compiler.reserveVariable('classType', injectorToken);
326
+ const injectorTokenVar = compiler.reserveVariable('type', parameter.parameter.type);
261
327
  const parameterResolverFoundVar = compiler.reserveVariable('parameterResolverFound', false);
262
328
 
263
329
  setParameters.push(`${parameterResolverFoundVar} = false;`);
264
330
 
265
- const resolverType = config.resolverForParameterName?.get(parameter.getName()) || config.resolverForToken?.get(injectorToken);
331
+ const resolverType = config.resolverForParameterName?.get(parameter.getName())
332
+ || config.resolverForToken?.get(parameter.parameter.type.kind === ReflectionKind.class ? parameter.parameter.type.classType : undefined);
266
333
 
267
334
  //make sure all parameter values from the path are available, important for parameter resolver
268
335
  if (resolverType && !setParametersFromPath && config.pathParameterNames) {
@@ -271,8 +338,14 @@ export function getRequestParserCodeForParameters(
271
338
  }
272
339
  }
273
340
 
341
+ if (!resolverType && !parameter.isPartOfPath() && isTypeUnknown(parameter.parameter.type)) {
342
+ const label = config.routeConfig ? getRouteActionLabel(config.routeConfig?.action) + ' ' : '';
343
+ throw new Error(`Parameter ${label}${JSON.stringify(parameter.parameter.name)} has no runtime type. Runtime types disabled or circular dependencies?`);
344
+ }
345
+
274
346
  let injector = '_injector';
275
- const moduleVar = config.module ? ', ' + compiler.reserveConst(config.module, 'module') : '';
347
+ const moduleRawVar = config.module ? compiler.reserveConst(config.module, 'module') : 'undefined';
348
+ const moduleVar = config.module ? `, ${moduleRawVar}` : '';
276
349
 
277
350
  if (resolverType) {
278
351
  requiresAsyncParameters = true;
@@ -287,13 +360,15 @@ export function getRequestParserCodeForParameters(
287
360
  const instance = compiler.reserveVariable('resolverInstance');
288
361
 
289
362
  const routeConfigVar = compiler.reserveVariable('routeConfigVar', config.routeConfig);
363
+ const classTypeToken = parameter.parameter.type.kind === ReflectionKind.class ? parameter.parameter.type.classType : undefined;
364
+ const classTypeTokenVar = compiler.reserveVariable('classType', classTypeToken);
290
365
  setParameters.push(`
291
366
  //resolver ${getClassName(resolverType)} for ${parameter.getName()}
292
367
  ${instance} = ${instanceFetcher};
293
368
  if (!${parameterResolverFoundVar}) {
294
369
  ${parameterResolverFoundVar} = true;
295
370
  parameters.${parameter.parameter.name} = await ${instance}.resolve({
296
- token: ${injectorTokenVar},
371
+ token: ${classTypeTokenVar},
297
372
  route: ${routeConfigVar},
298
373
  request: request,
299
374
  name: ${JSON.stringify(parameter.parameter.name)},
@@ -306,11 +381,20 @@ export function getRequestParserCodeForParameters(
306
381
  }
307
382
 
308
383
  if (!parameter.isPartOfPath()) {
309
- let injectorGet = `parameters.${parameter.parameter.name} = ${injector}.get(${injectorTokenVar});`;
310
- if (parameter.parameter.isOptional()) {
311
- injectorGet = `try {parameters.${parameter.parameter.name} = ${injector}.get(${injectorTokenVar}); } catch (e) {}`;
384
+ const resolverVar = compiler.reserveVariable('resolver');
385
+ let injectorGet = `
386
+ if (!${resolverVar}) ${resolverVar} = ${injector}.resolve(${moduleRawVar}, ${injectorTokenVar});
387
+ parameters.${parameter.parameter.name} = ${resolverVar}(${injector}.scope);
388
+ `;
389
+ if (!parameter.parameter.isOptional()) {
390
+ injectorGet += `
391
+ if (!parameters.${parameter.parameter.name}) {
392
+ throw new DependenciesUnmetError(
393
+ \`Parameter \${${JSON.stringify(parameter.parameter.name)}} is required but provider returned undefined.\`,
394
+ );
395
+ }`;
312
396
  }
313
- setParameters.push(`if (!${parameterResolverFoundVar}) ${injectorGet}`);
397
+ setParameters.push(`if (!${parameterResolverFoundVar}) { ${injectorGet} }`);
314
398
  }
315
399
  }
316
400
  }
@@ -321,7 +405,7 @@ export function getRequestParserCodeForParameters(
321
405
  const parseOptionsVar = compiler.reserveVariable('parseOptions', parseOptions);
322
406
  const parseBodyVar = compiler.reserveVariable('parseBody', parseBody);
323
407
  parseBodyLoading = `
324
- const bodyFields = (await ${parseBodyVar}(${parseOptionsVar}, request, uploadedFiles));`;
408
+ bodyFields = bodyFields || (await ${parseBodyVar}(${parseOptionsVar}, request, uploadedFiles));`;
325
409
  requiresAsyncParameters = true;
326
410
  }
327
411
 
@@ -332,6 +416,7 @@ export function getRequestParserCodeForParameters(
332
416
  const validationErrors = [];
333
417
  const bodyErrors = [];
334
418
  const parameters = {};
419
+ let bodyFields;
335
420
  ${setParametersFromPath}
336
421
  ${parseBodyLoading}
337
422
  ${setParameters.join('\n')}
package/src/router.ts CHANGED
@@ -8,10 +8,21 @@
8
8
  * You should have received a copy of the MIT License along with this program.
9
9
  */
10
10
  import { ClassType, CompilerContext, getClassName, isArray, isClass, urlJoin } from '@deepkit/core';
11
- import { entity, ReflectionClass, ReflectionFunction, ReflectionKind, ReflectionParameter, SerializationOptions, Serializer, Type, ValidationError } from '@deepkit/type';
12
- import { HttpAction, httpClass, HttpController, HttpDecorator } from './decorator.js';
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';
13
24
  import { HttpRequest, HttpRequestPositionedParameters, HttpRequestQuery, HttpRequestResolvedParameters } from './model.js';
14
- import { InjectorContext, InjectorModule, TagRegistry } from '@deepkit/injector';
25
+ import { InjectorContext, InjectorModule } from '@deepkit/injector';
15
26
  import { Logger, LoggerInterface } from '@deepkit/logger';
16
27
  import { HttpControllers } from './controllers.js';
17
28
  import { MiddlewareRegistry, MiddlewareRegistryEntry } from '@deepkit/app';
@@ -21,7 +32,7 @@ import { HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware.js';
21
32
  import qs from 'qs';
22
33
  import { HtmlResponse, JSONResponse, Response } from './http.js';
23
34
  import { getRequestParserCodeForParameters, ParameterForRequestParser, parseRoutePathToRegex } from './request-parser.js';
24
- import { HttpConfig, HttpParserOptions } from './module.config.js';
35
+ import { HttpConfig } from './module.config.js';
25
36
 
26
37
  export type RouteParameterResolverForInjector = ((injector: InjectorContext) => HttpRequestPositionedParameters | Promise<HttpRequestPositionedParameters>);
27
38
 
@@ -29,11 +40,18 @@ interface ResolvedController {
29
40
  parameters: RouteParameterResolverForInjector;
30
41
  routeConfig: RouteConfig;
31
42
  uploadedFiles: { [name: string]: UploadedFile };
32
- middlewares?: (injector: InjectorContext) => { fn: HttpMiddlewareFn, timeout: number }[];
43
+ middlewares?: (injector: InjectorContext) => { fn: HttpMiddlewareFn, timeout?: number }[];
33
44
  }
34
45
 
46
+ export const UploadedFileSymbol = Symbol('UploadedFile');
47
+
35
48
  @entity.name('@deepkit/UploadedFile')
36
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
+
37
55
  /**
38
56
  * The size of the uploaded file in bytes.
39
57
  */
@@ -66,6 +84,11 @@ export class UploadedFile {
66
84
  // hash!: string | 'sha1' | 'md5' | 'sha256' | null;
67
85
  }
68
86
 
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
+
69
92
  export interface RouteFunctionControllerAction {
70
93
  type: 'function';
71
94
  //if not set, the root module is used
@@ -81,6 +104,11 @@ export interface RouteClassControllerAction {
81
104
  methodName: string;
82
105
  }
83
106
 
107
+ export function getRouteActionLabel(action: RouteClassControllerAction | RouteFunctionControllerAction): string {
108
+ if (action.type === 'controller') return `${getClassName(action.controller)}.${action.methodName}`;
109
+ return action.fn.name;
110
+ }
111
+
84
112
  export class RouteConfig {
85
113
  public baseUrl: string = '';
86
114
 
@@ -141,7 +169,7 @@ export class RouteConfig {
141
169
  }
142
170
  }
143
171
 
144
- class ParsedRoute {
172
+ export class ParsedRoute {
145
173
  public regex?: string;
146
174
 
147
175
  public pathParameterNames: { [name: string]: number } = {};
@@ -503,7 +531,7 @@ export class HttpRouterRegistry extends HttpRouterRegistryFunctionRegistrar {
503
531
  if (!controllerData) throw new Error(`Http controller class ${getClassName(controller)} has no @http.controller decorator.`);
504
532
  const schema = ReflectionClass.from(controller);
505
533
 
506
- for (const action of controllerData.getActions()) {
534
+ for (const action of getActions(controller)) {
507
535
  const routeAction: RouteClassControllerAction = {
508
536
  type: 'controller',
509
537
  controller,
@@ -533,7 +561,6 @@ export class HttpRouter {
533
561
  constructor(
534
562
  controllers: HttpControllers,
535
563
  private logger: LoggerInterface,
536
- tagRegistry: TagRegistry,
537
564
  private config: HttpConfig,
538
565
  private middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry,
539
566
  private registry: HttpRouterRegistry = new HttpRouterRegistry,
@@ -549,14 +576,13 @@ export class HttpRouter {
549
576
 
550
577
  static forControllers(
551
578
  controllers: (ClassType | { module: InjectorModule<any>, controller: ClassType })[],
552
- tagRegistry: TagRegistry = new TagRegistry(),
553
579
  middlewareRegistry: MiddlewareRegistry = new MiddlewareRegistry(),
554
580
  module: InjectorModule<any> = new InjectorModule(),
555
581
  config: HttpConfig = new HttpConfig()
556
582
  ): HttpRouter {
557
583
  return new this(new HttpControllers(controllers.map(v => {
558
584
  return isClass(v) ? { controller: v, module } : v;
559
- })), new Logger([], []), tagRegistry, config, middlewareRegistry);
585
+ })), new Logger([], []), config, middlewareRegistry);
560
586
  }
561
587
 
562
588
  protected getRouteCode(compiler: CompilerContext, routeConfig: RouteConfig): string {
@@ -18,44 +18,55 @@ import { ClassType, urlJoin } from '@deepkit/core';
18
18
  import { HttpRequest, HttpResponse } from './model.js';
19
19
  import send from 'send';
20
20
  import { eventDispatcher } from '@deepkit/event';
21
- import { RouteConfig, HttpRouter } from './router.js';
21
+ import { HttpRouter, RouteConfig } from './router.js';
22
+
23
+ export function staticServe(
24
+ localPath: string,
25
+ path: string,
26
+ request: HttpRequest,
27
+ response: HttpResponse,
28
+ ) {
29
+ return new Promise((resolve, reject) => {
30
+ const res = send(request, path, { root: localPath });
31
+ res.pipe(response);
32
+ res.on('end', resolve);
33
+ });
34
+ }
35
+
36
+ export async function staticOnRoute(
37
+ event: typeof httpWorkflow.onRoute.event,
38
+ path: string,
39
+ localPath: string,
40
+ ) {
41
+ if (event.sent) return;
42
+ if (event.route) return;
43
+
44
+ if (!event.request.url?.startsWith(path)) return;
45
+
46
+ const relativePath = urlJoin('/', event.url.substr(path.length));
47
+ const finalLocalPath = join(localPath, relativePath);
48
+
49
+ return new Promise(resolve => {
50
+ stat(finalLocalPath, (err, stat) => {
51
+ if (stat && stat.isFile()) {
52
+ event.routeFound(
53
+ new RouteConfig('static', ['GET'], event.url, {
54
+ type: 'function',
55
+ fn: staticServe,
56
+ }),
57
+ () => ({ arguments: [localPath, relativePath, event.request, event.response], parameters: {} }),
58
+ );
59
+ }
60
+ resolve(undefined);
61
+ });
62
+ });
63
+ }
22
64
 
23
65
  export function serveStaticListener(module: AppModule<any>, path: string, localPath: string = path): ClassType {
24
66
  class HttpRequestStaticServingListener {
25
- serve(path: string, request: HttpRequest, response: HttpResponse) {
26
- return new Promise((resolve, reject) => {
27
- const res = send(request, path, { root: localPath });
28
- res.pipe(response);
29
- res.on('end', resolve);
30
- });
31
- }
32
-
33
67
  @eventDispatcher.listen(httpWorkflow.onRoute, 101) //after default route listener at 100
34
68
  onRoute(event: typeof httpWorkflow.onRoute.event) {
35
- if (event.sent) return;
36
- if (event.route) return;
37
-
38
- if (!event.request.url?.startsWith(path)) return;
39
-
40
- const relativePath = urlJoin('/', event.url.substr(path.length));
41
- const finalLocalPath = join(localPath, relativePath);
42
-
43
- return new Promise(resolve => {
44
- stat(finalLocalPath, (err, stat) => {
45
- if (stat && stat.isFile()) {
46
- event.routeFound(
47
- new RouteConfig('static', ['GET'], event.url, {
48
- type: 'controller',
49
- controller: HttpRequestStaticServingListener,
50
- module,
51
- methodName: 'serve'
52
- }),
53
- () => ({arguments: [relativePath, event.request, event.response], parameters: {}})
54
- );
55
- }
56
- resolve(undefined);
57
- });
58
- });
69
+ return staticOnRoute(event, path, localPath);
59
70
  }
60
71
  }
61
72
 
@@ -108,7 +119,7 @@ export function registerStaticHttpController(module: AppModule<any>, options: St
108
119
  const groups = options.groups || [];
109
120
 
110
121
  class StaticController {
111
- @http.GET().group(...groups)
122
+ @(http.GET().group(...groups))
112
123
  serveIndex(request: HttpRequest, response: HttpResponse) {
113
124
  if (!indexHtml) {
114
125
  indexHtml = loadHtml(options.localPath, normalizeDirectory(options.path));
@@ -132,20 +143,20 @@ export function registerStaticHttpController(module: AppModule<any>, options: St
132
143
  type: 'controller',
133
144
  controller: StaticController,
134
145
  module,
135
- methodName: 'serveIndex'
146
+ methodName: 'serveIndex',
136
147
  });
137
148
  route1.groups = groups;
138
- module.setupGlobalProvider<HttpRouter>().addRoute(route1);
149
+ module.configureProvider<HttpRouter>(router => router.addRoute(route1), { global: true });
139
150
 
140
151
  if (path !== '/') {
141
152
  const route2 = new RouteConfig('static', ['GET'], path.slice(0, -1), {
142
153
  type: 'controller',
143
154
  controller: StaticController,
144
155
  module,
145
- methodName: 'serveIndex'
156
+ methodName: 'serveIndex',
146
157
  });
147
158
  route2.groups = groups;
148
- module.setupGlobalProvider<HttpRouter>().addRoute(route2);
159
+ module.configureProvider<HttpRouter>(router => router.addRoute(route2), { global: true });
149
160
  }
150
161
 
151
162
  module.addProvider(StaticController);
package/tsconfig.json CHANGED
@@ -8,19 +8,25 @@
8
8
  "experimentalDecorators": true,
9
9
  "emitDecoratorMetadata": true,
10
10
  "moduleResolution": "node",
11
- "target": "es2018",
11
+ "target": "es2020",
12
12
  "module": "CommonJS",
13
13
  "esModuleInterop": true,
14
14
  "outDir": "./dist/cjs",
15
15
  "declaration": true,
16
- "composite": true
16
+ "composite": true,
17
+ "types": [
18
+ "node",
19
+ "send"
20
+ ]
17
21
  },
18
22
  "reflection": true,
19
23
  "include": [
20
24
  "src",
21
- "tests",
22
25
  "index.ts"
23
26
  ],
27
+ "exclude": [
28
+ "tests"
29
+ ],
24
30
  "references": [
25
31
  {
26
32
  "path": "../app/tsconfig.json"
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "include": [
4
+ "src",
5
+ "index.ts",
6
+ "tests"
7
+ ]
8
+ }