@deepkit/http 1.0.1-alpha.124 → 1.0.1-alpha.128

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.
package/src/model.ts CHANGED
@@ -12,7 +12,7 @@ import { IncomingMessage, OutgoingHttpHeader, OutgoingHttpHeaders, ServerRespons
12
12
  import { UploadedFile } from './router.js';
13
13
  import * as querystring from 'querystring';
14
14
  import { Writable } from 'stream';
15
- import { metaAnnotation, ReflectionKind, Type, ValidationErrorItem, TypeAnnotation } from '@deepkit/type';
15
+ import { metaAnnotation, ReflectionKind, Type, TypeAnnotation, ValidationErrorItem } from '@deepkit/type';
16
16
  import { asyncOperation, isArray } from '@deepkit/core';
17
17
 
18
18
  export class HttpResponse extends ServerResponse {
@@ -134,6 +134,35 @@ export class ValidatedBody<T> {
134
134
  export type HttpBody<T> = T & TypeAnnotation<'httpBody'>;
135
135
  export type HttpBodyValidation<T> = ValidatedBody<T> & TypeAnnotation<'httpBodyValidation'>;
136
136
 
137
+ export interface HttpRequestParserOptions {
138
+ withBody?: boolean;
139
+ withQuery?: boolean;
140
+ withHeader?: boolean;
141
+ }
142
+
143
+ /**
144
+ * Delays the parsing of the body/query/header to the very last moment, when the parameter is actually used.
145
+ *
146
+ * If no options are provided, the parser will receive data from header, body, and query, in this order.
147
+ * This basically allows to fetch data from all possible HTTP sources in one go.
148
+ *
149
+ * You can disable various sources by providing the options, e.g. `{withBody: false}` to disable body parsing.
150
+ * Or `{withQuery: false}` to disable query parsing. Or `{withHeader: false}` to disable header parsing.
151
+ * To only parse the body, use `{withQuery: false, withHeader: false}`.
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * async route(parser: HttpRequestParser<{authorization: string}>) {
156
+ * const data = await parser();
157
+ * console.log(data.authorization);
158
+ * }
159
+ * ```
160
+ *
161
+ * This is necessary in event listeners, since they are instantiated synchronously,
162
+ * but body is parsed asynchronously. So use in event listeners HttpRequestParser instead of HttpBody.
163
+ */
164
+ export type HttpRequestParser<T> = ((options?: HttpRequestParserOptions) => Promise<T>) & TypeAnnotation<'httpRequestParser', T>;
165
+
137
166
  /**
138
167
  * Marks a parameter as HTTP path and reads the value from the request path.
139
168
  * This is normally not requires since the parameter name automatically maps to the path parameter,
package/src/module.ts CHANGED
@@ -15,8 +15,12 @@ import { buildRequestParser } from './request-parser.js';
15
15
  import { InjectorContext } from '@deepkit/injector';
16
16
 
17
17
  function parameterRequiresRequest(parameter: ReflectionParameter): boolean {
18
- return Boolean(metaAnnotation.getForName(parameter.type, 'httpQueries') || metaAnnotation.getForName(parameter.type, 'httpQuery')
19
- || metaAnnotation.getForName(parameter.type, 'httpBody') || metaAnnotation.getForName(parameter.type, 'httpPath') || metaAnnotation.getForName(parameter.type, 'httpHeader'));
18
+ return Boolean(metaAnnotation.getForName(parameter.type, 'httpQueries')
19
+ || metaAnnotation.getForName(parameter.type, 'httpQuery')
20
+ || metaAnnotation.getForName(parameter.type, 'httpBody')
21
+ || metaAnnotation.getForName(parameter.type, 'httpRequestParser')
22
+ || metaAnnotation.getForName(parameter.type, 'httpPath')
23
+ || metaAnnotation.getForName(parameter.type, 'httpHeader'));
20
24
  }
21
25
 
22
26
  export class HttpModule extends createModule({
@@ -76,15 +80,15 @@ export class HttpModule extends createModule({
76
80
  throw new Error(`Listener ${stringifyListener(listener)} requires async HttpBody. This is not yet supported. You have to parse the request manually by injecting HttpRequest.`);
77
81
  }
78
82
 
83
+ let build: Function;
79
84
  for (let index = 0; index < params.length; index++) {
80
85
  const parameter = params[index];
81
86
  if (!parameterRequiresRequest(parameter)) continue;
82
87
 
83
88
  //change the reflection type so that we create a unique injection token for that type.
84
- const unique = Symbol('unique');
89
+ const unique = Symbol('event.parameter:' + parameter.name);
85
90
  const uniqueType: Type = { kind: ReflectionKind.literal, literal: unique };
86
91
  metaAnnotation.registerType(parameter.type, { name: 'inject', options: [uniqueType] });
87
- let build: Function;
88
92
  let i = index;
89
93
 
90
94
  this.addProvider({
@@ -89,11 +89,20 @@ export class ParameterForRequestParser {
89
89
  return metaAnnotation.getForName(this.parameter.type, 'httpBody') !== undefined;
90
90
  }
91
91
 
92
+ get requestParser() {
93
+ return metaAnnotation.getForName(this.parameter.type, 'httpRequestParser') !== undefined;
94
+ }
95
+
92
96
  get bodyValidation() {
93
97
  return metaAnnotation.getForName(this.parameter.type, 'httpBodyValidation') !== undefined;
94
98
  }
95
99
 
96
100
  getType(): Type {
101
+ const parser = metaAnnotation.getForName(this.parameter.type, 'httpRequestParser');
102
+ if (parser && parser[0]) {
103
+ return parser[0];
104
+ }
105
+
97
106
  if (this.bodyValidation) {
98
107
  assertType(this.parameter.type, ReflectionKind.class);
99
108
  const valueType = findMember('value', this.parameter.type.types);
@@ -180,7 +189,7 @@ export function buildRequestParser(parseOptions: HttpParserOptions, parameters:
180
189
  compiler.context.set('ValidationError', ValidationError);
181
190
  compiler.context.set('qs', qs);
182
191
 
183
- let needsQueryString = !!params.find(v => v.query || v.queries);
192
+ let needsQueryString = !!params.find(v => v.query || v.queries || v.requestParser);
184
193
  const query = needsQueryString ? '_qPosition === -1 ? {} : qs.parse(_url.substr(_qPosition + 1))' : '{}';
185
194
 
186
195
  const regexVar = compiler.reserveVariable('regex', new RegExp('^' + pathRegex + '$'));
@@ -219,14 +228,11 @@ export function getRequestParserCodeForParameters(
219
228
  let bodyValidationErrorHandling = `if (bodyErrors.length) throw ValidationError.from(bodyErrors);`;
220
229
 
221
230
  for (const parameter of parameters) {
222
- if (parameter.body || parameter.bodyValidation) {
231
+ if (parameter.requestParser || parameter.body || parameter.bodyValidation) {
223
232
  const type = parameter.getType();
224
233
  const validatorVar = compiler.reserveVariable('argumentValidator', getValidatorFunction(undefined, type));
225
234
  const converterVar = compiler.reserveVariable('argumentConverter', getSerializeFunction(type, serializer.deserializeRegistry));
226
235
 
227
- enableParseBody = true;
228
- setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(bodyFields, {loosely: true});`);
229
- parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: bodyErrors});`);
230
236
  if (parameter.bodyValidation) {
231
237
  compiler.context.set('BodyValidation', ValidatedBody);
232
238
  compiler.context.set('BodyValidationError', BodyValidationError);
@@ -235,6 +241,32 @@ export function getRequestParserCodeForParameters(
235
241
  } else {
236
242
  parameterNames.push(`parameters.${parameter.parameter.name}`);
237
243
  }
244
+
245
+ if (parameter.requestParser) {
246
+ const parseOptionsVar = compiler.reserveVariable('parseOptions', parseOptions);
247
+ const parseBodyVar = compiler.reserveVariable('parseBody', parseBody);
248
+ setParameters.push(`parameters.${parameter.parameter.name} = async (options = {}) => {
249
+ let res = {};
250
+ if (options.withHeader !== false) {
251
+ Object.assign(res, _headers);
252
+ }
253
+ if (options.withBody !== false) {
254
+ bodyFields = bodyFields || (await ${parseBodyVar}(${parseOptionsVar}, request, uploadedFiles));
255
+ Object.assign(res, bodyFields);
256
+ }
257
+ if (options.withQuery !== false) {
258
+ Object.assign(res, _query);
259
+ }
260
+ res = ${converterVar}(res, {loosely: true});
261
+ ${validatorVar}(res, {errors: bodyErrors});
262
+ if (bodyErrors.length) throw ValidationError.from(bodyErrors);
263
+ return res;
264
+ }`);
265
+ } else {
266
+ enableParseBody = true;
267
+ setParameters.push(`parameters.${parameter.parameter.name} = ${converterVar}(bodyFields, {loosely: true});`);
268
+ parameterValidator.push(`${validatorVar}(parameters.${parameter.parameter.name}, {errors: bodyErrors});`);
269
+ }
238
270
  } else if (parameter.query || parameter.queries || parameter.header) {
239
271
  const converted = getSerializeFunction(parameter.parameter.parameter, serializer.deserializeRegistry, undefined, parameter.getName());
240
272
  const validator = getValidatorFunction(undefined, parameter.parameter.parameter);
@@ -343,7 +375,7 @@ export function getRequestParserCodeForParameters(
343
375
  const parseOptionsVar = compiler.reserveVariable('parseOptions', parseOptions);
344
376
  const parseBodyVar = compiler.reserveVariable('parseBody', parseBody);
345
377
  parseBodyLoading = `
346
- const bodyFields = (await ${parseBodyVar}(${parseOptionsVar}, request, uploadedFiles));`;
378
+ bodyFields = bodyFields || (await ${parseBodyVar}(${parseOptionsVar}, request, uploadedFiles));`;
347
379
  requiresAsyncParameters = true;
348
380
  }
349
381
 
@@ -354,6 +386,7 @@ export function getRequestParserCodeForParameters(
354
386
  const validationErrors = [];
355
387
  const bodyErrors = [];
356
388
  const parameters = {};
389
+ let bodyFields;
357
390
  ${setParametersFromPath}
358
391
  ${parseBodyLoading}
359
392
  ${setParameters.join('\n')}