@carno.js/core 0.2.8 → 0.2.10
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/dist/Carno.d.ts +10 -8
- package/dist/Carno.js +31 -33
- package/dist/commons/decorators/index.d.ts +1 -0
- package/dist/commons/decorators/index.js +1 -0
- package/dist/commons/decorators/validation.decorator.d.ts +32 -0
- package/dist/commons/decorators/validation.decorator.js +40 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -1
- package/dist/container/InjectorService.d.ts +3 -1
- package/dist/container/InjectorService.js +4 -3
- package/dist/container/MethodInvoker.d.ts +3 -2
- package/dist/container/MethodInvoker.js +4 -17
- package/dist/container/middleware.resolver.js +6 -6
- package/dist/domain/BaseContext.d.ts +15 -0
- package/dist/domain/Context.d.ts +45 -24
- package/dist/domain/Context.js +110 -89
- package/dist/domain/FastContext.d.ts +34 -0
- package/dist/domain/FastContext.js +59 -0
- package/dist/domain/cors-headers-cache.d.ts +2 -0
- package/dist/domain/cors-headers-cache.js +44 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/route/FastPathExecutor.d.ts +10 -2
- package/dist/route/FastPathExecutor.js +43 -12
- package/dist/route/JITCompiler.d.ts +25 -1
- package/dist/route/JITCompiler.js +205 -98
- package/dist/route/ParamResolverFactory.d.ts +0 -5
- package/dist/route/ParamResolverFactory.js +0 -40
- package/dist/route/RouteCompiler.d.ts +3 -4
- package/dist/route/RouteCompiler.js +2 -54
- package/dist/route/RouteExecutor.js +18 -1
- package/dist/route/memoirist.d.ts +3 -0
- package/dist/route/memoirist.js +33 -3
- package/dist/utils/ValidationCache.d.ts +2 -0
- package/dist/utils/ValidationCache.js +10 -2
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/validation/ValidatorAdapter.d.ts +66 -0
- package/dist/validation/ValidatorAdapter.js +20 -0
- package/dist/validation/adapters/ClassValidatorAdapter.d.ts +23 -0
- package/dist/validation/adapters/ClassValidatorAdapter.js +47 -0
- package/dist/validation/adapters/ZodAdapter.d.ts +14 -0
- package/dist/validation/adapters/ZodAdapter.js +56 -0
- package/dist/validation/adapters/index.d.ts +4 -0
- package/dist/validation/adapters/index.js +7 -0
- package/dist/validation/index.d.ts +3 -0
- package/dist/validation/index.js +20 -0
- package/package.json +17 -6
- package/dist/Cheetah.d.ts +0 -65
- package/dist/Cheetah.js +0 -307
- package/dist/default-routes-cheetah.d.ts +0 -3
- package/dist/default-routes-cheetah.js +0 -29
- package/dist/domain/CheetahClosure.d.ts +0 -1
- package/dist/domain/CheetahMiddleware.d.ts +0 -5
- package/dist/domain/CheetahMiddleware.js +0 -2
- package/dist/services/request-logger.service.d.ts +0 -15
- package/dist/services/request-logger.service.js +0 -50
- package/dist/utils/isClassValidator.d.ts +0 -6
- package/dist/utils/isClassValidator.js +0 -13
- /package/dist/domain/{CheetahClosure.js → BaseContext.js} +0 -0
|
@@ -2,137 +2,244 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.compileRouteHandler = compileRouteHandler;
|
|
4
4
|
exports.compileValidatedHandler = compileValidatedHandler;
|
|
5
|
+
const TEXT_HEADERS = Object.freeze({
|
|
6
|
+
'Content-Type': 'text/html'
|
|
7
|
+
});
|
|
8
|
+
const JSON_HEADERS = Object.freeze({
|
|
9
|
+
'Content-Type': 'application/json'
|
|
10
|
+
});
|
|
11
|
+
function isBodyInitResult(result) {
|
|
12
|
+
if (!result) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
if (result instanceof ReadableStream) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (result instanceof Blob || result instanceof ArrayBuffer) {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
if (ArrayBuffer.isView(result)) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
return result instanceof FormData || result instanceof URLSearchParams;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Cria handler inline com response building integrado.
|
|
28
|
+
* Usado quando não há parâmetros.
|
|
29
|
+
*/
|
|
30
|
+
function createInlineResponseHandler(handler, isAsync) {
|
|
31
|
+
if (isAsync) {
|
|
32
|
+
return async (c) => {
|
|
33
|
+
const r = await handler();
|
|
34
|
+
const s = c.getResponseStatus() || 200;
|
|
35
|
+
if (typeof r === 'string')
|
|
36
|
+
return new Response(r, { status: s, headers: TEXT_HEADERS });
|
|
37
|
+
if (r instanceof Response)
|
|
38
|
+
return r;
|
|
39
|
+
if (r == null)
|
|
40
|
+
return new Response('', { status: s, headers: TEXT_HEADERS });
|
|
41
|
+
if (typeof r === 'number' || typeof r === 'boolean') {
|
|
42
|
+
return new Response(String(r), { status: s, headers: TEXT_HEADERS });
|
|
43
|
+
}
|
|
44
|
+
if (isBodyInitResult(r)) {
|
|
45
|
+
return new Response(r, { status: s });
|
|
46
|
+
}
|
|
47
|
+
return new Response(JSON.stringify(r), { status: s, headers: JSON_HEADERS });
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return (c) => {
|
|
51
|
+
const r = handler();
|
|
52
|
+
const s = c.getResponseStatus() || 200;
|
|
53
|
+
if (typeof r === 'string')
|
|
54
|
+
return new Response(r, { status: s, headers: TEXT_HEADERS });
|
|
55
|
+
if (r instanceof Response)
|
|
56
|
+
return r;
|
|
57
|
+
if (r == null)
|
|
58
|
+
return new Response('', { status: s, headers: TEXT_HEADERS });
|
|
59
|
+
if (typeof r === 'number' || typeof r === 'boolean') {
|
|
60
|
+
return new Response(String(r), { status: s, headers: TEXT_HEADERS });
|
|
61
|
+
}
|
|
62
|
+
if (isBodyInitResult(r)) {
|
|
63
|
+
return new Response(r, { status: s });
|
|
64
|
+
}
|
|
65
|
+
return new Response(JSON.stringify(r), { status: s, headers: JSON_HEADERS });
|
|
66
|
+
};
|
|
67
|
+
}
|
|
5
68
|
function escapeKey(key) {
|
|
6
|
-
return key.replace(/['"\\]/g, '\\$&');
|
|
69
|
+
return key.replace(/['\"\\]/g, '\\$&');
|
|
7
70
|
}
|
|
8
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Gera expressão de acesso a parâmetro inline.
|
|
73
|
+
* Otimizado para evitar chamadas de função quando possível.
|
|
74
|
+
*/
|
|
75
|
+
function buildArgExpression(param) {
|
|
9
76
|
const key = param.key ? escapeKey(param.key) : undefined;
|
|
10
77
|
switch (param.type) {
|
|
11
78
|
case 'param':
|
|
12
|
-
return key ? `
|
|
79
|
+
return key ? `c.param['${key}']` : 'c.param';
|
|
13
80
|
case 'query':
|
|
14
|
-
return key ? `
|
|
81
|
+
return key ? `c.query['${key}']` : 'c.query';
|
|
15
82
|
case 'body':
|
|
16
|
-
return key ? `
|
|
83
|
+
return key ? `c.body['${key}']` : 'c.body';
|
|
17
84
|
case 'headers':
|
|
18
|
-
return key
|
|
19
|
-
? `ctx.headers.get('${key}')`
|
|
20
|
-
: 'ctx.headers';
|
|
85
|
+
return key ? `c.headers.get('${key}')` : 'c.headers';
|
|
21
86
|
case 'req':
|
|
22
|
-
return '
|
|
87
|
+
return 'c.req';
|
|
23
88
|
case 'locals':
|
|
24
|
-
return '
|
|
25
|
-
case 'di':
|
|
26
|
-
return `resolvers[${index}](ctx)`;
|
|
89
|
+
return 'c.locals';
|
|
27
90
|
default:
|
|
28
|
-
return
|
|
91
|
+
return 'undefined';
|
|
29
92
|
}
|
|
30
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Compila route handler em função otimizada.
|
|
96
|
+
*
|
|
97
|
+
* Estratégias de otimização:
|
|
98
|
+
* - Inline de acesso a parâmetros
|
|
99
|
+
* - Bind do handler no compile time
|
|
100
|
+
* - Código gerado monomórfico
|
|
101
|
+
* - Sem overhead de resolvers array
|
|
102
|
+
*/
|
|
103
|
+
/**
|
|
104
|
+
* Compila route handler em função otimizada que retorna Response inline.
|
|
105
|
+
*
|
|
106
|
+
* Estratégias de otimização:
|
|
107
|
+
* - Inline de acesso a parâmetros
|
|
108
|
+
* - Inline de response building (elimina executeFastPath)
|
|
109
|
+
* - Bind do handler no compile time
|
|
110
|
+
* - Código gerado monomórfico
|
|
111
|
+
* - Headers pré-frozen para otimização V8
|
|
112
|
+
*/
|
|
31
113
|
function compileRouteHandler(instance, methodName, paramInfos) {
|
|
32
114
|
const handler = instance[methodName].bind(instance);
|
|
33
115
|
if (paramInfos.length === 0) {
|
|
34
|
-
return (
|
|
116
|
+
return createInlineResponseHandler(handler, false);
|
|
35
117
|
}
|
|
36
|
-
const hasBodyParam = paramInfos.some((p) => p.type === 'body');
|
|
37
118
|
const hasDIParam = paramInfos.some((p) => p.type === 'di');
|
|
38
119
|
if (hasDIParam) {
|
|
39
120
|
return createFallbackHandler(handler, paramInfos);
|
|
40
121
|
}
|
|
122
|
+
const hasBodyParam = paramInfos.some((p) => p.type === 'body');
|
|
41
123
|
const argExpressions = paramInfos.map(buildArgExpression);
|
|
124
|
+
const argsCode = argExpressions.join(',');
|
|
42
125
|
if (hasBodyParam) {
|
|
43
|
-
return
|
|
126
|
+
const code = `return async function(c){
|
|
127
|
+
await c.getBody();
|
|
128
|
+
const r=await h(${argsCode});
|
|
129
|
+
const s=c.getResponseStatus()||200;
|
|
130
|
+
if(typeof r==='string')return new Response(r,{status:s,headers:TH});
|
|
131
|
+
if(r instanceof Response)return r;
|
|
132
|
+
if(r==null)return new Response('',{status:s,headers:TH});
|
|
133
|
+
if(typeof r==='number'||typeof r==='boolean')return new Response(String(r),{status:s,headers:TH});
|
|
134
|
+
if(isBI(r))return new Response(r,{status:s});
|
|
135
|
+
return new Response(JSON.stringify(r),{status:s,headers:JH});
|
|
136
|
+
}`;
|
|
137
|
+
return new Function('h', 'TH', 'JH', 'isBI', code)(handler, TEXT_HEADERS, JSON_HEADERS, isBodyInitResult);
|
|
44
138
|
}
|
|
45
|
-
return
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const code = `
|
|
57
|
-
return async function compiledHandler(ctx) {
|
|
58
|
-
await ctx.getBody();
|
|
59
|
-
return handler(${argExpressions.join(', ')});
|
|
60
|
-
}
|
|
61
|
-
`;
|
|
62
|
-
return new Function('handler', code)(handler);
|
|
139
|
+
const code = `return function(c){
|
|
140
|
+
const r=h(${argsCode});
|
|
141
|
+
const s=c.getResponseStatus()||200;
|
|
142
|
+
if(typeof r==='string')return new Response(r,{status:s,headers:TH});
|
|
143
|
+
if(r instanceof Response)return r;
|
|
144
|
+
if(r==null)return new Response('',{status:s,headers:TH});
|
|
145
|
+
if(typeof r==='number'||typeof r==='boolean')return new Response(String(r),{status:s,headers:TH});
|
|
146
|
+
if(isBI(r))return new Response(r,{status:s});
|
|
147
|
+
return new Response(JSON.stringify(r),{status:s,headers:JH});
|
|
148
|
+
}`;
|
|
149
|
+
return new Function('h', 'TH', 'JH', 'isBI', code)(handler, TEXT_HEADERS, JSON_HEADERS, isBodyInitResult);
|
|
63
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Handler fallback para casos com DI.
|
|
153
|
+
*/
|
|
64
154
|
function createFallbackHandler(handler, paramInfos) {
|
|
65
155
|
return (ctx) => {
|
|
66
|
-
const args =
|
|
67
|
-
for (const param of paramInfos) {
|
|
68
|
-
switch (param.type) {
|
|
69
|
-
case 'param':
|
|
70
|
-
args.push(param.key ? ctx.param[param.key] : ctx.param);
|
|
71
|
-
break;
|
|
72
|
-
case 'query':
|
|
73
|
-
args.push(param.key ? ctx.query[param.key] : ctx.query);
|
|
74
|
-
break;
|
|
75
|
-
case 'body':
|
|
76
|
-
args.push(param.key ? ctx.body[param.key] : ctx.body);
|
|
77
|
-
break;
|
|
78
|
-
case 'headers':
|
|
79
|
-
args.push(param.key ? ctx.headers.get(param.key) : ctx.headers);
|
|
80
|
-
break;
|
|
81
|
-
case 'req':
|
|
82
|
-
args.push(ctx.req);
|
|
83
|
-
break;
|
|
84
|
-
case 'locals':
|
|
85
|
-
args.push(ctx.locals);
|
|
86
|
-
break;
|
|
87
|
-
default:
|
|
88
|
-
args.push(undefined);
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
156
|
+
const args = resolveArgs(paramInfos, ctx);
|
|
92
157
|
return handler(...args);
|
|
93
158
|
};
|
|
94
159
|
}
|
|
95
|
-
|
|
160
|
+
/**
|
|
161
|
+
* Resolve argumentos para fallback handler.
|
|
162
|
+
*/
|
|
163
|
+
function resolveArgs(paramInfos, ctx) {
|
|
164
|
+
const args = new Array(paramInfos.length);
|
|
165
|
+
let i = 0;
|
|
166
|
+
for (const param of paramInfos) {
|
|
167
|
+
args[i++] = resolveArg(param, ctx);
|
|
168
|
+
}
|
|
169
|
+
return args;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Resolve um argumento individual.
|
|
173
|
+
*/
|
|
174
|
+
function resolveArg(param, ctx) {
|
|
175
|
+
switch (param.type) {
|
|
176
|
+
case 'param':
|
|
177
|
+
return param.key ? ctx.param[param.key] : ctx.param;
|
|
178
|
+
case 'query':
|
|
179
|
+
return param.key ? ctx.query[param.key] : ctx.query;
|
|
180
|
+
case 'body':
|
|
181
|
+
return param.key ? ctx.body[param.key] : ctx.body;
|
|
182
|
+
case 'headers':
|
|
183
|
+
return param.key ? ctx.headers.get(param.key) : ctx.headers;
|
|
184
|
+
case 'req':
|
|
185
|
+
return ctx.req;
|
|
186
|
+
case 'locals':
|
|
187
|
+
return ctx.locals;
|
|
188
|
+
default:
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Compila handler com validação inline.
|
|
194
|
+
*/
|
|
195
|
+
function compileValidatedHandler(instance, methodName, paramInfos, validatorAdapter) {
|
|
96
196
|
const handler = instance[methodName].bind(instance);
|
|
97
197
|
const hasBodyParam = paramInfos.some((p) => p.type === 'body');
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
value = param.key ? ctx.param[param.key] : ctx.param;
|
|
103
|
-
break;
|
|
104
|
-
case 'query':
|
|
105
|
-
value = param.key ? ctx.query[param.key] : ctx.query;
|
|
106
|
-
break;
|
|
107
|
-
case 'body':
|
|
108
|
-
value = param.key ? ctx.body[param.key] : ctx.body;
|
|
109
|
-
break;
|
|
110
|
-
case 'headers':
|
|
111
|
-
value = param.key ? ctx.headers.get(param.key) : ctx.headers;
|
|
112
|
-
break;
|
|
113
|
-
case 'req':
|
|
114
|
-
value = ctx.req;
|
|
115
|
-
break;
|
|
116
|
-
case 'locals':
|
|
117
|
-
value = ctx.locals;
|
|
118
|
-
break;
|
|
119
|
-
default:
|
|
120
|
-
value = undefined;
|
|
121
|
-
}
|
|
122
|
-
if (param.needsValidation && validators[index]) {
|
|
123
|
-
return validators[index](value);
|
|
124
|
-
}
|
|
125
|
-
return value;
|
|
126
|
-
};
|
|
198
|
+
if (paramInfos.length === 0) {
|
|
199
|
+
return createInlineResponseHandler(handler, false);
|
|
200
|
+
}
|
|
201
|
+
const { argAssignments, argList, tokenParams, tokenValues, } = buildValidatedArgs(paramInfos);
|
|
127
202
|
if (hasBodyParam) {
|
|
128
|
-
return async (
|
|
129
|
-
|
|
130
|
-
const args = paramInfos.map((p, i) => resolveArg(ctx, p, i));
|
|
131
|
-
return handler(...args);
|
|
132
|
-
};
|
|
203
|
+
const code = `return async function(c){\nawait c.getBody();\n${argAssignments}\nconst r=await h(${argList});\nconst s=c.getResponseStatus()||200;\nif(typeof r==='string')return new Response(r,{status:s,headers:TH});\nif(r instanceof Response)return r;\nif(r==null)return new Response('',{status:s,headers:TH});\nif(typeof r==='number'||typeof r==='boolean')return new Response(String(r),{status:s,headers:TH});\nif(isBI(r))return new Response(r,{status:s});\nreturn new Response(JSON.stringify(r),{status:s,headers:JH});\n}`;
|
|
204
|
+
return new Function('h', 'va', ...tokenParams, 'TH', 'JH', 'isBI', code)(handler, validatorAdapter, ...tokenValues, TEXT_HEADERS, JSON_HEADERS, isBodyInitResult);
|
|
133
205
|
}
|
|
134
|
-
return (
|
|
135
|
-
|
|
136
|
-
|
|
206
|
+
const code = `return function(c){\n${argAssignments}\nconst r=h(${argList});\nconst s=c.getResponseStatus()||200;\nif(typeof r==='string')return new Response(r,{status:s,headers:TH});\nif(r instanceof Response)return r;\nif(r==null)return new Response('',{status:s,headers:TH});\nif(typeof r==='number'||typeof r==='boolean')return new Response(String(r),{status:s,headers:TH});\nif(isBI(r))return new Response(r,{status:s});\nreturn new Response(JSON.stringify(r),{status:s,headers:JH});\n}`;
|
|
207
|
+
return new Function('h', 'va', ...tokenParams, 'TH', 'JH', 'isBI', code)(handler, validatorAdapter, ...tokenValues, TEXT_HEADERS, JSON_HEADERS, isBodyInitResult);
|
|
208
|
+
}
|
|
209
|
+
function buildValidatedArgs(paramInfos) {
|
|
210
|
+
const assignments = [];
|
|
211
|
+
const args = [];
|
|
212
|
+
const tokenParams = [];
|
|
213
|
+
const tokenValues = [];
|
|
214
|
+
let index = 0;
|
|
215
|
+
for (const param of paramInfos) {
|
|
216
|
+
const argName = `a${index}`;
|
|
217
|
+
const argExpr = buildArgExpression(param);
|
|
218
|
+
const tokenName = getTokenParamName(param, index, tokenParams, tokenValues);
|
|
219
|
+
const valueExpr = buildValidatedExpression(argExpr, tokenName);
|
|
220
|
+
assignments.push(`const ${argName}=${valueExpr};`);
|
|
221
|
+
args.push(argName);
|
|
222
|
+
index += 1;
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
argAssignments: assignments.join('\n'),
|
|
226
|
+
argList: args.join(','),
|
|
227
|
+
tokenParams,
|
|
228
|
+
tokenValues,
|
|
137
229
|
};
|
|
138
230
|
}
|
|
231
|
+
function buildValidatedExpression(argExpr, tokenName) {
|
|
232
|
+
if (!tokenName) {
|
|
233
|
+
return argExpr;
|
|
234
|
+
}
|
|
235
|
+
return `va.validateAndTransform(${tokenName}, ${argExpr})`;
|
|
236
|
+
}
|
|
237
|
+
function getTokenParamName(param, index, tokenParams, tokenValues) {
|
|
238
|
+
if (!param.needsValidation || !param.token) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
const tokenName = `t${index}`;
|
|
242
|
+
tokenParams.push(tokenName);
|
|
243
|
+
tokenValues.push(param.token);
|
|
244
|
+
return tokenName;
|
|
245
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { type ValidatorOptions } from 'class-validator';
|
|
2
1
|
import type { Context } from '../domain/Context';
|
|
3
|
-
import type { ParamResolver, AsyncParamResolver } from './CompiledRoute';
|
|
4
2
|
export type ParamDecoratorType = 'body' | 'query' | 'param' | 'headers' | 'req' | 'locals';
|
|
5
3
|
export interface ParamDecoratorMeta {
|
|
6
4
|
fun: (context: Context, data?: any) => any;
|
|
@@ -14,6 +12,3 @@ export interface ParamInfo {
|
|
|
14
12
|
token?: any;
|
|
15
13
|
}
|
|
16
14
|
export declare function analyzeParamDecorator(decoratorMeta: ParamDecoratorMeta | undefined, token: any): ParamInfo;
|
|
17
|
-
export declare function createParamResolver(decoratorMeta: ParamDecoratorMeta | undefined, token: any, validationConfig?: ValidatorOptions): ParamResolver | AsyncParamResolver | null;
|
|
18
|
-
export declare function createParamResolvers(paramMetas: Record<number, ParamDecoratorMeta> | undefined, argTypes: any[], validationConfig?: ValidatorOptions): (ParamResolver | AsyncParamResolver | null)[];
|
|
19
|
-
export declare function hasAnyDIParam(resolvers: (ParamResolver | AsyncParamResolver | null)[]): boolean;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.analyzeParamDecorator = analyzeParamDecorator;
|
|
4
|
-
exports.createParamResolver = createParamResolver;
|
|
5
|
-
exports.createParamResolvers = createParamResolvers;
|
|
6
|
-
exports.hasAnyDIParam = hasAnyDIParam;
|
|
7
|
-
const class_transformer_1 = require("class-transformer");
|
|
8
|
-
const class_validator_1 = require("class-validator");
|
|
9
|
-
const HttpException_1 = require("../exceptions/HttpException");
|
|
10
4
|
const ValidationCache_1 = require("../utils/ValidationCache");
|
|
11
5
|
function analyzeParamDecorator(decoratorMeta, token) {
|
|
12
6
|
if (!decoratorMeta) {
|
|
@@ -53,37 +47,3 @@ function inferTypeFromSource(resolver) {
|
|
|
53
47
|
}
|
|
54
48
|
return null;
|
|
55
49
|
}
|
|
56
|
-
function createValidationResolver(extractFn, token, validationConfig) {
|
|
57
|
-
return (context) => {
|
|
58
|
-
const value = extractFn(context);
|
|
59
|
-
const obj = (0, class_transformer_1.plainToInstance)(token, value);
|
|
60
|
-
const errors = (0, class_validator_1.validateSync)(obj, validationConfig);
|
|
61
|
-
if (errors.length > 0) {
|
|
62
|
-
throw new HttpException_1.HttpException(errors, 400);
|
|
63
|
-
}
|
|
64
|
-
return obj;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function createParamResolver(decoratorMeta, token, validationConfig) {
|
|
68
|
-
if (!decoratorMeta) {
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
const extractFn = (context) => decoratorMeta.fun(context, decoratorMeta.param);
|
|
72
|
-
const needsValidation = typeof token === 'function' && (0, ValidationCache_1.isValidatable)(token);
|
|
73
|
-
if (needsValidation) {
|
|
74
|
-
return createValidationResolver(extractFn, token, validationConfig);
|
|
75
|
-
}
|
|
76
|
-
return extractFn;
|
|
77
|
-
}
|
|
78
|
-
function createParamResolvers(paramMetas, argTypes, validationConfig) {
|
|
79
|
-
const resolvers = [];
|
|
80
|
-
for (let i = 0; i < argTypes.length; i++) {
|
|
81
|
-
const meta = paramMetas?.[i];
|
|
82
|
-
const token = argTypes[i];
|
|
83
|
-
resolvers.push(createParamResolver(meta, token, validationConfig));
|
|
84
|
-
}
|
|
85
|
-
return resolvers;
|
|
86
|
-
}
|
|
87
|
-
function hasAnyDIParam(resolvers) {
|
|
88
|
-
return resolvers.some((r) => r === null);
|
|
89
|
-
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { type ValidatorOptions } from 'class-validator';
|
|
2
1
|
import type { TokenRouteWithProvider } from '../container/ContainerConfiguration';
|
|
2
|
+
import type { ValidatorAdapter } from '../validation/ValidatorAdapter';
|
|
3
3
|
import type { Container } from '../container/container';
|
|
4
4
|
import { ProviderScope } from '../domain/provider-scope';
|
|
5
5
|
import { type CompiledRoute } from './CompiledRoute';
|
|
6
6
|
export interface RouteCompilerOptions {
|
|
7
7
|
container: Container;
|
|
8
8
|
controllerScopes: Map<any, ProviderScope>;
|
|
9
|
-
|
|
9
|
+
validatorAdapter: ValidatorAdapter;
|
|
10
10
|
hasOnRequestHook: boolean;
|
|
11
11
|
hasOnResponseHook: boolean;
|
|
12
12
|
}
|
|
13
13
|
export declare class RouteCompiler {
|
|
14
14
|
private container;
|
|
15
15
|
private controllerScopes;
|
|
16
|
-
private
|
|
16
|
+
private validatorAdapter;
|
|
17
17
|
private hasOnRequestHook;
|
|
18
18
|
private hasOnResponseHook;
|
|
19
19
|
constructor(options: RouteCompilerOptions);
|
|
@@ -25,5 +25,4 @@ export declare class RouteCompiler {
|
|
|
25
25
|
private createStandardRoute;
|
|
26
26
|
private createComplexRoute;
|
|
27
27
|
private createFallbackRoute;
|
|
28
|
-
private createValidatedHandler;
|
|
29
28
|
}
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RouteCompiler = void 0;
|
|
4
|
-
const class_transformer_1 = require("class-transformer");
|
|
5
|
-
const class_validator_1 = require("class-validator");
|
|
6
4
|
const provider_scope_1 = require("../domain/provider-scope");
|
|
7
5
|
const Metadata_1 = require("../domain/Metadata");
|
|
8
|
-
const HttpException_1 = require("../exceptions/HttpException");
|
|
9
6
|
const getMethodArgTypes_1 = require("../utils/getMethodArgTypes");
|
|
10
7
|
const CompiledRoute_1 = require("./CompiledRoute");
|
|
11
8
|
const ParamResolverFactory_1 = require("./ParamResolverFactory");
|
|
@@ -14,7 +11,7 @@ class RouteCompiler {
|
|
|
14
11
|
constructor(options) {
|
|
15
12
|
this.container = options.container;
|
|
16
13
|
this.controllerScopes = options.controllerScopes;
|
|
17
|
-
this.
|
|
14
|
+
this.validatorAdapter = options.validatorAdapter;
|
|
18
15
|
this.hasOnRequestHook = options.hasOnRequestHook;
|
|
19
16
|
this.hasOnResponseHook = options.hasOnResponseHook;
|
|
20
17
|
}
|
|
@@ -70,7 +67,7 @@ class RouteCompiler {
|
|
|
70
67
|
const hasBodyParam = paramInfos.some((p) => p.type === 'body');
|
|
71
68
|
let boundHandler;
|
|
72
69
|
if (hasValidation) {
|
|
73
|
-
boundHandler =
|
|
70
|
+
boundHandler = (0, JITCompiler_1.compileValidatedHandler)(instance, route.methodName, paramInfos, this.validatorAdapter);
|
|
74
71
|
}
|
|
75
72
|
else {
|
|
76
73
|
boundHandler = (0, JITCompiler_1.compileRouteHandler)(instance, route.methodName, paramInfos);
|
|
@@ -156,54 +153,5 @@ class RouteCompiler {
|
|
|
156
153
|
original: route,
|
|
157
154
|
};
|
|
158
155
|
}
|
|
159
|
-
createValidatedHandler(instance, methodName, paramInfos, hasBodyParam) {
|
|
160
|
-
const handler = instance[methodName].bind(instance);
|
|
161
|
-
const config = this.validationConfig;
|
|
162
|
-
const resolveArg = (ctx, param) => {
|
|
163
|
-
let value;
|
|
164
|
-
switch (param.type) {
|
|
165
|
-
case 'param':
|
|
166
|
-
value = param.key ? ctx.param[param.key] : ctx.param;
|
|
167
|
-
break;
|
|
168
|
-
case 'query':
|
|
169
|
-
value = param.key ? ctx.query[param.key] : ctx.query;
|
|
170
|
-
break;
|
|
171
|
-
case 'body':
|
|
172
|
-
value = param.key ? ctx.body[param.key] : ctx.body;
|
|
173
|
-
break;
|
|
174
|
-
case 'headers':
|
|
175
|
-
value = param.key ? ctx.headers.get(param.key) : ctx.headers;
|
|
176
|
-
break;
|
|
177
|
-
case 'req':
|
|
178
|
-
value = ctx.req;
|
|
179
|
-
break;
|
|
180
|
-
case 'locals':
|
|
181
|
-
value = ctx.locals;
|
|
182
|
-
break;
|
|
183
|
-
default:
|
|
184
|
-
value = undefined;
|
|
185
|
-
}
|
|
186
|
-
if (param.needsValidation && param.token) {
|
|
187
|
-
const obj = (0, class_transformer_1.plainToInstance)(param.token, value);
|
|
188
|
-
const errors = (0, class_validator_1.validateSync)(obj, config);
|
|
189
|
-
if (errors.length > 0) {
|
|
190
|
-
throw new HttpException_1.HttpException(errors, 400);
|
|
191
|
-
}
|
|
192
|
-
return obj;
|
|
193
|
-
}
|
|
194
|
-
return value;
|
|
195
|
-
};
|
|
196
|
-
if (hasBodyParam) {
|
|
197
|
-
return async (ctx) => {
|
|
198
|
-
await ctx.getBody();
|
|
199
|
-
const args = paramInfos.map((p) => resolveArg(ctx, p));
|
|
200
|
-
return handler(...args);
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
return (ctx) => {
|
|
204
|
-
const args = paramInfos.map((p) => resolveArg(ctx, p));
|
|
205
|
-
return handler(...args);
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
156
|
}
|
|
209
157
|
exports.RouteCompiler = RouteCompiler;
|
|
@@ -24,7 +24,24 @@ class Router {
|
|
|
24
24
|
if (injector.hasOnResponseHook()) {
|
|
25
25
|
await injector.callHook(events_1.EventType.OnResponse, { context, result });
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
const status = context.getResponseStatus() || 200;
|
|
28
|
+
if (result instanceof Response) {
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
if (result === null || result === undefined) {
|
|
32
|
+
return new Response("", { status, headers: this.textHeaders });
|
|
33
|
+
}
|
|
34
|
+
const resultType = typeof result;
|
|
35
|
+
if (resultType === "string") {
|
|
36
|
+
return new Response(result, { status, headers: this.textHeaders });
|
|
37
|
+
}
|
|
38
|
+
if (resultType === "number" || resultType === "boolean") {
|
|
39
|
+
return new Response(String(result), { status, headers: this.textHeaders });
|
|
40
|
+
}
|
|
41
|
+
if (this.isBodyInit(result)) {
|
|
42
|
+
return new Response(result, { status });
|
|
43
|
+
}
|
|
44
|
+
return this.createJsonResponse(result, status);
|
|
28
45
|
}
|
|
29
46
|
mountResponse(result, context) {
|
|
30
47
|
const status = context.getResponseStatus() || 200;
|
|
@@ -18,10 +18,13 @@ export interface Node<T> {
|
|
|
18
18
|
export declare class Memoirist<T> {
|
|
19
19
|
root: Record<string, Node<T>>;
|
|
20
20
|
history: [string, string, T][];
|
|
21
|
+
private routeCache;
|
|
21
22
|
private static regex;
|
|
22
23
|
add(method: string, path: string, store: T): FindResult<T>['store'];
|
|
23
24
|
find(method: string, url: string): FindResult<T> | null;
|
|
24
25
|
updateStore(method: string, path: string, oldStore: T, newStore: T): boolean;
|
|
26
|
+
private buildCacheKey;
|
|
27
|
+
private invalidateCache;
|
|
25
28
|
private updateHistoryStore;
|
|
26
29
|
private normalizePath;
|
|
27
30
|
private findNode;
|
package/dist/route/memoirist.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Memoirist = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* * Empty object shared for static routes without parameters.
|
|
6
|
+
* * Frozen for V8 optimization and immutability.
|
|
7
|
+
* * Avoid allocating an empty object on every request in static routes.
|
|
8
|
+
* */
|
|
9
|
+
const EMPTY_PARAMS = Object.freeze({});
|
|
4
10
|
const createNode = (part, inert) => ({
|
|
5
11
|
part,
|
|
6
12
|
store: null,
|
|
@@ -54,6 +60,7 @@ class Memoirist {
|
|
|
54
60
|
constructor() {
|
|
55
61
|
this.root = {};
|
|
56
62
|
this.history = [];
|
|
63
|
+
this.routeCache = new Map();
|
|
57
64
|
}
|
|
58
65
|
add(method, path, store) {
|
|
59
66
|
if (typeof path !== 'string')
|
|
@@ -62,6 +69,7 @@ class Memoirist {
|
|
|
62
69
|
path = '/';
|
|
63
70
|
else if (path[0] !== '/')
|
|
64
71
|
path = `/${path}`;
|
|
72
|
+
this.invalidateCache();
|
|
65
73
|
this.history.push([method, path, store]);
|
|
66
74
|
const isWildcard = path[path.length - 1] === '*';
|
|
67
75
|
if (isWildcard) {
|
|
@@ -162,10 +170,16 @@ class Memoirist {
|
|
|
162
170
|
return node.store;
|
|
163
171
|
}
|
|
164
172
|
find(method, url) {
|
|
173
|
+
const cacheKey = this.buildCacheKey(method, url);
|
|
174
|
+
if (this.routeCache.has(cacheKey)) {
|
|
175
|
+
return this.routeCache.get(cacheKey);
|
|
176
|
+
}
|
|
165
177
|
const root = this.root[method];
|
|
166
178
|
if (!root)
|
|
167
179
|
return null;
|
|
168
|
-
|
|
180
|
+
const result = matchRoute(url, url.length, root, 0);
|
|
181
|
+
this.routeCache.set(cacheKey, result);
|
|
182
|
+
return result;
|
|
169
183
|
}
|
|
170
184
|
updateStore(method, path, oldStore, newStore) {
|
|
171
185
|
const node = this.findNode(method, path);
|
|
@@ -175,6 +189,7 @@ class Memoirist {
|
|
|
175
189
|
if (node.store === oldStore) {
|
|
176
190
|
node.store = newStore;
|
|
177
191
|
this.updateHistoryStore(method, path, newStore);
|
|
192
|
+
this.invalidateCache();
|
|
178
193
|
return true;
|
|
179
194
|
}
|
|
180
195
|
if (node.params?.store === oldStore) {
|
|
@@ -184,15 +199,24 @@ class Memoirist {
|
|
|
184
199
|
node.params.names.set(newStore, paramName);
|
|
185
200
|
}
|
|
186
201
|
this.updateHistoryStore(method, path, newStore);
|
|
202
|
+
this.invalidateCache();
|
|
187
203
|
return true;
|
|
188
204
|
}
|
|
189
205
|
if (node.wildcardStore === oldStore) {
|
|
190
206
|
node.wildcardStore = newStore;
|
|
191
207
|
this.updateHistoryStore(method, path, newStore);
|
|
208
|
+
this.invalidateCache();
|
|
192
209
|
return true;
|
|
193
210
|
}
|
|
194
211
|
return false;
|
|
195
212
|
}
|
|
213
|
+
buildCacheKey(method, url) {
|
|
214
|
+
const normalizedMethod = method.toLowerCase();
|
|
215
|
+
return `${normalizedMethod}:${url}`;
|
|
216
|
+
}
|
|
217
|
+
invalidateCache() {
|
|
218
|
+
this.routeCache.clear();
|
|
219
|
+
}
|
|
196
220
|
updateHistoryStore(method, path, newStore) {
|
|
197
221
|
const normalizedPath = this.normalizePath(path);
|
|
198
222
|
for (let i = 0; i < this.history.length; i++) {
|
|
@@ -288,7 +312,7 @@ const matchRoute = (url, urlLength, node, startIndex) => {
|
|
|
288
312
|
if (node.store !== null)
|
|
289
313
|
return {
|
|
290
314
|
store: node.store,
|
|
291
|
-
params:
|
|
315
|
+
params: EMPTY_PARAMS
|
|
292
316
|
};
|
|
293
317
|
if (node.wildcardStore !== null)
|
|
294
318
|
return {
|
|
@@ -325,7 +349,13 @@ const matchRoute = (url, urlLength, node, startIndex) => {
|
|
|
325
349
|
const route = matchRoute(url, urlLength, param.inert, slashIndex);
|
|
326
350
|
if (route !== null) {
|
|
327
351
|
const paramName = resolveParamName(param, route.store);
|
|
328
|
-
|
|
352
|
+
const paramValue = url.substring(endIndex, slashIndex);
|
|
353
|
+
if (route.params === EMPTY_PARAMS) {
|
|
354
|
+
route.params = { [paramName]: paramValue };
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
route.params[paramName] = paramValue;
|
|
358
|
+
}
|
|
329
359
|
return route;
|
|
330
360
|
}
|
|
331
361
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { ValidatorAdapter } from '../validation/ValidatorAdapter';
|
|
2
|
+
export declare function setValidatorAdapter(adapter: ValidatorAdapter): void;
|
|
1
3
|
export declare function isValidatable(token: Function): boolean;
|
|
2
4
|
export declare function preloadValidationForParams(args: any[]): number[];
|
|
3
5
|
export declare function clearValidationCache(): void;
|