@carno.js/core 1.1.1 → 1.2.0
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/LICENSE +21 -21
- package/README.md +188 -188
- package/dist/Carno.js +45 -26
- package/dist/Carno.mjs +45 -26
- package/dist/bun/index.js +4 -4
- package/dist/bun/index.js.map +30 -29
- package/dist/compression/CompressionMiddleware.js +110 -0
- package/dist/compression/CompressionMiddleware.mjs +90 -0
- package/dist/index.js +3 -1
- package/dist/index.mjs +2 -0
- package/package.json +2 -2
- package/src/Carno.ts +728 -673
- package/src/DefaultRoutes.ts +34 -34
- package/src/cache/CacheDriver.ts +50 -50
- package/src/cache/CacheService.ts +139 -139
- package/src/cache/MemoryDriver.ts +104 -104
- package/src/cache/RedisDriver.ts +116 -116
- package/src/compiler/JITCompiler.ts +167 -167
- package/src/compression/CompressionMiddleware.ts +221 -0
- package/src/container/Container.ts +168 -168
- package/src/context/Context.ts +130 -130
- package/src/cors/CorsHandler.ts +145 -145
- package/src/decorators/Controller.ts +63 -63
- package/src/decorators/Inject.ts +16 -16
- package/src/decorators/Middleware.ts +22 -22
- package/src/decorators/Service.ts +18 -18
- package/src/decorators/methods.ts +58 -58
- package/src/decorators/params.ts +47 -47
- package/src/events/Lifecycle.ts +97 -97
- package/src/exceptions/HttpException.ts +99 -99
- package/src/index.ts +99 -95
- package/src/metadata.ts +46 -46
- package/src/middleware/CarnoMiddleware.ts +20 -14
- package/src/router/RadixRouter.ts +225 -225
- package/src/testing/TestHarness.ts +185 -185
- package/src/utils/Metadata.ts +43 -43
- package/src/utils/parseQuery.ts +161 -161
- package/src/validation/ValibotAdapter.ts +95 -95
- package/src/validation/ValidatorAdapter.ts +69 -69
- package/src/validation/ZodAdapter.ts +102 -102
- package/dist/Carno.d.js +0 -14
- package/dist/Carno.d.mjs +0 -1
- package/dist/DefaultRoutes.d.js +0 -13
- package/dist/DefaultRoutes.d.mjs +0 -0
- package/dist/cache/CacheDriver.d.js +0 -13
- package/dist/cache/CacheDriver.d.mjs +0 -0
- package/dist/cache/CacheService.d.js +0 -13
- package/dist/cache/CacheService.d.mjs +0 -0
- package/dist/cache/MemoryDriver.d.js +0 -13
- package/dist/cache/MemoryDriver.d.mjs +0 -0
- package/dist/cache/RedisDriver.d.js +0 -13
- package/dist/cache/RedisDriver.d.mjs +0 -0
- package/dist/compiler/JITCompiler.d.js +0 -13
- package/dist/compiler/JITCompiler.d.mjs +0 -0
- package/dist/container/Container.d.js +0 -13
- package/dist/container/Container.d.mjs +0 -0
- package/dist/context/Context.d.js +0 -13
- package/dist/context/Context.d.mjs +0 -0
- package/dist/cors/CorsHandler.d.js +0 -13
- package/dist/cors/CorsHandler.d.mjs +0 -0
- package/dist/decorators/Controller.d.js +0 -13
- package/dist/decorators/Controller.d.mjs +0 -0
- package/dist/decorators/Inject.d.js +0 -13
- package/dist/decorators/Inject.d.mjs +0 -0
- package/dist/decorators/Middleware.d.js +0 -13
- package/dist/decorators/Middleware.d.mjs +0 -0
- package/dist/decorators/Service.d.js +0 -13
- package/dist/decorators/Service.d.mjs +0 -0
- package/dist/decorators/methods.d.js +0 -13
- package/dist/decorators/methods.d.mjs +0 -0
- package/dist/decorators/params.d.js +0 -13
- package/dist/decorators/params.d.mjs +0 -0
- package/dist/events/Lifecycle.d.js +0 -13
- package/dist/events/Lifecycle.d.mjs +0 -0
- package/dist/exceptions/HttpException.d.js +0 -13
- package/dist/exceptions/HttpException.d.mjs +0 -0
- package/dist/index.d.js +0 -130
- package/dist/index.d.mjs +0 -78
- package/dist/metadata.d.js +0 -13
- package/dist/metadata.d.mjs +0 -0
- package/dist/middleware/CarnoMiddleware.d.js +0 -13
- package/dist/middleware/CarnoMiddleware.d.mjs +0 -0
- package/dist/router/RadixRouter.d.js +0 -13
- package/dist/router/RadixRouter.d.mjs +0 -0
- package/dist/testing/TestHarness.d.js +0 -13
- package/dist/testing/TestHarness.d.mjs +0 -0
- package/dist/utils/Metadata.d.js +0 -13
- package/dist/utils/Metadata.d.mjs +0 -0
- package/dist/utils/parseQuery.d.js +0 -13
- package/dist/utils/parseQuery.d.mjs +0 -0
- package/dist/validation/ValibotAdapter.d.js +0 -13
- package/dist/validation/ValibotAdapter.d.mjs +0 -0
- package/dist/validation/ValidatorAdapter.d.js +0 -13
- package/dist/validation/ValidatorAdapter.d.mjs +0 -0
- package/dist/validation/ZodAdapter.d.js +0 -13
- package/dist/validation/ZodAdapter.d.mjs +0 -0
- package/src/Carno.d.ts +0 -135
- package/src/DefaultRoutes.d.ts +0 -19
- package/src/cache/CacheDriver.d.ts +0 -43
- package/src/cache/CacheService.d.ts +0 -89
- package/src/cache/MemoryDriver.d.ts +0 -32
- package/src/cache/RedisDriver.d.ts +0 -34
- package/src/compiler/JITCompiler.d.ts +0 -36
- package/src/container/Container.d.ts +0 -38
- package/src/context/Context.d.ts +0 -36
- package/src/cors/CorsHandler.d.ts +0 -47
- package/src/decorators/Controller.d.ts +0 -13
- package/src/decorators/Inject.d.ts +0 -6
- package/src/decorators/Middleware.d.ts +0 -5
- package/src/decorators/Service.d.ts +0 -9
- package/src/decorators/methods.d.ts +0 -7
- package/src/decorators/params.d.ts +0 -13
- package/src/events/Lifecycle.d.ts +0 -54
- package/src/exceptions/HttpException.d.ts +0 -43
- package/src/index.d.ts +0 -42
- package/src/metadata.d.ts +0 -41
- package/src/middleware/CarnoMiddleware.d.ts +0 -12
- package/src/router/RadixRouter.d.ts +0 -19
- package/src/testing/TestHarness.d.ts +0 -71
- package/src/utils/Metadata.d.ts +0 -20
- package/src/utils/parseQuery.d.ts +0 -23
- package/src/validation/ValibotAdapter.d.ts +0 -30
- package/src/validation/ValidatorAdapter.d.ts +0 -54
- package/src/validation/ZodAdapter.d.ts +0 -35
package/src/cache/RedisDriver.ts
CHANGED
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import type { CacheDriver } from './CacheDriver';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Redis Cache Driver Configuration.
|
|
5
|
-
*/
|
|
6
|
-
export interface RedisConfig {
|
|
7
|
-
host?: string;
|
|
8
|
-
port?: number;
|
|
9
|
-
password?: string;
|
|
10
|
-
db?: number;
|
|
11
|
-
url?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Redis Cache Driver.
|
|
16
|
-
* For distributed caching in multi-instance deployments.
|
|
17
|
-
*
|
|
18
|
-
* Uses Bun's native Redis client for maximum performance.
|
|
19
|
-
*/
|
|
20
|
-
export class RedisDriver implements CacheDriver {
|
|
21
|
-
readonly name = 'RedisDriver';
|
|
22
|
-
|
|
23
|
-
private client: any = null;
|
|
24
|
-
private connected = false;
|
|
25
|
-
|
|
26
|
-
constructor(private config: RedisConfig = {}) { }
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Connect to Redis (lazy - connects on first use).
|
|
30
|
-
*/
|
|
31
|
-
private async ensureConnected(): Promise<void> {
|
|
32
|
-
if (this.connected) return;
|
|
33
|
-
|
|
34
|
-
const url = this.config.url ||
|
|
35
|
-
`redis://${this.config.host || 'localhost'}:${this.config.port || 6379}`;
|
|
36
|
-
|
|
37
|
-
// Use Bun's native Redis client if available
|
|
38
|
-
if (typeof Bun !== 'undefined' && (Bun as any).redis) {
|
|
39
|
-
this.client = new (Bun as any).redis(url);
|
|
40
|
-
} else {
|
|
41
|
-
// Fallback to ioredis
|
|
42
|
-
try {
|
|
43
|
-
const Redis = require('ioredis');
|
|
44
|
-
this.client = new Redis({
|
|
45
|
-
host: this.config.host || 'localhost',
|
|
46
|
-
port: this.config.port || 6379,
|
|
47
|
-
password: this.config.password,
|
|
48
|
-
db: this.config.db || 0
|
|
49
|
-
});
|
|
50
|
-
} catch {
|
|
51
|
-
throw new Error('Redis client not available. Install ioredis or use Bun with Redis support.');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.connected = true;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
async get<T>(key: string): Promise<T | null> {
|
|
59
|
-
await this.ensureConnected();
|
|
60
|
-
|
|
61
|
-
const value = await this.client.get(key);
|
|
62
|
-
|
|
63
|
-
if (value === null) {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
return JSON.parse(value);
|
|
69
|
-
} catch {
|
|
70
|
-
return value as T;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
async set<T>(key: string, value: T, ttl?: number): Promise<boolean> {
|
|
75
|
-
await this.ensureConnected();
|
|
76
|
-
|
|
77
|
-
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
78
|
-
|
|
79
|
-
if (ttl) {
|
|
80
|
-
await this.client.setex(key, ttl, serialized);
|
|
81
|
-
} else {
|
|
82
|
-
await this.client.set(key, serialized);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async del(key: string): Promise<boolean> {
|
|
89
|
-
await this.ensureConnected();
|
|
90
|
-
|
|
91
|
-
const result = await this.client.del(key);
|
|
92
|
-
|
|
93
|
-
return result > 0;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async has(key: string): Promise<boolean> {
|
|
97
|
-
await this.ensureConnected();
|
|
98
|
-
|
|
99
|
-
const result = await this.client.exists(key);
|
|
100
|
-
|
|
101
|
-
return result > 0;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async clear(): Promise<void> {
|
|
105
|
-
await this.ensureConnected();
|
|
106
|
-
|
|
107
|
-
await this.client.flushdb();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async close(): Promise<void> {
|
|
111
|
-
if (this.client && this.connected) {
|
|
112
|
-
await this.client.quit?.();
|
|
113
|
-
this.connected = false;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
1
|
+
import type { CacheDriver } from './CacheDriver';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Redis Cache Driver Configuration.
|
|
5
|
+
*/
|
|
6
|
+
export interface RedisConfig {
|
|
7
|
+
host?: string;
|
|
8
|
+
port?: number;
|
|
9
|
+
password?: string;
|
|
10
|
+
db?: number;
|
|
11
|
+
url?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Redis Cache Driver.
|
|
16
|
+
* For distributed caching in multi-instance deployments.
|
|
17
|
+
*
|
|
18
|
+
* Uses Bun's native Redis client for maximum performance.
|
|
19
|
+
*/
|
|
20
|
+
export class RedisDriver implements CacheDriver {
|
|
21
|
+
readonly name = 'RedisDriver';
|
|
22
|
+
|
|
23
|
+
private client: any = null;
|
|
24
|
+
private connected = false;
|
|
25
|
+
|
|
26
|
+
constructor(private config: RedisConfig = {}) { }
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Connect to Redis (lazy - connects on first use).
|
|
30
|
+
*/
|
|
31
|
+
private async ensureConnected(): Promise<void> {
|
|
32
|
+
if (this.connected) return;
|
|
33
|
+
|
|
34
|
+
const url = this.config.url ||
|
|
35
|
+
`redis://${this.config.host || 'localhost'}:${this.config.port || 6379}`;
|
|
36
|
+
|
|
37
|
+
// Use Bun's native Redis client if available
|
|
38
|
+
if (typeof Bun !== 'undefined' && (Bun as any).redis) {
|
|
39
|
+
this.client = new (Bun as any).redis(url);
|
|
40
|
+
} else {
|
|
41
|
+
// Fallback to ioredis
|
|
42
|
+
try {
|
|
43
|
+
const Redis = require('ioredis');
|
|
44
|
+
this.client = new Redis({
|
|
45
|
+
host: this.config.host || 'localhost',
|
|
46
|
+
port: this.config.port || 6379,
|
|
47
|
+
password: this.config.password,
|
|
48
|
+
db: this.config.db || 0
|
|
49
|
+
});
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error('Redis client not available. Install ioredis or use Bun with Redis support.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
this.connected = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async get<T>(key: string): Promise<T | null> {
|
|
59
|
+
await this.ensureConnected();
|
|
60
|
+
|
|
61
|
+
const value = await this.client.get(key);
|
|
62
|
+
|
|
63
|
+
if (value === null) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(value);
|
|
69
|
+
} catch {
|
|
70
|
+
return value as T;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async set<T>(key: string, value: T, ttl?: number): Promise<boolean> {
|
|
75
|
+
await this.ensureConnected();
|
|
76
|
+
|
|
77
|
+
const serialized = typeof value === 'string' ? value : JSON.stringify(value);
|
|
78
|
+
|
|
79
|
+
if (ttl) {
|
|
80
|
+
await this.client.setex(key, ttl, serialized);
|
|
81
|
+
} else {
|
|
82
|
+
await this.client.set(key, serialized);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async del(key: string): Promise<boolean> {
|
|
89
|
+
await this.ensureConnected();
|
|
90
|
+
|
|
91
|
+
const result = await this.client.del(key);
|
|
92
|
+
|
|
93
|
+
return result > 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async has(key: string): Promise<boolean> {
|
|
97
|
+
await this.ensureConnected();
|
|
98
|
+
|
|
99
|
+
const result = await this.client.exists(key);
|
|
100
|
+
|
|
101
|
+
return result > 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async clear(): Promise<void> {
|
|
105
|
+
await this.ensureConnected();
|
|
106
|
+
|
|
107
|
+
await this.client.flushdb();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async close(): Promise<void> {
|
|
111
|
+
if (this.client && this.connected) {
|
|
112
|
+
await this.client.quit?.();
|
|
113
|
+
this.connected = false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -1,167 +1,167 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JIT Compiler for Turbo.
|
|
3
|
-
*
|
|
4
|
-
* Aggressive AOT optimizations:
|
|
5
|
-
* - Detects async at compile time (not runtime)
|
|
6
|
-
* - Generates specialized handlers via new Function()
|
|
7
|
-
* - Inlines parameter access
|
|
8
|
-
* - Zero overhead for simple handlers
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { ParamType } from '../decorators/params';
|
|
12
|
-
|
|
13
|
-
export interface ParamInfo {
|
|
14
|
-
type: ParamType;
|
|
15
|
-
key?: string;
|
|
16
|
-
index: number;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface CompiledHandler {
|
|
20
|
-
fn: Function;
|
|
21
|
-
isAsync: boolean;
|
|
22
|
-
isStatic: boolean;
|
|
23
|
-
staticValue?: any;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const ASYNC_REGEX = /^async\s|^\([^)]*\)\s*=>\s*\{[\s\S]*await\s|^function\s*\*|\.then\s*\(/;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Detects if function is async at compile time.
|
|
30
|
-
* Checks: async keyword, await usage, generators, .then()
|
|
31
|
-
*/
|
|
32
|
-
export function isAsyncFunction(fn: Function): boolean {
|
|
33
|
-
if (fn.constructor.name === 'AsyncFunction') {
|
|
34
|
-
return true;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const source = fn.toString();
|
|
38
|
-
|
|
39
|
-
return ASYNC_REGEX.test(source);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Detects if handler returns a static value.
|
|
44
|
-
* Static handlers can be pre-computed at startup.
|
|
45
|
-
*/
|
|
46
|
-
export function isStaticHandler(fn: Function): boolean {
|
|
47
|
-
const source = fn.toString();
|
|
48
|
-
|
|
49
|
-
if (source.includes('this.') || source.includes('await')) {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const returnMatch = source.match(/=>\s*["'`]|return\s+["'`]|=>\s*\{|=>\s*\d/);
|
|
54
|
-
|
|
55
|
-
return !!returnMatch;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Compiles handler with inlined parameter access.
|
|
60
|
-
* Uses new Function() for maximum V8 optimization.
|
|
61
|
-
*/
|
|
62
|
-
export function compileHandler(
|
|
63
|
-
instance: any,
|
|
64
|
-
methodName: string,
|
|
65
|
-
params: ParamInfo[]
|
|
66
|
-
): CompiledHandler {
|
|
67
|
-
const method = instance[methodName];
|
|
68
|
-
const bound = method.bind(instance);
|
|
69
|
-
const async = isAsyncFunction(method);
|
|
70
|
-
|
|
71
|
-
if (params.length === 0) {
|
|
72
|
-
const isStatic = isStaticHandler(method);
|
|
73
|
-
|
|
74
|
-
if (isStatic && !async) {
|
|
75
|
-
const staticValue = bound();
|
|
76
|
-
|
|
77
|
-
return {
|
|
78
|
-
fn: bound,
|
|
79
|
-
isAsync: false,
|
|
80
|
-
isStatic: true,
|
|
81
|
-
staticValue
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
fn: bound,
|
|
87
|
-
isAsync: async,
|
|
88
|
-
isStatic: false
|
|
89
|
-
};
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const argExprs = params
|
|
93
|
-
.sort((a, b) => a.index - b.index)
|
|
94
|
-
.map(p => buildArgExpression(p));
|
|
95
|
-
|
|
96
|
-
const argsCode = argExprs.join(',');
|
|
97
|
-
const hasBody = params.some(p => p.type === 'body');
|
|
98
|
-
|
|
99
|
-
if (hasBody) {
|
|
100
|
-
const code = `return async function(c){
|
|
101
|
-
await c.parseBody();
|
|
102
|
-
return h(${argsCode});
|
|
103
|
-
}`;
|
|
104
|
-
|
|
105
|
-
return {
|
|
106
|
-
fn: new Function('h', code)(bound),
|
|
107
|
-
isAsync: true,
|
|
108
|
-
isStatic: false
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (async) {
|
|
113
|
-
const code = `return async function(c){
|
|
114
|
-
return await h(${argsCode});
|
|
115
|
-
}`;
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
fn: new Function('h', code)(bound),
|
|
119
|
-
isAsync: true,
|
|
120
|
-
isStatic: false
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const code = `return function(c){
|
|
125
|
-
return h(${argsCode});
|
|
126
|
-
}`;
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
fn: new Function('h', code)(bound),
|
|
130
|
-
isAsync: false,
|
|
131
|
-
isStatic: false
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function escapeKey(key: string): string {
|
|
136
|
-
return key.replace(/['\"\\]/g, '\\$&');
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function buildArgExpression(param: ParamInfo): string {
|
|
140
|
-
const key = param.key ? escapeKey(param.key) : undefined;
|
|
141
|
-
|
|
142
|
-
switch (param.type) {
|
|
143
|
-
case 'param':
|
|
144
|
-
return key ? `c.params['${key}']` : 'c.params';
|
|
145
|
-
|
|
146
|
-
case 'query':
|
|
147
|
-
return key ? `c.query['${key}']` : 'c.query';
|
|
148
|
-
|
|
149
|
-
case 'body':
|
|
150
|
-
return key ? `c.body['${key}']` : 'c.body';
|
|
151
|
-
|
|
152
|
-
case 'header':
|
|
153
|
-
return key ? `c.req.headers.get('${key}')` : 'c.req.headers';
|
|
154
|
-
|
|
155
|
-
case 'req':
|
|
156
|
-
return 'c.req';
|
|
157
|
-
|
|
158
|
-
case 'ctx':
|
|
159
|
-
return 'c';
|
|
160
|
-
|
|
161
|
-
case 'locals':
|
|
162
|
-
return key ? `c.locals['${key}']` : 'c.locals';
|
|
163
|
-
|
|
164
|
-
default:
|
|
165
|
-
return 'undefined';
|
|
166
|
-
}
|
|
167
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* JIT Compiler for Turbo.
|
|
3
|
+
*
|
|
4
|
+
* Aggressive AOT optimizations:
|
|
5
|
+
* - Detects async at compile time (not runtime)
|
|
6
|
+
* - Generates specialized handlers via new Function()
|
|
7
|
+
* - Inlines parameter access
|
|
8
|
+
* - Zero overhead for simple handlers
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ParamType } from '../decorators/params';
|
|
12
|
+
|
|
13
|
+
export interface ParamInfo {
|
|
14
|
+
type: ParamType;
|
|
15
|
+
key?: string;
|
|
16
|
+
index: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface CompiledHandler {
|
|
20
|
+
fn: Function;
|
|
21
|
+
isAsync: boolean;
|
|
22
|
+
isStatic: boolean;
|
|
23
|
+
staticValue?: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const ASYNC_REGEX = /^async\s|^\([^)]*\)\s*=>\s*\{[\s\S]*await\s|^function\s*\*|\.then\s*\(/;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Detects if function is async at compile time.
|
|
30
|
+
* Checks: async keyword, await usage, generators, .then()
|
|
31
|
+
*/
|
|
32
|
+
export function isAsyncFunction(fn: Function): boolean {
|
|
33
|
+
if (fn.constructor.name === 'AsyncFunction') {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const source = fn.toString();
|
|
38
|
+
|
|
39
|
+
return ASYNC_REGEX.test(source);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Detects if handler returns a static value.
|
|
44
|
+
* Static handlers can be pre-computed at startup.
|
|
45
|
+
*/
|
|
46
|
+
export function isStaticHandler(fn: Function): boolean {
|
|
47
|
+
const source = fn.toString();
|
|
48
|
+
|
|
49
|
+
if (source.includes('this.') || source.includes('await')) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const returnMatch = source.match(/=>\s*["'`]|return\s+["'`]|=>\s*\{|=>\s*\d/);
|
|
54
|
+
|
|
55
|
+
return !!returnMatch;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Compiles handler with inlined parameter access.
|
|
60
|
+
* Uses new Function() for maximum V8 optimization.
|
|
61
|
+
*/
|
|
62
|
+
export function compileHandler(
|
|
63
|
+
instance: any,
|
|
64
|
+
methodName: string,
|
|
65
|
+
params: ParamInfo[]
|
|
66
|
+
): CompiledHandler {
|
|
67
|
+
const method = instance[methodName];
|
|
68
|
+
const bound = method.bind(instance);
|
|
69
|
+
const async = isAsyncFunction(method);
|
|
70
|
+
|
|
71
|
+
if (params.length === 0) {
|
|
72
|
+
const isStatic = isStaticHandler(method);
|
|
73
|
+
|
|
74
|
+
if (isStatic && !async) {
|
|
75
|
+
const staticValue = bound();
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
fn: bound,
|
|
79
|
+
isAsync: false,
|
|
80
|
+
isStatic: true,
|
|
81
|
+
staticValue
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
fn: bound,
|
|
87
|
+
isAsync: async,
|
|
88
|
+
isStatic: false
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const argExprs = params
|
|
93
|
+
.sort((a, b) => a.index - b.index)
|
|
94
|
+
.map(p => buildArgExpression(p));
|
|
95
|
+
|
|
96
|
+
const argsCode = argExprs.join(',');
|
|
97
|
+
const hasBody = params.some(p => p.type === 'body');
|
|
98
|
+
|
|
99
|
+
if (hasBody) {
|
|
100
|
+
const code = `return async function(c){
|
|
101
|
+
await c.parseBody();
|
|
102
|
+
return h(${argsCode});
|
|
103
|
+
}`;
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
fn: new Function('h', code)(bound),
|
|
107
|
+
isAsync: true,
|
|
108
|
+
isStatic: false
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (async) {
|
|
113
|
+
const code = `return async function(c){
|
|
114
|
+
return await h(${argsCode});
|
|
115
|
+
}`;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
fn: new Function('h', code)(bound),
|
|
119
|
+
isAsync: true,
|
|
120
|
+
isStatic: false
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const code = `return function(c){
|
|
125
|
+
return h(${argsCode});
|
|
126
|
+
}`;
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
fn: new Function('h', code)(bound),
|
|
130
|
+
isAsync: false,
|
|
131
|
+
isStatic: false
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function escapeKey(key: string): string {
|
|
136
|
+
return key.replace(/['\"\\]/g, '\\$&');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function buildArgExpression(param: ParamInfo): string {
|
|
140
|
+
const key = param.key ? escapeKey(param.key) : undefined;
|
|
141
|
+
|
|
142
|
+
switch (param.type) {
|
|
143
|
+
case 'param':
|
|
144
|
+
return key ? `c.params['${key}']` : 'c.params';
|
|
145
|
+
|
|
146
|
+
case 'query':
|
|
147
|
+
return key ? `c.query['${key}']` : 'c.query';
|
|
148
|
+
|
|
149
|
+
case 'body':
|
|
150
|
+
return key ? `c.body['${key}']` : 'c.body';
|
|
151
|
+
|
|
152
|
+
case 'header':
|
|
153
|
+
return key ? `c.req.headers.get('${key}')` : 'c.req.headers';
|
|
154
|
+
|
|
155
|
+
case 'req':
|
|
156
|
+
return 'c.req';
|
|
157
|
+
|
|
158
|
+
case 'ctx':
|
|
159
|
+
return 'c';
|
|
160
|
+
|
|
161
|
+
case 'locals':
|
|
162
|
+
return key ? `c.locals['${key}']` : 'c.locals';
|
|
163
|
+
|
|
164
|
+
default:
|
|
165
|
+
return 'undefined';
|
|
166
|
+
}
|
|
167
|
+
}
|