wynkjs 1.0.4 → 1.0.6
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/README.md +296 -313
- package/dist/cors.d.ts +28 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +121 -0
- package/dist/decorators/exception.decorators.d.ts +1 -0
- package/dist/decorators/exception.decorators.d.ts.map +1 -1
- package/dist/decorators/exception.decorators.js +20 -3
- package/dist/decorators/guard.decorators.d.ts.map +1 -1
- package/dist/decorators/guard.decorators.js +11 -5
- package/dist/decorators/http.decorators.d.ts +8 -3
- package/dist/decorators/http.decorators.d.ts.map +1 -1
- package/dist/decorators/http.decorators.js +9 -2
- package/dist/decorators/interceptor.advanced.d.ts +9 -9
- package/dist/decorators/interceptor.advanced.d.ts.map +1 -1
- package/dist/decorators/interceptor.advanced.js +7 -7
- package/dist/decorators/interceptor.decorators.d.ts +9 -7
- package/dist/decorators/interceptor.decorators.d.ts.map +1 -1
- package/dist/decorators/interceptor.decorators.js +29 -18
- package/dist/decorators/param.decorators.d.ts +2 -2
- package/dist/decorators/param.decorators.js +1 -1
- package/dist/decorators/pipe.decorators.d.ts +2 -2
- package/dist/decorators/pipe.decorators.js +2 -2
- package/dist/factory.d.ts +29 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +155 -180
- package/dist/global-prefix.d.ts +49 -0
- package/dist/global-prefix.d.ts.map +1 -0
- package/dist/global-prefix.js +155 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/interfaces/interceptor.interface.d.ts +15 -0
- package/dist/interfaces/interceptor.interface.d.ts.map +1 -0
- package/dist/interfaces/interceptor.interface.js +1 -0
- package/dist/optimized-handler.d.ts +31 -0
- package/dist/optimized-handler.d.ts.map +1 -0
- package/dist/optimized-handler.js +180 -0
- package/dist/pipes/validation.pipe.d.ts +10 -10
- package/dist/pipes/validation.pipe.js +4 -4
- package/dist/ultra-optimized-handler.d.ts +51 -0
- package/dist/ultra-optimized-handler.d.ts.map +1 -0
- package/dist/ultra-optimized-handler.js +302 -0
- package/package.json +10 -8
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ULTRA-OPTIMIZED Handler Builder for WynkJS
|
|
3
|
+
*
|
|
4
|
+
* KEY OPTIMIZATION: Eliminate nested async/await and IIFEs
|
|
5
|
+
*
|
|
6
|
+
* Performance findings:
|
|
7
|
+
* - Direct sync: 32ms per 1M calls
|
|
8
|
+
* - Single async: 42ms per 1M calls
|
|
9
|
+
* - Nested async + IIFE: 232ms per 1M calls (5.7x SLOWER!)
|
|
10
|
+
*
|
|
11
|
+
* Root cause: The current pattern creates an IIFE for every request:
|
|
12
|
+
* return (async () => { try { ... } catch { ... } })();
|
|
13
|
+
*
|
|
14
|
+
* This creates massive overhead because:
|
|
15
|
+
* 1. Creates a new Promise on EVERY request
|
|
16
|
+
* 2. Wraps it in a try-catch block
|
|
17
|
+
* 3. Immediately invokes it (IIFE pattern)
|
|
18
|
+
* 4. Then awaits the result
|
|
19
|
+
*
|
|
20
|
+
* Solution: Build a SINGLE async function at registration time,
|
|
21
|
+
* not nested functions that get called on every request.
|
|
22
|
+
*/
|
|
23
|
+
import { createExecutionContext, executeGuards, } from "./decorators/guard.decorators";
|
|
24
|
+
import { executeInterceptors } from "./decorators/interceptor.decorators";
|
|
25
|
+
import { executePipes } from "./decorators/pipe.decorators";
|
|
26
|
+
import { executeExceptionFilters, HttpException, } from "./decorators/exception.decorators";
|
|
27
|
+
/**
|
|
28
|
+
* Ultra-optimized handler builder
|
|
29
|
+
* Builds a SINGLE async function, not nested closures
|
|
30
|
+
*/
|
|
31
|
+
export function buildUltraOptimizedHandler(options) {
|
|
32
|
+
const { instance, methodName, ControllerClass, params, allGuards, allInterceptors, allPipes, allFilters, httpCode, headers, redirect, routePath, routeMethod, } = options;
|
|
33
|
+
// Pre-sort params once during registration
|
|
34
|
+
if (params.length > 0) {
|
|
35
|
+
params.sort((a, b) => a.index - b.index);
|
|
36
|
+
}
|
|
37
|
+
// Determine which features are actually used
|
|
38
|
+
const hasGuards = allGuards.length > 0;
|
|
39
|
+
const hasInterceptors = allInterceptors.length > 0;
|
|
40
|
+
const hasPipes = allPipes.length > 0;
|
|
41
|
+
const hasFilters = allFilters.length > 0;
|
|
42
|
+
const hasParams = params.length > 0;
|
|
43
|
+
const hasResponseModifiers = !!(httpCode || headers || redirect);
|
|
44
|
+
// Build specialized handler based on what's actually needed
|
|
45
|
+
// This eliminates ALL conditional checks at runtime
|
|
46
|
+
// CASE 1: Absolute minimal - no features at all (like /health endpoint)
|
|
47
|
+
if (!hasGuards &&
|
|
48
|
+
!hasInterceptors &&
|
|
49
|
+
!hasPipes &&
|
|
50
|
+
!hasFilters &&
|
|
51
|
+
!hasParams &&
|
|
52
|
+
!hasResponseModifiers) {
|
|
53
|
+
// Direct call - zero overhead!
|
|
54
|
+
return async (ctx) => {
|
|
55
|
+
return await instance[methodName](ctx);
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// CASE 2: Has params but no other features
|
|
59
|
+
if (!hasGuards &&
|
|
60
|
+
!hasInterceptors &&
|
|
61
|
+
!hasFilters &&
|
|
62
|
+
!hasResponseModifiers &&
|
|
63
|
+
hasParams) {
|
|
64
|
+
return async (ctx) => {
|
|
65
|
+
const args = new Array(params.length);
|
|
66
|
+
for (const param of params) {
|
|
67
|
+
let value;
|
|
68
|
+
switch (param.type) {
|
|
69
|
+
case "body":
|
|
70
|
+
value = param.data ? ctx.body?.[param.data] : ctx.body;
|
|
71
|
+
break;
|
|
72
|
+
case "param":
|
|
73
|
+
value = param.data ? ctx.params?.[param.data] : ctx.params;
|
|
74
|
+
break;
|
|
75
|
+
case "query":
|
|
76
|
+
value = param.data ? ctx.query?.[param.data] : ctx.query;
|
|
77
|
+
break;
|
|
78
|
+
case "headers":
|
|
79
|
+
value = param.data
|
|
80
|
+
? ctx.headers?.get?.(param.data) ||
|
|
81
|
+
ctx.request?.headers?.get?.(param.data)
|
|
82
|
+
: ctx.headers || ctx.request?.headers;
|
|
83
|
+
break;
|
|
84
|
+
case "request":
|
|
85
|
+
value = ctx.request || ctx;
|
|
86
|
+
break;
|
|
87
|
+
case "response":
|
|
88
|
+
value = ctx.set || ctx.response;
|
|
89
|
+
break;
|
|
90
|
+
case "context":
|
|
91
|
+
if (param.data) {
|
|
92
|
+
const keys = param.data.split(".");
|
|
93
|
+
value = keys.reduce((obj, key) => obj?.[key], ctx);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
value = ctx;
|
|
97
|
+
}
|
|
98
|
+
break;
|
|
99
|
+
case "user":
|
|
100
|
+
value = param.data ? ctx.user?.[param.data] : ctx.user;
|
|
101
|
+
break;
|
|
102
|
+
case "file":
|
|
103
|
+
value = ctx.body?.file || ctx.file;
|
|
104
|
+
break;
|
|
105
|
+
case "files":
|
|
106
|
+
value = ctx.body?.files || ctx.files;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
// Apply pipes if any
|
|
110
|
+
if (hasPipes) {
|
|
111
|
+
const metadata = {
|
|
112
|
+
type: param.type,
|
|
113
|
+
data: param.data,
|
|
114
|
+
};
|
|
115
|
+
value = await executePipes(allPipes, value, metadata);
|
|
116
|
+
}
|
|
117
|
+
if (param.pipes && param.pipes.length > 0) {
|
|
118
|
+
const metadata = {
|
|
119
|
+
type: param.type,
|
|
120
|
+
data: param.data,
|
|
121
|
+
};
|
|
122
|
+
value = await executePipes(param.pipes, value, metadata);
|
|
123
|
+
}
|
|
124
|
+
args[param.index] = value;
|
|
125
|
+
}
|
|
126
|
+
return await instance[methodName].apply(instance, args);
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// CASE 3: Full-featured handler (has guards/interceptors/filters)
|
|
130
|
+
// This is where we MUST use try-catch, but still avoid nested async
|
|
131
|
+
return async (ctx) => {
|
|
132
|
+
try {
|
|
133
|
+
// Guards
|
|
134
|
+
if (hasGuards) {
|
|
135
|
+
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
136
|
+
// Add route metadata
|
|
137
|
+
executionContext.path = routePath;
|
|
138
|
+
executionContext.method = routeMethod;
|
|
139
|
+
executionContext.request = ctx.request || ctx;
|
|
140
|
+
executionContext.body = ctx.body;
|
|
141
|
+
executionContext.params = ctx.params;
|
|
142
|
+
executionContext.query = ctx.query;
|
|
143
|
+
executionContext.headers = ctx.headers || ctx.request?.headers;
|
|
144
|
+
const canActivate = await executeGuards(allGuards, executionContext);
|
|
145
|
+
if (!canActivate) {
|
|
146
|
+
throw new HttpException("Forbidden", 403, "Access denied");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Prepare the actual handler execution
|
|
150
|
+
const executeMethod = async () => {
|
|
151
|
+
if (!hasParams) {
|
|
152
|
+
return await instance[methodName](ctx);
|
|
153
|
+
}
|
|
154
|
+
const args = new Array(params.length);
|
|
155
|
+
for (const param of params) {
|
|
156
|
+
let value;
|
|
157
|
+
switch (param.type) {
|
|
158
|
+
case "body":
|
|
159
|
+
value = param.data ? ctx.body?.[param.data] : ctx.body;
|
|
160
|
+
break;
|
|
161
|
+
case "param":
|
|
162
|
+
value = param.data ? ctx.params?.[param.data] : ctx.params;
|
|
163
|
+
break;
|
|
164
|
+
case "query":
|
|
165
|
+
value = param.data ? ctx.query?.[param.data] : ctx.query;
|
|
166
|
+
break;
|
|
167
|
+
case "headers":
|
|
168
|
+
value = param.data
|
|
169
|
+
? ctx.headers?.get?.(param.data) ||
|
|
170
|
+
ctx.request?.headers?.get?.(param.data)
|
|
171
|
+
: ctx.headers || ctx.request?.headers;
|
|
172
|
+
break;
|
|
173
|
+
case "request":
|
|
174
|
+
value = ctx.request || ctx;
|
|
175
|
+
break;
|
|
176
|
+
case "response":
|
|
177
|
+
value = ctx.set || ctx.response;
|
|
178
|
+
break;
|
|
179
|
+
case "context":
|
|
180
|
+
if (param.data) {
|
|
181
|
+
const keys = param.data.split(".");
|
|
182
|
+
value = keys.reduce((obj, key) => obj?.[key], ctx);
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
value = ctx;
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
case "user":
|
|
189
|
+
value = param.data ? ctx.user?.[param.data] : ctx.user;
|
|
190
|
+
break;
|
|
191
|
+
case "file":
|
|
192
|
+
value = ctx.body?.file || ctx.file;
|
|
193
|
+
break;
|
|
194
|
+
case "files":
|
|
195
|
+
value = ctx.body?.files || ctx.files;
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
if (hasPipes) {
|
|
199
|
+
const metadata = {
|
|
200
|
+
type: param.type,
|
|
201
|
+
data: param.data,
|
|
202
|
+
};
|
|
203
|
+
value = await executePipes(allPipes, value, metadata);
|
|
204
|
+
}
|
|
205
|
+
if (param.pipes && param.pipes.length > 0) {
|
|
206
|
+
const metadata = {
|
|
207
|
+
type: param.type,
|
|
208
|
+
data: param.data,
|
|
209
|
+
};
|
|
210
|
+
value = await executePipes(param.pipes, value, metadata);
|
|
211
|
+
}
|
|
212
|
+
args[param.index] = value;
|
|
213
|
+
}
|
|
214
|
+
return await instance[methodName].apply(instance, args);
|
|
215
|
+
};
|
|
216
|
+
// Interceptors
|
|
217
|
+
let result;
|
|
218
|
+
if (hasInterceptors) {
|
|
219
|
+
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
220
|
+
// Add route metadata
|
|
221
|
+
executionContext.path = routePath;
|
|
222
|
+
executionContext.method = routeMethod;
|
|
223
|
+
executionContext.request = ctx.request || ctx;
|
|
224
|
+
executionContext.body = ctx.body;
|
|
225
|
+
executionContext.params = ctx.params;
|
|
226
|
+
executionContext.query = ctx.query;
|
|
227
|
+
executionContext.headers = ctx.headers || ctx.request?.headers;
|
|
228
|
+
result = await executeInterceptors(allInterceptors, executionContext, executeMethod);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
result = await executeMethod();
|
|
232
|
+
}
|
|
233
|
+
// Response modifiers
|
|
234
|
+
if (hasResponseModifiers) {
|
|
235
|
+
if (redirect) {
|
|
236
|
+
ctx.set.redirect = redirect.url;
|
|
237
|
+
ctx.set.status = redirect.statusCode;
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (httpCode) {
|
|
241
|
+
ctx.set.status = httpCode;
|
|
242
|
+
}
|
|
243
|
+
if (headers) {
|
|
244
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
245
|
+
ctx.set.headers[key] = value;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return result;
|
|
250
|
+
}
|
|
251
|
+
catch (error) {
|
|
252
|
+
// Exception filters (only if configured)
|
|
253
|
+
if (hasFilters) {
|
|
254
|
+
const executionContext = createExecutionContext(ctx, instance[methodName], ControllerClass);
|
|
255
|
+
// Add route metadata
|
|
256
|
+
executionContext.path = routePath;
|
|
257
|
+
executionContext.method = routeMethod;
|
|
258
|
+
executionContext.request = ctx.request || ctx;
|
|
259
|
+
executionContext.body = ctx.body;
|
|
260
|
+
executionContext.params = ctx.params;
|
|
261
|
+
executionContext.query = ctx.query;
|
|
262
|
+
executionContext.headers = ctx.headers || ctx.request?.headers;
|
|
263
|
+
try {
|
|
264
|
+
const result = await executeExceptionFilters(allFilters, error, executionContext);
|
|
265
|
+
if (result) {
|
|
266
|
+
if (result.statusCode) {
|
|
267
|
+
ctx.set.status = result.statusCode;
|
|
268
|
+
}
|
|
269
|
+
return result;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch (filterError) {
|
|
273
|
+
error = filterError;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Default error handling
|
|
277
|
+
if (error instanceof HttpException) {
|
|
278
|
+
ctx.set.status = error.getStatus();
|
|
279
|
+
return error.getResponse();
|
|
280
|
+
}
|
|
281
|
+
ctx.set.status = 500;
|
|
282
|
+
return {
|
|
283
|
+
statusCode: 500,
|
|
284
|
+
message: error.message || "Internal server error",
|
|
285
|
+
error: "Internal Server Error",
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Build middleware chain - same as before
|
|
292
|
+
*/
|
|
293
|
+
export function buildMiddlewareChain(handler, middlewares) {
|
|
294
|
+
if (middlewares.length === 0) {
|
|
295
|
+
return handler;
|
|
296
|
+
}
|
|
297
|
+
return middlewares.reduceRight((next, middleware) => {
|
|
298
|
+
return async (ctx) => {
|
|
299
|
+
return await middleware(ctx, () => next(ctx));
|
|
300
|
+
};
|
|
301
|
+
}, handler);
|
|
302
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wynkjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "A high-performance TypeScript framework built on Elysia for Bun with elegant decorator-based architecture - 10x faster than Express/NestJS",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -42,15 +42,14 @@
|
|
|
42
42
|
"scripts": {
|
|
43
43
|
"build": "tsc",
|
|
44
44
|
"prepublishOnly": "npm run build",
|
|
45
|
-
"test": "bun test"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
45
|
+
"test": "bun test",
|
|
46
|
+
"test:all": "bun test tests/",
|
|
47
|
+
"test:core": "bash tests/test-core.sh",
|
|
48
|
+
"test:watch": "bun test tests/ --watch",
|
|
49
|
+
"test:coverage": "bun test tests/ --coverage"
|
|
50
50
|
},
|
|
51
|
+
"peerDependencies": {},
|
|
51
52
|
"devDependencies": {
|
|
52
|
-
"elysia": "^1.0.0",
|
|
53
|
-
"reflect-metadata": "^0.2.2",
|
|
54
53
|
"@types/bun": "latest",
|
|
55
54
|
"typescript": "^5.0.0"
|
|
56
55
|
},
|
|
@@ -69,6 +68,9 @@
|
|
|
69
68
|
"LICENSE"
|
|
70
69
|
],
|
|
71
70
|
"dependencies": {
|
|
71
|
+
"elysia": "^1.0.0",
|
|
72
|
+
"reflect-metadata": "^0.2.2",
|
|
73
|
+
"@elysiajs/cors": "^1.4.0",
|
|
72
74
|
"tsyringe": "^4.10.0"
|
|
73
75
|
}
|
|
74
76
|
}
|