@deepkit/http 1.0.1-alpha.97 → 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.
- package/CHANGELOG.md +240 -0
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/src/controllers.js +1 -1
- package/dist/cjs/src/decorator.d.ts +7 -6
- package/dist/cjs/src/decorator.js +55 -42
- package/dist/cjs/src/decorator.js.map +1 -1
- package/dist/cjs/src/filter.js +4 -4
- package/dist/cjs/src/filter.js.map +1 -1
- package/dist/cjs/src/http-app.d.ts +8 -0
- package/dist/cjs/src/http-app.js +16 -0
- package/dist/cjs/src/http-app.js.map +1 -0
- package/dist/cjs/src/http.d.ts +2 -4
- package/dist/cjs/src/http.js +63 -51
- package/dist/cjs/src/http.js.map +1 -1
- package/dist/cjs/src/kernel.d.ts +43 -3
- package/dist/cjs/src/kernel.js +58 -8
- package/dist/cjs/src/kernel.js.map +1 -1
- package/dist/cjs/src/logger.js +2 -2
- package/dist/cjs/src/logger.js.map +1 -1
- package/dist/cjs/src/middleware.d.ts +2 -2
- package/dist/cjs/src/middleware.js +5 -6
- package/dist/cjs/src/middleware.js.map +1 -1
- package/dist/cjs/src/model.d.ts +154 -31
- package/dist/cjs/src/model.js +83 -42
- package/dist/cjs/src/model.js.map +1 -1
- package/dist/cjs/src/module.config.d.ts +67 -2
- package/dist/cjs/src/module.config.js +2 -2
- package/dist/cjs/src/module.config.js.map +1 -1
- package/dist/cjs/src/module.js +18 -13
- package/dist/cjs/src/module.js.map +1 -1
- package/dist/cjs/src/request-parser.d.ts +2 -1
- package/dist/cjs/src/request-parser.js +138 -60
- package/dist/cjs/src/request-parser.js.map +1 -1
- package/dist/cjs/src/router.d.ts +12 -6
- package/dist/cjs/src/router.js +40 -28
- package/dist/cjs/src/router.js.map +1 -1
- package/dist/cjs/src/static-serving.d.ts +4 -0
- package/dist/cjs/src/static-serving.js +44 -40
- package/dist/cjs/src/static-serving.js.map +1 -1
- package/dist/cjs/src/utils.js +1 -2
- package/dist/cjs/src/utils.js.map +1 -1
- package/dist/cjs/tsconfig.tsbuildinfo +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/src/controllers.js +2 -2
- package/dist/esm/src/controllers.js.map +1 -1
- package/dist/esm/src/decorator.d.ts +7 -6
- package/dist/esm/src/decorator.js +66 -54
- package/dist/esm/src/decorator.js.map +1 -1
- package/dist/esm/src/filter.js +5 -5
- package/dist/esm/src/filter.js.map +1 -1
- package/dist/esm/src/http-app.d.ts +8 -0
- package/dist/esm/src/http-app.js +12 -0
- package/dist/esm/src/http-app.js.map +1 -0
- package/dist/esm/src/http.d.ts +2 -4
- package/dist/esm/src/http.js +65 -53
- package/dist/esm/src/http.js.map +1 -1
- package/dist/esm/src/kernel.d.ts +43 -3
- package/dist/esm/src/kernel.js +60 -10
- package/dist/esm/src/kernel.js.map +1 -1
- package/dist/esm/src/logger.js +2 -2
- package/dist/esm/src/logger.js.map +1 -1
- package/dist/esm/src/middleware.d.ts +2 -2
- package/dist/esm/src/middleware.js +6 -7
- package/dist/esm/src/middleware.js.map +1 -1
- package/dist/esm/src/model.d.ts +154 -31
- package/dist/esm/src/model.js +61 -32
- package/dist/esm/src/model.js.map +1 -1
- package/dist/esm/src/module.config.d.ts +67 -2
- package/dist/esm/src/module.config.js +2 -2
- package/dist/esm/src/module.config.js.map +1 -1
- package/dist/esm/src/module.js +20 -15
- package/dist/esm/src/module.js.map +1 -1
- package/dist/esm/src/request-parser.d.ts +2 -1
- package/dist/esm/src/request-parser.js +139 -61
- package/dist/esm/src/request-parser.js.map +1 -1
- package/dist/esm/src/router.d.ts +12 -6
- package/dist/esm/src/router.js +46 -36
- package/dist/esm/src/router.js.map +1 -1
- package/dist/esm/src/static-serving.d.ts +4 -0
- package/dist/esm/src/static-serving.js +41 -39
- package/dist/esm/src/static-serving.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/index.ts +1 -0
- package/package.json +23 -16
- package/src/decorator.ts +20 -2
- package/src/http-app.ts +11 -0
- package/src/http.ts +30 -10
- package/src/kernel.ts +81 -9
- package/src/middleware.ts +1 -1
- package/src/model.ts +177 -17
- package/src/module.config.ts +75 -1
- package/src/module.ts +20 -12
- package/src/request-parser.ts +147 -62
- package/src/router.ts +36 -10
- package/src/static-serving.ts +49 -38
- package/tsconfig.json +9 -3
- package/tsconfig.spec.json +8 -0
package/src/request-parser.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
29
|
+
|
|
30
|
+
const form = formidable(Object.assign(options as Options, {
|
|
33
31
|
multiples: true,
|
|
34
32
|
}));
|
|
35
|
-
return asyncOperation((resolve, reject) => {
|
|
36
|
-
function parseData(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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)
|
|
57
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
121
|
+
return typeAnnotation.getType(this.parameter.type, 'httpHeader') !== undefined;
|
|
105
122
|
}
|
|
106
123
|
|
|
107
124
|
get query() {
|
|
108
|
-
return
|
|
125
|
+
return typeAnnotation.getType(this.parameter.type, 'httpQuery') !== undefined;
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
get queries() {
|
|
112
|
-
return
|
|
129
|
+
return typeAnnotation.getType(this.parameter.type, 'httpQueries') !== undefined;
|
|
113
130
|
}
|
|
114
131
|
|
|
115
132
|
get typePath(): string | undefined {
|
|
116
|
-
const typeOptions =
|
|
117
|
-
||
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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
|
|
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())
|
|
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
|
|
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: ${
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
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 {
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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([], []),
|
|
585
|
+
})), new Logger([], []), config, middlewareRegistry);
|
|
560
586
|
}
|
|
561
587
|
|
|
562
588
|
protected getRouteCode(compiler: CompilerContext, routeConfig: RouteConfig): string {
|
package/src/static-serving.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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": "
|
|
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"
|