@deepkit/http 1.0.1-alpha.59 → 1.0.1-alpha.63
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/coverage/clover.xml +1086 -0
- package/coverage/coverage-final.json +12 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +79 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +126 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +170 -0
- package/coverage/lcov-report/src/controllers.ts.html +113 -0
- package/coverage/lcov-report/src/decorator.ts.html +1280 -0
- package/coverage/lcov-report/src/filter.ts.html +734 -0
- package/coverage/lcov-report/src/http.ts.html +2192 -0
- package/coverage/lcov-report/src/index.html +246 -0
- package/coverage/lcov-report/src/kernel.ts.html +260 -0
- package/coverage/lcov-report/src/middleware.ts.html +428 -0
- package/coverage/lcov-report/src/model.ts.html +937 -0
- package/coverage/lcov-report/src/module.config.ts.html +89 -0
- package/coverage/lcov-report/src/module.ts.html +245 -0
- package/coverage/lcov-report/src/router.ts.html +2318 -0
- package/coverage/lcov-report/tests/index.html +111 -0
- package/coverage/lcov-report/tests/utils.ts.html +176 -0
- package/coverage/lcov.info +2225 -0
- package/dist/cjs/index.js +5 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/src/controllers.d.ts +4 -4
- package/dist/cjs/src/controllers.js +3 -0
- package/dist/cjs/src/controllers.js.map +1 -1
- package/dist/cjs/src/decorator.d.ts +29 -94
- package/dist/cjs/src/decorator.js +53 -118
- package/dist/cjs/src/decorator.js.map +1 -1
- package/dist/cjs/src/filter.d.ts +1 -0
- package/dist/cjs/src/filter.js +11 -17
- package/dist/cjs/src/filter.js.map +1 -1
- package/dist/cjs/src/http.d.ts +5 -3
- package/dist/cjs/src/http.js +91 -55
- package/dist/cjs/src/http.js.map +1 -1
- package/dist/cjs/src/kernel.d.ts +3 -3
- package/dist/cjs/src/kernel.js +5 -25
- package/dist/cjs/src/kernel.js.map +1 -1
- package/dist/cjs/src/logger.d.ts +2 -2
- package/dist/cjs/src/logger.js +4 -8
- package/dist/cjs/src/logger.js.map +1 -1
- package/dist/cjs/src/middleware.d.ts +5 -1
- package/dist/cjs/src/middleware.js +15 -3
- package/dist/cjs/src/middleware.js.map +1 -1
- package/dist/cjs/src/model.d.ts +62 -2
- package/dist/cjs/src/model.js +101 -26
- package/dist/cjs/src/model.js.map +1 -1
- package/dist/cjs/src/module.config.d.ts +3 -4
- package/dist/cjs/src/module.config.js +8 -6
- package/dist/cjs/src/module.config.js.map +1 -1
- package/dist/cjs/src/module.d.ts +2 -3
- package/dist/cjs/src/module.js +4 -2
- package/dist/cjs/src/module.js.map +1 -1
- package/dist/cjs/src/router.d.ts +24 -53
- package/dist/cjs/src/router.js +140 -159
- package/dist/cjs/src/router.js.map +1 -1
- package/dist/cjs/src/static-serving.d.ts +1 -0
- package/dist/cjs/src/static-serving.js +46 -39
- package/dist/cjs/src/static-serving.js.map +1 -1
- package/dist/cjs/src/utils.js +1 -0
- package/dist/cjs/tests/filter.spec.js +61 -47
- package/dist/cjs/tests/filter.spec.js.map +1 -1
- package/dist/cjs/tests/middleware.spec.d.ts +1 -1
- package/dist/cjs/tests/middleware.spec.js +157 -145
- package/dist/cjs/tests/middleware.spec.js.map +1 -1
- package/dist/cjs/tests/model.spec.js +4 -4
- package/dist/cjs/tests/model.spec.js.map +1 -1
- package/dist/cjs/tests/module.spec.js +4 -3
- package/dist/cjs/tests/module.spec.js.map +1 -1
- package/dist/cjs/tests/router.spec.d.ts +1 -1
- package/dist/cjs/tests/router.spec.js +298 -333
- package/dist/cjs/tests/router.spec.js.map +1 -1
- package/dist/cjs/tests/utils.js +5 -2
- package/dist/cjs/tests/utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/src/controllers.d.ts +4 -4
- package/dist/esm/src/controllers.js +3 -0
- package/dist/esm/src/controllers.js.map +1 -1
- package/dist/esm/src/decorator.d.ts +29 -94
- package/dist/esm/src/decorator.js +49 -114
- package/dist/esm/src/decorator.js.map +1 -1
- package/dist/esm/src/filter.d.ts +1 -0
- package/dist/esm/src/filter.js +11 -18
- package/dist/esm/src/filter.js.map +1 -1
- package/dist/esm/src/http.d.ts +5 -3
- package/dist/esm/src/http.js +89 -55
- package/dist/esm/src/http.js.map +1 -1
- package/dist/esm/src/kernel.d.ts +3 -3
- package/dist/esm/src/kernel.js +6 -27
- package/dist/esm/src/kernel.js.map +1 -1
- package/dist/esm/src/logger.d.ts +2 -2
- package/dist/esm/src/logger.js +4 -9
- package/dist/esm/src/logger.js.map +1 -1
- package/dist/esm/src/middleware.d.ts +5 -1
- package/dist/esm/src/middleware.js +12 -0
- package/dist/esm/src/middleware.js.map +1 -1
- package/dist/esm/src/model.d.ts +62 -2
- package/dist/esm/src/model.js +92 -24
- package/dist/esm/src/model.js.map +1 -1
- package/dist/esm/src/module.config.d.ts +3 -4
- package/dist/esm/src/module.config.js +6 -5
- package/dist/esm/src/module.config.js.map +1 -1
- package/dist/esm/src/module.d.ts +2 -3
- package/dist/esm/src/module.js +5 -3
- package/dist/esm/src/module.js.map +1 -1
- package/dist/esm/src/router.d.ts +24 -53
- package/dist/esm/src/router.js +136 -155
- package/dist/esm/src/router.js.map +1 -1
- package/dist/esm/src/static-serving.d.ts +1 -0
- package/dist/esm/src/static-serving.js +39 -32
- package/dist/esm/src/static-serving.js.map +1 -1
- package/dist/esm/src/utils.js +1 -0
- package/dist/esm/tests/filter.spec.js +14 -0
- package/dist/esm/tests/filter.spec.js.map +1 -1
- package/dist/esm/tests/middleware.spec.d.ts +1 -1
- package/dist/esm/tests/middleware.spec.js +60 -48
- package/dist/esm/tests/middleware.spec.js.map +1 -1
- package/dist/esm/tests/module.spec.js +1 -0
- package/dist/esm/tests/module.spec.js.map +1 -1
- package/dist/esm/tests/router.spec.d.ts +1 -1
- package/dist/esm/tests/router.spec.js +167 -202
- package/dist/esm/tests/router.spec.js.map +1 -1
- package/dist/esm/tests/utils.js +4 -1
- package/dist/esm/tests/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/package.json +14 -15
- package/src/controllers.ts +3 -3
- package/src/decorator.ts +42 -131
- package/src/filter.ts +0 -2
- package/src/http.ts +35 -36
- package/src/kernel.ts +4 -5
- package/src/logger.ts +2 -4
- package/src/middleware.ts +2 -1
- package/src/model.ts +80 -8
- package/src/module.config.ts +3 -6
- package/src/module.ts +2 -2
- package/src/router.ts +121 -161
- package/src/static-serving.ts +14 -12
- package/tests/middleware.spec.ts +0 -1
- package/tests/router.spec.ts +131 -133
- package/tsconfig.json +2 -1
package/src/decorator.ts
CHANGED
|
@@ -11,27 +11,25 @@
|
|
|
11
11
|
import { ClassType, isClass, urlJoin } from '@deepkit/core';
|
|
12
12
|
import {
|
|
13
13
|
ClassDecoratorResult,
|
|
14
|
-
ClassSchema,
|
|
15
14
|
createClassDecoratorContext,
|
|
16
15
|
createPropertyDecoratorContext,
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
DecoratorAndFetchSignature,
|
|
17
|
+
DualDecorator,
|
|
18
|
+
ExtractApiDataType,
|
|
19
|
+
ExtractClass,
|
|
19
20
|
isDecoratorContext,
|
|
20
|
-
isFieldDecorator,
|
|
21
|
-
JitConverterOptions,
|
|
22
21
|
mergeDecorator,
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
PropertyDecoratorFn,
|
|
23
|
+
ReceiveType,
|
|
24
|
+
resolveReceiveType,
|
|
25
|
+
SerializationOptions,
|
|
25
26
|
Serializer,
|
|
26
|
-
|
|
27
|
+
Type,
|
|
28
|
+
UnionToIntersection
|
|
27
29
|
} from '@deepkit/type';
|
|
28
30
|
import { RouteParameterResolver } from './router';
|
|
29
31
|
import { httpMiddleware, HttpMiddleware, HttpMiddlewareConfig, HttpMiddlewareFn } from './middleware';
|
|
30
32
|
|
|
31
|
-
export interface ControllerOptions {
|
|
32
|
-
name: string;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
33
|
type HttpActionMiddleware = (() => HttpMiddlewareConfig) | ClassType<HttpMiddleware> | HttpMiddlewareFn;
|
|
36
34
|
|
|
37
35
|
function isMiddlewareClassTypeOrFn(v: HttpActionMiddleware): v is ClassType<HttpMiddleware> | HttpMiddlewareFn {
|
|
@@ -99,24 +97,17 @@ export class HttpAction {
|
|
|
99
97
|
groups: string[] = [];
|
|
100
98
|
serializer?: Serializer;
|
|
101
99
|
middlewares: (() => HttpMiddlewareConfig)[] = [];
|
|
102
|
-
serializationOptions?:
|
|
103
|
-
|
|
104
|
-
parameterRegularExpressions: { [name: string]: any } = {};
|
|
100
|
+
serializationOptions?: SerializationOptions;
|
|
105
101
|
|
|
106
102
|
resolverForToken: Map<any, ClassType> = new Map();
|
|
107
103
|
resolverForParameterName: Map<string, ClassType> = new Map();
|
|
108
104
|
|
|
109
|
-
/**
|
|
110
|
-
* This is only filled when the user used @http.body() for example on an method argument.
|
|
111
|
-
*/
|
|
112
|
-
parameters: { [name: string]: HttpActionParameter } = {};
|
|
113
|
-
|
|
114
105
|
/**
|
|
115
106
|
* An arbitrary data container the user can use to store app specific settings/values.
|
|
116
107
|
*/
|
|
117
108
|
data = new Map<any, any>();
|
|
118
109
|
|
|
119
|
-
responses: { statusCode: number, description: string, type?:
|
|
110
|
+
responses: { statusCode: number, description: string, type?: Type }[] = [];
|
|
120
111
|
}
|
|
121
112
|
|
|
122
113
|
export class HttpDecorator {
|
|
@@ -196,7 +187,8 @@ export const httpClass: ClassDecoratorResult<typeof HttpDecorator> = createClass
|
|
|
196
187
|
export class HttpActionDecorator {
|
|
197
188
|
t = new HttpAction;
|
|
198
189
|
|
|
199
|
-
onDecorator(target: ClassType, property: string) {
|
|
190
|
+
onDecorator(target: ClassType, property: string | undefined, parameterIndexOrDescriptor?: any) {
|
|
191
|
+
if (!property) return;
|
|
200
192
|
this.t.methodName = property;
|
|
201
193
|
httpClass.setAction(this.t)(target);
|
|
202
194
|
}
|
|
@@ -205,15 +197,11 @@ export class HttpActionDecorator {
|
|
|
205
197
|
this.t.name = name;
|
|
206
198
|
}
|
|
207
199
|
|
|
208
|
-
setParameter(name: string, parameter: HttpActionParameter) {
|
|
209
|
-
this.t.parameters[name] = parameter;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
200
|
description(description: string) {
|
|
213
201
|
this.t.description = description;
|
|
214
202
|
}
|
|
215
203
|
|
|
216
|
-
serialization(options:
|
|
204
|
+
serialization(options: SerializationOptions) {
|
|
217
205
|
this.t.serializationOptions = options;
|
|
218
206
|
}
|
|
219
207
|
|
|
@@ -298,12 +286,12 @@ export class HttpActionDecorator {
|
|
|
298
286
|
}
|
|
299
287
|
|
|
300
288
|
OPTIONS(path: string = '') {
|
|
301
|
-
this.t.httpMethods.push('
|
|
289
|
+
this.t.httpMethods.push('OPTIONS');
|
|
302
290
|
if (path) this.t.path = path;
|
|
303
291
|
}
|
|
304
292
|
|
|
305
293
|
TRACE(path: string = '') {
|
|
306
|
-
this.t.httpMethods.push('
|
|
294
|
+
this.t.httpMethods.push('TRACE');
|
|
307
295
|
if (path) this.t.path = path;
|
|
308
296
|
}
|
|
309
297
|
|
|
@@ -319,31 +307,29 @@ export class HttpActionDecorator {
|
|
|
319
307
|
|
|
320
308
|
/**
|
|
321
309
|
* Adds additional information about what HTTP status codes are available in this route.
|
|
322
|
-
* You can add additionally a description and a
|
|
310
|
+
* You can add additionally a description and a response body type.
|
|
323
311
|
*
|
|
324
|
-
* The
|
|
312
|
+
* The type is used for serialization for responses with the given statusCode.
|
|
325
313
|
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* classType can be a schema, class, or deepkit/type t.
|
|
314
|
+
* This information is available in Deepkit API console.
|
|
329
315
|
*
|
|
330
316
|
* ```typescript
|
|
331
317
|
*
|
|
332
|
-
* @http.GET().response(200, 'All ok'
|
|
318
|
+
* @http.GET().response<boolean>(200, 'All ok')
|
|
333
319
|
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
320
|
+
* interface User {
|
|
321
|
+
* username: string;
|
|
336
322
|
* }
|
|
337
|
-
* @http.GET().response(200, 'User object'
|
|
338
|
-
*
|
|
323
|
+
* @http.GET().response<User>(200, 'User object')
|
|
339
324
|
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
325
|
+
* interface HttpErrorMessage {
|
|
326
|
+
* error: string;
|
|
327
|
+
* }
|
|
328
|
+
* @http.GET().response<HttpErrorMessage>(500, 'Error')
|
|
342
329
|
* ```
|
|
343
330
|
*/
|
|
344
|
-
response(statusCode: number, description: string = '',
|
|
345
|
-
|
|
346
|
-
this.t.responses.push({ statusCode, description, type });
|
|
331
|
+
response<T>(statusCode: number, description: string = '', type?: ReceiveType<T>) {
|
|
332
|
+
this.t.responses.push({ statusCode, description, type: type ? resolveReceiveType(type) : undefined });
|
|
347
333
|
}
|
|
348
334
|
|
|
349
335
|
/**
|
|
@@ -395,95 +381,20 @@ export class HttpActionDecorator {
|
|
|
395
381
|
resolveParameterByName(name: string, resolver: ClassType<RouteParameterResolver>) {
|
|
396
382
|
this.t.resolverForParameterName.set(name, resolver);
|
|
397
383
|
}
|
|
398
|
-
|
|
399
|
-
regexp(parameterName: string, regex: any) {
|
|
400
|
-
this.t.parameterRegularExpressions[parameterName] = regex;
|
|
401
|
-
}
|
|
402
384
|
}
|
|
403
385
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
const property = schema.getMethodProperties(propertyName)[parameterIndex];
|
|
412
|
-
this.t.name = property.name;
|
|
413
|
-
if (this.t.typePath === undefined) {
|
|
414
|
-
this.t.typePath = property.name;
|
|
415
|
-
}
|
|
416
|
-
httpAction.setParameter(property.name, this.t)(target.prototype, propertyName);
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
/**
|
|
420
|
-
* Marks the argument as body parameter. Data from the client sent in the body
|
|
421
|
-
* will be tried to parsed (JSON/form data) and deserialized to the defined type.
|
|
422
|
-
* Make sure the class type as a schema assigned.
|
|
423
|
-
*
|
|
424
|
-
* @example
|
|
425
|
-
* ```typescript
|
|
426
|
-
* class MyActionBody {
|
|
427
|
-
* @t name!: string;
|
|
428
|
-
* }
|
|
429
|
-
*
|
|
430
|
-
* class Controller {
|
|
431
|
-
* @http.GET()
|
|
432
|
-
* myAction(@http.body() body: MyActionBody) {
|
|
433
|
-
* console.log('body', body.name);
|
|
434
|
-
* }
|
|
435
|
-
* }
|
|
436
|
-
* ```
|
|
437
|
-
*/
|
|
438
|
-
body() {
|
|
439
|
-
this.t.type = 'body';
|
|
440
|
-
this.t.typePath = ''; //root
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
query(path?: string) {
|
|
444
|
-
this.t.typePath = path; //undefined === propertyName
|
|
445
|
-
this.t.type = 'query';
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
get optional() {
|
|
449
|
-
this.t.optional = true;
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
/**
|
|
454
|
-
* Marks the argument as query parameter. Data from the query string is parsed
|
|
455
|
-
* and deserialized to the defined type.
|
|
456
|
-
* Define a `path` if you want to parse a subset of the query string only.
|
|
457
|
-
*
|
|
458
|
-
* Note: Make sure the defined parameter type has optional properties,
|
|
459
|
-
* otherwise it's always required to pass a query string.
|
|
460
|
-
*
|
|
461
|
-
* @example
|
|
462
|
-
* ```typescript
|
|
463
|
-
* class MyActionQueries {
|
|
464
|
-
* @t.optional name?: string;
|
|
465
|
-
* }
|
|
466
|
-
*
|
|
467
|
-
* class Controller {
|
|
468
|
-
* @http.GET('my-action')
|
|
469
|
-
* myAction(@http.queries() query: MyActionQueries) {
|
|
470
|
-
* console.log('query', query.name);
|
|
471
|
-
* }
|
|
472
|
-
* }
|
|
473
|
-
*
|
|
474
|
-
* // Open via, e.g.
|
|
475
|
-
* // -> /my-action?name=Peter
|
|
476
|
-
* ```
|
|
477
|
-
*
|
|
478
|
-
*/
|
|
479
|
-
queries(path: string = '') {
|
|
480
|
-
this.t.typePath = path; //'' === root
|
|
481
|
-
this.t.type = 'queries';
|
|
482
|
-
}
|
|
483
|
-
}
|
|
386
|
+
//this workaround is necessary since generic functions (necessary for response<T>) are lost during a mapped type and changed ReturnType
|
|
387
|
+
type HttpActionFluidDecorator<T, D extends Function> = {
|
|
388
|
+
[name in keyof T]: name extends 'response' ? <T2>(statusCode: number, description?: string, type?: ReceiveType<T2>) => D & HttpActionFluidDecorator<T, D>
|
|
389
|
+
: T[name] extends (...args: infer K) => any ? (...args: K) => D & HttpActionFluidDecorator<T, D>
|
|
390
|
+
: D & HttpActionFluidDecorator<T, D> & { _data: ExtractApiDataType<T> };
|
|
391
|
+
};
|
|
392
|
+
type HttpActionPropertyDecoratorResult = HttpActionFluidDecorator<ExtractClass<typeof HttpActionDecorator>, PropertyDecoratorFn> & DecoratorAndFetchSignature<typeof HttpActionDecorator, PropertyDecoratorFn>;
|
|
484
393
|
|
|
485
|
-
export const httpAction:
|
|
394
|
+
export const httpAction: HttpActionPropertyDecoratorResult = createPropertyDecoratorContext(HttpActionDecorator);
|
|
486
395
|
|
|
487
|
-
|
|
396
|
+
//this workaround is necessary since generic functions are lost during a mapped type and changed ReturnType
|
|
397
|
+
type HttpMerge<U> = { [K in keyof U]: K extends 'response' ? <T2>(statusCode: number, description?: string, type?: ReceiveType<T2>) => PropertyDecoratorFn & U : U[K] extends ((...a: infer A) => infer R) ? R extends DualDecorator ? (...a: A) => PropertyDecoratorFn & R & U : (...a: A) => R : never };
|
|
398
|
+
type MergedHttp<T extends any[]> = HttpMerge<Omit<UnionToIntersection<T[number]>, '_fetch' | 't'>>
|
|
488
399
|
|
|
489
|
-
export const http = mergeDecorator(httpClass, httpAction,
|
|
400
|
+
export const http: MergedHttp<[typeof httpClass, typeof httpAction]> = mergeDecorator(httpClass, httpAction) as any as MergedHttp<[typeof httpClass, typeof httpAction]>;
|
package/src/filter.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ClassType } from '@deepkit/core';
|
|
2
2
|
import { AppModule } from '@deepkit/app';
|
|
3
3
|
import { RouteConfig, Router } from './router';
|
|
4
|
-
import { injectable } from '@deepkit/injector';
|
|
5
4
|
|
|
6
5
|
export interface HttpRouteFilterRoute {
|
|
7
6
|
path?: string;
|
|
@@ -57,7 +56,6 @@ function match(routeConfig: RouteConfig, route: HttpRouteFilterRoute): boolean {
|
|
|
57
56
|
return false;
|
|
58
57
|
}
|
|
59
58
|
|
|
60
|
-
@injectable
|
|
61
59
|
export class HttpRouterFilterResolver {
|
|
62
60
|
constructor(protected router: Router) {
|
|
63
61
|
}
|
package/src/http.ts
CHANGED
|
@@ -8,17 +8,17 @@
|
|
|
8
8
|
* You should have received a copy of the MIT License along with this program.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { asyncOperation, ClassType, CustomError, getClassName } from '@deepkit/core';
|
|
12
|
-
import { getClassTypeFromInstance, getPropertyClassToXFunction, isClassInstance, isRegisteredEntity, jsonSerializer, ValidationFailed } from '@deepkit/type';
|
|
11
|
+
import { asyncOperation, ClassType, CustomError, getClassName, getClassTypeFromInstance, isClassInstance } from '@deepkit/core';
|
|
13
12
|
import { OutgoingHttpHeaders, ServerResponse } from 'http';
|
|
14
13
|
import { eventDispatcher } from '@deepkit/event';
|
|
15
14
|
import { HttpRequest, HttpResponse } from './model';
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
15
|
+
import { InjectorContext } from '@deepkit/injector';
|
|
16
|
+
import { LoggerInterface } from '@deepkit/logger';
|
|
18
17
|
import { RouteConfig, RouteParameterResolverForInjector, Router } from './router';
|
|
19
18
|
import { createWorkflow, WorkflowEvent } from '@deepkit/workflow';
|
|
20
19
|
import type { ElementStruct, render } from '@deepkit/template';
|
|
21
20
|
import { FrameCategory, Stopwatch } from '@deepkit/stopwatch';
|
|
21
|
+
import { getSerializeFunction, hasTypeInformation, ReflectionKind, resolveReceiveType, SerializationError, serialize, serializer, ValidationError } from '@deepkit/type';
|
|
22
22
|
|
|
23
23
|
export function isElementStruct(v: any): v is ElementStruct {
|
|
24
24
|
return 'object' === typeof v && v.hasOwnProperty('render') && v.hasOwnProperty('attributes') && !v.slice;
|
|
@@ -350,7 +350,6 @@ export interface HttpResultFormatterContext {
|
|
|
350
350
|
route?: RouteConfig;
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
@injectable
|
|
354
353
|
export class HttpResultFormatter {
|
|
355
354
|
protected jsonContentType: string = 'application/json; charset=utf-8';
|
|
356
355
|
protected htmlContentType: string = 'text/html; charset=utf-8';
|
|
@@ -406,14 +405,9 @@ export class HttpResultFormatter {
|
|
|
406
405
|
handleTypeEntity<T>(classType: ClassType<T>, instance: T, context: HttpResultFormatterContext, route?: RouteConfig): void {
|
|
407
406
|
this.setContentTypeIfNotSetAlready(context.response, this.jsonContentType);
|
|
408
407
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
instance,
|
|
413
|
-
route ? route.serializationOptions : undefined
|
|
414
|
-
)
|
|
415
|
-
)
|
|
416
|
-
);
|
|
408
|
+
const serializerToUse = route && route?.serializer ? route.serializer : serializer;
|
|
409
|
+
|
|
410
|
+
context.response.end(JSON.stringify(serialize(instance, route ? route.serializationOptions : undefined, serializerToUse, undefined, resolveReceiveType(classType))));
|
|
417
411
|
}
|
|
418
412
|
|
|
419
413
|
handleBinary(result: Uint8Array, context: HttpResultFormatterContext): void {
|
|
@@ -441,7 +435,7 @@ export class HttpResultFormatter {
|
|
|
441
435
|
} else {
|
|
442
436
|
if (isClassInstance(result)) {
|
|
443
437
|
const classType = getClassTypeFromInstance(result);
|
|
444
|
-
if (
|
|
438
|
+
if (hasTypeInformation(classType)) {
|
|
445
439
|
this.handleTypeEntity(classType, result, context);
|
|
446
440
|
return;
|
|
447
441
|
}
|
|
@@ -452,13 +446,12 @@ export class HttpResultFormatter {
|
|
|
452
446
|
}
|
|
453
447
|
}
|
|
454
448
|
|
|
455
|
-
@injectable
|
|
456
449
|
export class HttpListener {
|
|
457
450
|
constructor(
|
|
458
451
|
protected router: Router,
|
|
459
|
-
protected logger:
|
|
452
|
+
protected logger: LoggerInterface,
|
|
460
453
|
protected resultFormatter: HttpResultFormatter,
|
|
461
|
-
|
|
454
|
+
protected stopwatch?: Stopwatch,
|
|
462
455
|
) {
|
|
463
456
|
}
|
|
464
457
|
|
|
@@ -573,7 +566,7 @@ export class HttpListener {
|
|
|
573
566
|
try {
|
|
574
567
|
event.parameters = await event.parameterResolver(event.injectorContext);
|
|
575
568
|
event.next('controller', new HttpControllerEvent(event.injectorContext, event.request, event.response, event.parameters, event.route));
|
|
576
|
-
} catch (error) {
|
|
569
|
+
} catch (error: any) {
|
|
577
570
|
event.next('parametersFailed', new HttpControllerErrorEvent(event.injectorContext, event.request, event.response, event.route, error));
|
|
578
571
|
}
|
|
579
572
|
}
|
|
@@ -606,7 +599,7 @@ export class HttpListener {
|
|
|
606
599
|
const responseEvent = new HttpResponseEvent(event.injectorContext, event.request, event.response, result, event.route);
|
|
607
600
|
responseEvent.controllerActionTime = Date.now() - start;
|
|
608
601
|
event.next('response', responseEvent);
|
|
609
|
-
} catch (error) {
|
|
602
|
+
} catch (error: any) {
|
|
610
603
|
if (frame) frame.end();
|
|
611
604
|
if (error instanceof HttpAccessDeniedError) {
|
|
612
605
|
event.next('accessDenied', new HttpAccessDeniedEvent(event.injectorContext, event.request, event.response, event.route));
|
|
@@ -625,7 +618,16 @@ export class HttpListener {
|
|
|
625
618
|
if (event.response.finished) return;
|
|
626
619
|
if (event.sent) return;
|
|
627
620
|
|
|
628
|
-
if (event.error instanceof
|
|
621
|
+
if (event.error instanceof SerializationError) {
|
|
622
|
+
event.send(new JSONResponse({
|
|
623
|
+
message: event.error.message,
|
|
624
|
+
errors: [{
|
|
625
|
+
path: event.error.path,
|
|
626
|
+
code: 'serialization',
|
|
627
|
+
message: event.error.originalMessage,
|
|
628
|
+
}]
|
|
629
|
+
}, 400).disableAutoSerializing());
|
|
630
|
+
} else if (event.error instanceof ValidationError) {
|
|
629
631
|
event.send(new JSONResponse({
|
|
630
632
|
message: event.error.message,
|
|
631
633
|
errors: event.error.errors
|
|
@@ -642,7 +644,7 @@ export class HttpListener {
|
|
|
642
644
|
if (event.response.finished) return;
|
|
643
645
|
if (event.sent) return;
|
|
644
646
|
|
|
645
|
-
if (event.error instanceof
|
|
647
|
+
if (event.error instanceof ValidationError) {
|
|
646
648
|
event.send(new JSONResponse({
|
|
647
649
|
message: event.error.message,
|
|
648
650
|
errors: event.error.errors
|
|
@@ -672,28 +674,25 @@ export class HttpListener {
|
|
|
672
674
|
if (event.result instanceof HtmlResponse || event.result instanceof ServerResponse || event.result instanceof Redirect) {
|
|
673
675
|
// don't do anything
|
|
674
676
|
} else if (event.result instanceof JSONResponse) {
|
|
675
|
-
const schema = (event.result._statusCode && event.route.getSchemaForResponse(event.result._statusCode)) || event.route.
|
|
677
|
+
const schema = (event.result._statusCode && event.route.getSchemaForResponse(event.result._statusCode)) || event.route.returnType;
|
|
676
678
|
|
|
677
679
|
if (!schema || !event.result.autoSerializing) return;
|
|
678
680
|
|
|
679
|
-
event.
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
event.route && event.route.serializer ? event.route.serializer : jsonSerializer
|
|
687
|
-
)(event.result, event.route.serializationOptions);
|
|
681
|
+
const serializerToUse = event.route && event.route.serializer ? event.route.serializer : serializer;
|
|
682
|
+
const serialize = getSerializeFunction(schema, serializerToUse.serializeRegistry);
|
|
683
|
+
event.result.json = serialize(event.result.json, event.route.serializationOptions);
|
|
684
|
+
} else if (event.route.returnType && event.route.returnType.kind !== ReflectionKind.any) {
|
|
685
|
+
const serializerToUse = event.route && event.route.serializer ? event.route.serializer : serializer;
|
|
686
|
+
const serialize = getSerializeFunction(event.route.returnType, serializerToUse.serializeRegistry);
|
|
687
|
+
event.result = serialize(event.result, event.route.serializationOptions);
|
|
688
688
|
} else {
|
|
689
689
|
const schema = event.route.getSchemaForResponse(200);
|
|
690
690
|
if (!schema) return;
|
|
691
|
-
event.result = getPropertyClassToXFunction(
|
|
692
|
-
schema,
|
|
693
|
-
event.route && event.route.serializer ? event.route.serializer : jsonSerializer
|
|
694
|
-
)(event.result, event.route.serializationOptions);
|
|
695
|
-
}
|
|
696
691
|
|
|
692
|
+
const serializerToUse = event.route && event.route.serializer ? event.route.serializer : serializer;
|
|
693
|
+
const serialize = getSerializeFunction(schema, serializerToUse.serializeRegistry);
|
|
694
|
+
event.result = serialize(event.result, event.route.serializationOptions);
|
|
695
|
+
}
|
|
697
696
|
}
|
|
698
697
|
|
|
699
698
|
@eventDispatcher.listen(httpWorkflow.onResponse, 100)
|
package/src/kernel.ts
CHANGED
|
@@ -1,20 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { InjectorContext } from '@deepkit/injector';
|
|
2
2
|
import { Router } from './router';
|
|
3
3
|
import { EventDispatcher } from '@deepkit/event';
|
|
4
|
-
import {
|
|
4
|
+
import { LoggerInterface } from '@deepkit/logger';
|
|
5
5
|
import { HttpRequest, HttpResponse, MemoryHttpResponse, RequestBuilder } from './model';
|
|
6
6
|
import { HttpRequestEvent, httpWorkflow } from './http';
|
|
7
7
|
import { FrameCategory, Stopwatch } from '@deepkit/stopwatch';
|
|
8
8
|
import { unlink } from 'fs';
|
|
9
9
|
|
|
10
|
-
@injectable
|
|
11
10
|
export class HttpKernel {
|
|
12
11
|
constructor(
|
|
13
12
|
protected router: Router,
|
|
14
13
|
protected eventDispatcher: EventDispatcher,
|
|
15
14
|
protected injectorContext: InjectorContext,
|
|
16
|
-
protected logger:
|
|
17
|
-
|
|
15
|
+
protected logger: LoggerInterface,
|
|
16
|
+
protected stopwatch?: Stopwatch,
|
|
18
17
|
) {
|
|
19
18
|
|
|
20
19
|
}
|
package/src/logger.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Logger } from '@deepkit/logger';
|
|
1
|
+
import { LoggerInterface } from '@deepkit/logger';
|
|
3
2
|
import { eventDispatcher } from '@deepkit/event';
|
|
4
3
|
import { httpWorkflow } from './http';
|
|
5
4
|
|
|
6
|
-
@injectable
|
|
7
5
|
export class HttpLogger {
|
|
8
|
-
constructor(private logger:
|
|
6
|
+
constructor(private logger: LoggerInterface) {
|
|
9
7
|
}
|
|
10
8
|
|
|
11
9
|
@eventDispatcher.listen(httpWorkflow.onResponse, 101) //101 is right after 100 default listener
|
package/src/middleware.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { ClassType, isClass } from '@deepkit/core';
|
|
|
2
2
|
import { AppModule } from '@deepkit/app';
|
|
3
3
|
import { createFreeDecoratorContext } from '@deepkit/type';
|
|
4
4
|
import { HttpRequest, HttpResponse } from './model';
|
|
5
|
+
import { InjectorModule } from '@deepkit/injector';
|
|
5
6
|
|
|
6
7
|
export type HttpMiddlewareFn = (req: HttpRequest, res: HttpResponse, next: (err?: any) => void) => void | Promise<void>;
|
|
7
8
|
|
|
@@ -36,7 +37,7 @@ export class HttpMiddlewareConfig {
|
|
|
36
37
|
|
|
37
38
|
timeout: number = 4_000;
|
|
38
39
|
|
|
39
|
-
modules:
|
|
40
|
+
modules: InjectorModule<any>[] = [];
|
|
40
41
|
|
|
41
42
|
selfModule: boolean = false;
|
|
42
43
|
|
package/src/model.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { IncomingMessage, ServerResponse } from 'http';
|
|
|
12
12
|
import { UploadedFile } from './router';
|
|
13
13
|
import * as querystring from 'querystring';
|
|
14
14
|
import { Writable } from 'stream';
|
|
15
|
+
import { metaAnnotation, ReflectionKind, Type, ValidationErrorItem } from '@deepkit/type';
|
|
15
16
|
|
|
16
17
|
export class HttpResponse extends ServerResponse {
|
|
17
18
|
status(code: number) {
|
|
@@ -23,6 +24,68 @@ export class HttpResponse extends ServerResponse {
|
|
|
23
24
|
export type HttpRequestQuery = { [name: string]: string };
|
|
24
25
|
export type HttpRequestResolvedParameters = { [name: string]: any };
|
|
25
26
|
|
|
27
|
+
export class BodyValidationError {
|
|
28
|
+
constructor(
|
|
29
|
+
public readonly errors: ValidationErrorItem[] = []
|
|
30
|
+
) {
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
hasErrors(prefix?: string): boolean {
|
|
34
|
+
return this.getErrors(prefix).length > 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getErrors(prefix?: string): ValidationErrorItem[] {
|
|
38
|
+
if (prefix) return this.errors.filter(v => v.path.startsWith(prefix));
|
|
39
|
+
|
|
40
|
+
return this.errors;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
getErrorsForPath(path: string): ValidationErrorItem[] {
|
|
44
|
+
return this.errors.filter(v => v.path === path);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getErrorMessageForPath(path: string): string {
|
|
48
|
+
return this.getErrorsForPath(path).map(v => v.message).join(', ');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class ValidatedBody<T> {
|
|
53
|
+
constructor(public error: BodyValidationError, public value?: T) {
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
valid(): this is { value: T } {
|
|
57
|
+
return this.value !== undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type HttpBody<T> = T & { __meta?: ['httpBody'] };
|
|
62
|
+
export type HttpBodyValidation<T> = ValidatedBody<T> & { __meta?: ['httpBodyValidation'] };
|
|
63
|
+
export type HttpQuery<T, Options extends { name?: string } = {}> = T & { __meta?: ['httpQuery', Options] };
|
|
64
|
+
export type HttpQueries<T, Options extends { name?: string } = {}> = T & { __meta?: ['httpQueries', Options] };
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* For all parameters used in the URL path, a regular expression of /[^/]+/ is used. To change that, use getRegExp.
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```typescript
|
|
71
|
+
*
|
|
72
|
+
* class Controller {
|
|
73
|
+
* @http.GET('/user/:username')
|
|
74
|
+
* route(username: HttpRegExp<string, '.*'>) {
|
|
75
|
+
* //username is string
|
|
76
|
+
* }
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export type HttpRegExp<T, Pattern extends string> = T & { __meta?: ['httpRegExp', Pattern] };
|
|
81
|
+
|
|
82
|
+
export function getRegExp(type: Type): string | undefined {
|
|
83
|
+
const options = metaAnnotation.getForName(type, 'httpRegExp');
|
|
84
|
+
if (!options || !options[0]) return;
|
|
85
|
+
if (options[0].kind !== ReflectionKind.literal || 'string' !== typeof options[0].literal) return;
|
|
86
|
+
return options[0].literal;
|
|
87
|
+
}
|
|
88
|
+
|
|
26
89
|
export class RequestBuilder {
|
|
27
90
|
protected contentBuffer: Buffer = Buffer.alloc(0);
|
|
28
91
|
protected _headers: { [name: string]: string } = {};
|
|
@@ -48,8 +111,9 @@ export class RequestBuilder {
|
|
|
48
111
|
const bodyContent = this.contentBuffer;
|
|
49
112
|
|
|
50
113
|
const writable = new Writable({
|
|
51
|
-
write(chunk, encoding, callback) {
|
|
52
|
-
|
|
114
|
+
write(chunk, encoding, callback) {
|
|
115
|
+
-
|
|
116
|
+
callback();
|
|
53
117
|
},
|
|
54
118
|
writev(chunks, callback) {
|
|
55
119
|
callback();
|
|
@@ -69,7 +133,7 @@ export class RequestBuilder {
|
|
|
69
133
|
this.push(bodyContent);
|
|
70
134
|
process.nextTick(() => {
|
|
71
135
|
this.emit('end');
|
|
72
|
-
})
|
|
136
|
+
});
|
|
73
137
|
this.done = true;
|
|
74
138
|
}
|
|
75
139
|
}
|
|
@@ -136,6 +200,14 @@ export class HttpRequest extends IncomingMessage {
|
|
|
136
200
|
return new RequestBuilder(path, 'OPTIONS');
|
|
137
201
|
}
|
|
138
202
|
|
|
203
|
+
static TRACE(path: string): RequestBuilder {
|
|
204
|
+
return new RequestBuilder(path, 'TRACE');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static HEAD(path: string): RequestBuilder {
|
|
208
|
+
return new RequestBuilder(path, 'HEAD');
|
|
209
|
+
}
|
|
210
|
+
|
|
139
211
|
static PATCH(path: string): RequestBuilder {
|
|
140
212
|
return new RequestBuilder(path, 'PATCH');
|
|
141
213
|
}
|
|
@@ -157,7 +229,7 @@ export class HttpRequest extends IncomingMessage {
|
|
|
157
229
|
}
|
|
158
230
|
|
|
159
231
|
getRemoteAddress(): string {
|
|
160
|
-
return this.
|
|
232
|
+
return this.socket.remoteAddress || '';
|
|
161
233
|
}
|
|
162
234
|
}
|
|
163
235
|
|
|
@@ -168,7 +240,7 @@ export class MemoryHttpResponse extends HttpResponse {
|
|
|
168
240
|
const json = this.bodyString;
|
|
169
241
|
try {
|
|
170
242
|
return JSON.parse(json);
|
|
171
|
-
}
|
|
243
|
+
} catch (error: any) {
|
|
172
244
|
throw new Error(`Could not parse JSON: ${error.message}, body: ${json}`);
|
|
173
245
|
}
|
|
174
246
|
}
|
|
@@ -181,7 +253,7 @@ export class MemoryHttpResponse extends HttpResponse {
|
|
|
181
253
|
chunk: any,
|
|
182
254
|
encoding: any,
|
|
183
255
|
callback?: any
|
|
184
|
-
):
|
|
256
|
+
): any {
|
|
185
257
|
if (typeof encoding === 'function') {
|
|
186
258
|
callback = encoding;
|
|
187
259
|
encoding = null;
|
|
@@ -198,7 +270,7 @@ export class MemoryHttpResponse extends HttpResponse {
|
|
|
198
270
|
return true;
|
|
199
271
|
}
|
|
200
272
|
|
|
201
|
-
end(chunk: any, encoding?: any, callback?: any):
|
|
273
|
+
end(chunk: any, encoding?: any, callback?: any): any {
|
|
202
274
|
if (typeof chunk === 'function') {
|
|
203
275
|
callback = chunk;
|
|
204
276
|
chunk = null;
|
|
@@ -214,7 +286,7 @@ export class MemoryHttpResponse extends HttpResponse {
|
|
|
214
286
|
}
|
|
215
287
|
this.body = Buffer.concat([this.body, chunk]);
|
|
216
288
|
}
|
|
217
|
-
super.end(chunk, encoding, callback);
|
|
289
|
+
return super.end(chunk, encoding, callback);
|
|
218
290
|
// if (callback) callback();
|
|
219
291
|
}
|
|
220
292
|
}
|
package/src/module.config.ts
CHANGED
package/src/module.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HttpListener, HttpResultFormatter, httpWorkflow } from './http';
|
|
2
|
-
import {
|
|
2
|
+
import { HttpConfig } from './module.config';
|
|
3
3
|
import { AppModule, createModule } from '@deepkit/app';
|
|
4
4
|
import { Router } from './router';
|
|
5
5
|
import { HttpKernel } from './kernel';
|
|
@@ -12,7 +12,7 @@ import { ClassType } from '@deepkit/core';
|
|
|
12
12
|
import { httpClass } from './decorator';
|
|
13
13
|
|
|
14
14
|
export class HttpModule extends createModule({
|
|
15
|
-
config:
|
|
15
|
+
config: HttpConfig,
|
|
16
16
|
providers: [
|
|
17
17
|
Router,
|
|
18
18
|
HttpKernel,
|