@carno.js/core 0.2.9 → 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 +1 -1
- package/dist/Carno.js +17 -32
- package/dist/container/middleware.resolver.js +6 -6
- package/dist/domain/BaseContext.d.ts +15 -0
- package/dist/domain/BaseContext.js +2 -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/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/RouteCompiler.d.ts +0 -1
- package/dist/route/RouteCompiler.js +1 -44
- package/dist/route/RouteExecutor.js +18 -1
- package/dist/route/memoirist.d.ts +3 -0
- package/dist/route/memoirist.js +33 -3
- package/package.json +2 -2
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.CorsHeadersCache = void 0;
|
|
4
4
|
const cors_config_1 = require("./cors-config");
|
|
5
|
+
const allowAnyOrigin = () => {
|
|
6
|
+
return true;
|
|
7
|
+
};
|
|
8
|
+
const denyAnyOrigin = () => {
|
|
9
|
+
return false;
|
|
10
|
+
};
|
|
11
|
+
function buildOriginAllowed(origins) {
|
|
12
|
+
if (origins === '*') {
|
|
13
|
+
return allowAnyOrigin;
|
|
14
|
+
}
|
|
15
|
+
if (typeof origins === 'string') {
|
|
16
|
+
return createExactOriginMatcher(origins);
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(origins)) {
|
|
19
|
+
return createSetOriginMatcher(origins);
|
|
20
|
+
}
|
|
21
|
+
if (origins instanceof RegExp) {
|
|
22
|
+
return createRegexOriginMatcher(origins);
|
|
23
|
+
}
|
|
24
|
+
if (typeof origins === 'function') {
|
|
25
|
+
return origins;
|
|
26
|
+
}
|
|
27
|
+
return denyAnyOrigin;
|
|
28
|
+
}
|
|
29
|
+
function createExactOriginMatcher(origin) {
|
|
30
|
+
return (value) => {
|
|
31
|
+
return value === origin;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function createSetOriginMatcher(origins) {
|
|
35
|
+
const originSet = new Set(origins);
|
|
36
|
+
return (value) => {
|
|
37
|
+
return originSet.has(value);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function createRegexOriginMatcher(origins) {
|
|
41
|
+
return (value) => {
|
|
42
|
+
return origins.test(value);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
5
45
|
class CorsHeadersCache {
|
|
6
46
|
constructor(config) {
|
|
7
47
|
this.config = config;
|
|
@@ -18,6 +58,7 @@ class CorsHeadersCache {
|
|
|
18
58
|
: null;
|
|
19
59
|
this.hasCredentials = !!config.credentials;
|
|
20
60
|
this.isWildcard = config.origins === '*';
|
|
61
|
+
this.originAllowed = buildOriginAllowed(config.origins);
|
|
21
62
|
}
|
|
22
63
|
get(origin) {
|
|
23
64
|
const cacheKey = this.isWildcard ? '*' : origin;
|
|
@@ -53,5 +94,8 @@ class CorsHeadersCache {
|
|
|
53
94
|
}
|
|
54
95
|
return response;
|
|
55
96
|
}
|
|
97
|
+
isOriginAllowed(origin) {
|
|
98
|
+
return this.originAllowed(origin);
|
|
99
|
+
}
|
|
56
100
|
}
|
|
57
101
|
exports.CorsHeadersCache = CorsHeadersCache;
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { Context } from '../domain/Context';
|
|
2
2
|
import type { CompiledRoute } from './CompiledRoute';
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Fast path executor completo - executa handler e monta response inline.
|
|
5
|
+
* Máximo 2 type checks no fast path.
|
|
6
|
+
*/
|
|
7
|
+
export declare function executeFastPath(compiled: CompiledRoute, context: Context): Promise<Response>;
|
|
8
|
+
/**
|
|
9
|
+
* Fast path executor síncrono - para handlers que não retornam Promise.
|
|
10
|
+
* Elimina overhead de async/await quando handler é síncrono.
|
|
11
|
+
*/
|
|
12
|
+
export declare function executeFastPathSync(compiled: CompiledRoute, context: Context): Response;
|
|
@@ -1,19 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
exports.executeFastPath = executeFastPath;
|
|
4
|
+
exports.executeFastPathSync = executeFastPathSync;
|
|
5
|
+
/**
|
|
6
|
+
* Frozen header objects - V8 otimiza acesso a objetos frozen.
|
|
7
|
+
* Criados uma vez no startup, reutilizados em todas requests.
|
|
8
|
+
*/
|
|
9
|
+
const JSON_HEADERS = Object.freeze({
|
|
10
|
+
'Content-Type': 'application/json'
|
|
11
|
+
});
|
|
12
|
+
const TEXT_HEADERS = Object.freeze({
|
|
13
|
+
'Content-Type': 'text/html'
|
|
14
|
+
});
|
|
15
|
+
/**
|
|
16
|
+
* Fast path executor completo - executa handler e monta response inline.
|
|
17
|
+
* Máximo 2 type checks no fast path.
|
|
18
|
+
*/
|
|
19
|
+
async function executeFastPath(compiled, context) {
|
|
20
|
+
const result = await compiled.boundHandler(context);
|
|
21
|
+
const status = context.getResponseStatus() || 200;
|
|
22
|
+
if (typeof result === 'string') {
|
|
23
|
+
return new Response(result, { status, headers: TEXT_HEADERS });
|
|
8
24
|
}
|
|
9
|
-
if (
|
|
10
|
-
return
|
|
25
|
+
if (result instanceof Response) {
|
|
26
|
+
return result;
|
|
11
27
|
}
|
|
12
|
-
|
|
28
|
+
if (result === null || result === undefined) {
|
|
29
|
+
return new Response('', { status, headers: TEXT_HEADERS });
|
|
30
|
+
}
|
|
31
|
+
return new Response(JSON.stringify(result), { status, headers: JSON_HEADERS });
|
|
13
32
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
33
|
+
/**
|
|
34
|
+
* Fast path executor síncrono - para handlers que não retornam Promise.
|
|
35
|
+
* Elimina overhead de async/await quando handler é síncrono.
|
|
36
|
+
*/
|
|
37
|
+
function executeFastPathSync(compiled, context) {
|
|
38
|
+
const result = compiled.boundHandler(context);
|
|
39
|
+
const status = context.getResponseStatus() || 200;
|
|
40
|
+
if (typeof result === 'string') {
|
|
41
|
+
return new Response(result, { status, headers: TEXT_HEADERS });
|
|
42
|
+
}
|
|
43
|
+
if (result instanceof Response) {
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
if (result === null || result === undefined) {
|
|
47
|
+
return new Response('', { status, headers: TEXT_HEADERS });
|
|
17
48
|
}
|
|
18
|
-
return
|
|
49
|
+
return new Response(JSON.stringify(result), { status, headers: JSON_HEADERS });
|
|
19
50
|
}
|
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
import type { ParamInfo } from './ParamResolverFactory';
|
|
2
2
|
import type { CompiledHandler, AsyncCompiledHandler } from './CompiledRoute';
|
|
3
|
+
/**
|
|
4
|
+
* Compila route handler em função otimizada.
|
|
5
|
+
*
|
|
6
|
+
* Estratégias de otimização:
|
|
7
|
+
* - Inline de acesso a parâmetros
|
|
8
|
+
* - Bind do handler no compile time
|
|
9
|
+
* - Código gerado monomórfico
|
|
10
|
+
* - Sem overhead de resolvers array
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Compila route handler em função otimizada que retorna Response inline.
|
|
14
|
+
*
|
|
15
|
+
* Estratégias de otimização:
|
|
16
|
+
* - Inline de acesso a parâmetros
|
|
17
|
+
* - Inline de response building (elimina executeFastPath)
|
|
18
|
+
* - Bind do handler no compile time
|
|
19
|
+
* - Código gerado monomórfico
|
|
20
|
+
* - Headers pré-frozen para otimização V8
|
|
21
|
+
*/
|
|
3
22
|
export declare function compileRouteHandler(instance: any, methodName: string, paramInfos: ParamInfo[]): CompiledHandler | AsyncCompiledHandler;
|
|
4
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Compila handler com validação inline.
|
|
25
|
+
*/
|
|
26
|
+
export declare function compileValidatedHandler(instance: any, methodName: string, paramInfos: ParamInfo[], validatorAdapter: {
|
|
27
|
+
validateAndTransform: (token: any, value: any) => any;
|
|
28
|
+
}): CompiledHandler | AsyncCompiledHandler;
|
|
@@ -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
|
+
}
|
|
@@ -67,7 +67,7 @@ class RouteCompiler {
|
|
|
67
67
|
const hasBodyParam = paramInfos.some((p) => p.type === 'body');
|
|
68
68
|
let boundHandler;
|
|
69
69
|
if (hasValidation) {
|
|
70
|
-
boundHandler =
|
|
70
|
+
boundHandler = (0, JITCompiler_1.compileValidatedHandler)(instance, route.methodName, paramInfos, this.validatorAdapter);
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
73
|
boundHandler = (0, JITCompiler_1.compileRouteHandler)(instance, route.methodName, paramInfos);
|
|
@@ -153,48 +153,5 @@ class RouteCompiler {
|
|
|
153
153
|
original: route,
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
|
-
createValidatedHandler(instance, methodName, paramInfos, hasBodyParam) {
|
|
157
|
-
const handler = instance[methodName].bind(instance);
|
|
158
|
-
const resolveArg = (ctx, param) => {
|
|
159
|
-
let value;
|
|
160
|
-
switch (param.type) {
|
|
161
|
-
case 'param':
|
|
162
|
-
value = param.key ? ctx.param[param.key] : ctx.param;
|
|
163
|
-
break;
|
|
164
|
-
case 'query':
|
|
165
|
-
value = param.key ? ctx.query[param.key] : ctx.query;
|
|
166
|
-
break;
|
|
167
|
-
case 'body':
|
|
168
|
-
value = param.key ? ctx.body[param.key] : ctx.body;
|
|
169
|
-
break;
|
|
170
|
-
case 'headers':
|
|
171
|
-
value = param.key ? ctx.headers.get(param.key) : ctx.headers;
|
|
172
|
-
break;
|
|
173
|
-
case 'req':
|
|
174
|
-
value = ctx.req;
|
|
175
|
-
break;
|
|
176
|
-
case 'locals':
|
|
177
|
-
value = ctx.locals;
|
|
178
|
-
break;
|
|
179
|
-
default:
|
|
180
|
-
value = undefined;
|
|
181
|
-
}
|
|
182
|
-
if (param.needsValidation && param.token) {
|
|
183
|
-
return this.validatorAdapter.validateAndTransform(param.token, value);
|
|
184
|
-
}
|
|
185
|
-
return value;
|
|
186
|
-
};
|
|
187
|
-
if (hasBodyParam) {
|
|
188
|
-
return async (ctx) => {
|
|
189
|
-
await ctx.getBody();
|
|
190
|
-
const args = paramInfos.map((p) => resolveArg(ctx, p));
|
|
191
|
-
return handler(...args);
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
return (ctx) => {
|
|
195
|
-
const args = paramInfos.map((p) => resolveArg(ctx, p));
|
|
196
|
-
return handler(...args);
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
156
|
}
|
|
200
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;
|