@engjts/nexus 0.1.7 → 0.1.8
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/BENCHMARK_REPORT.md +343 -0
- package/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/advanced/playground/generatePlaygroundHTML.ts +107 -0
- package/src/advanced/playground/playground.ts +225 -145
- package/src/advanced/playground/types.ts +29 -0
- package/src/core/application.ts +202 -84
- package/src/core/context-pool.ts +8 -56
- package/src/core/context.ts +497 -53
- package/src/core/index.ts +14 -0
- package/src/core/middleware.ts +99 -89
- package/src/core/router/file-router.ts +41 -12
- package/src/core/router/index.ts +213 -180
- package/src/core/router/radix-tree.ts +20 -4
- package/src/core/serializer.ts +397 -0
- package/src/core/types.ts +43 -1
- package/src/index.ts +17 -0
package/src/core/index.ts
CHANGED
|
@@ -76,5 +76,19 @@ export {
|
|
|
76
76
|
export { BufferPool, StreamUtils } from './performance/buffer-pool';
|
|
77
77
|
export { MiddlewareOptimizer, PerformanceMonitor } from './performance/middleware-optimizer';
|
|
78
78
|
|
|
79
|
+
// Fast JSON Serializer
|
|
80
|
+
export {
|
|
81
|
+
createSerializer,
|
|
82
|
+
createArraySerializer,
|
|
83
|
+
serialize,
|
|
84
|
+
serializerRegistry,
|
|
85
|
+
SerializerRegistry,
|
|
86
|
+
CommonSchemas,
|
|
87
|
+
type JSONSchema,
|
|
88
|
+
type ResponseSchemaConfig,
|
|
89
|
+
type SerializerFunction,
|
|
90
|
+
type SerializerOptions
|
|
91
|
+
} from './serializer';
|
|
92
|
+
|
|
79
93
|
// Re-export Zod for convenience
|
|
80
94
|
export { z } from 'zod';
|
package/src/core/middleware.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { Context, Middleware, Handler, Response, Next, LifecycleHooks } from './
|
|
|
12
12
|
export class MiddlewareExecutor {
|
|
13
13
|
/**
|
|
14
14
|
* Execute middleware chain with a final handler
|
|
15
|
+
* Optimized: skip recursion when no middleware
|
|
15
16
|
*/
|
|
16
17
|
async execute(
|
|
17
18
|
ctx: Context,
|
|
@@ -19,50 +20,57 @@ export class MiddlewareExecutor {
|
|
|
19
20
|
handler: Handler,
|
|
20
21
|
deps: any = {}
|
|
21
22
|
): Promise<Response> {
|
|
23
|
+
const len = middlewares.length;
|
|
24
|
+
|
|
25
|
+
// Fast path: no middleware - call handler directly
|
|
26
|
+
if (len === 0) {
|
|
27
|
+
return this.executeHandler(ctx, handler, deps);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Single middleware - no recursion needed
|
|
31
|
+
if (len === 1) {
|
|
32
|
+
return middlewares[0](ctx, async (c) => this.executeHandler(c, handler, deps), deps);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Multiple middleware - use optimized loop
|
|
22
36
|
let index = 0;
|
|
23
37
|
|
|
24
38
|
const next: Next = async (currentCtx: Context): Promise<Response> => {
|
|
25
|
-
if (index <
|
|
39
|
+
if (index < len) {
|
|
26
40
|
const middleware = middlewares[index++];
|
|
27
|
-
|
|
28
|
-
try {
|
|
29
|
-
// Execute middleware with next function and dependencies
|
|
30
|
-
return await middleware(currentCtx, next, deps);
|
|
31
|
-
} catch (error) {
|
|
32
|
-
// Propagate error to be caught by error handler
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
// All middleware executed, call final handler
|
|
37
|
-
try {
|
|
38
|
-
const result = await handler(currentCtx, deps);
|
|
39
|
-
|
|
40
|
-
// If handler returns an Error, throw it to be caught by error handler
|
|
41
|
-
if (result instanceof Error) {
|
|
42
|
-
// Mark as intentional (returned, not thrown)
|
|
43
|
-
(result as any)._isIntentional = true;
|
|
44
|
-
throw result;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// If handler returns a Response, use it
|
|
48
|
-
if (this.isResponse(result)) {
|
|
49
|
-
return result;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Otherwise, wrap in JSON response
|
|
53
|
-
return currentCtx.json(result);
|
|
54
|
-
} catch (error) {
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
41
|
+
return middleware(currentCtx, next, deps);
|
|
57
42
|
}
|
|
43
|
+
return this.executeHandler(currentCtx, handler, deps);
|
|
58
44
|
};
|
|
59
45
|
|
|
60
46
|
return next(ctx);
|
|
61
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute handler and normalize response - inlined for performance
|
|
51
|
+
*/
|
|
52
|
+
private async executeHandler(ctx: Context, handler: Handler, deps: any): Promise<Response> {
|
|
53
|
+
const result = await handler(ctx, deps);
|
|
54
|
+
|
|
55
|
+
// If handler returns an Error, throw it
|
|
56
|
+
if (result instanceof Error) {
|
|
57
|
+
(result as any)._isIntentional = true;
|
|
58
|
+
throw result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Fast path: already a Response
|
|
62
|
+
if (result && typeof result === 'object' && 'statusCode' in result && 'body' in result) {
|
|
63
|
+
return result as Response;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Wrap in JSON response
|
|
67
|
+
return ctx.json(result);
|
|
68
|
+
}
|
|
62
69
|
|
|
63
70
|
/**
|
|
64
71
|
* Execute middleware chain with lifecycle hooks
|
|
65
72
|
* This method integrates hooks at the appropriate points in the request lifecycle
|
|
73
|
+
* Optimized to check hooks existence only once
|
|
66
74
|
*/
|
|
67
75
|
async executeWithHooks(
|
|
68
76
|
ctx: Context,
|
|
@@ -71,77 +79,79 @@ export class MiddlewareExecutor {
|
|
|
71
79
|
hooks: LifecycleHooks,
|
|
72
80
|
deps: any = {}
|
|
73
81
|
): Promise<Response> {
|
|
82
|
+
// Check if we have any hooks at all (optimization)
|
|
83
|
+
const hasHooks = hooks.beforeValidation || hooks.afterValidation ||
|
|
84
|
+
hooks.beforeHandler || hooks.afterHandler;
|
|
85
|
+
|
|
86
|
+
// Fast path: no hooks and no middleware
|
|
87
|
+
if (!hasHooks && middlewares.length === 0) {
|
|
88
|
+
return this.executeHandler(ctx, handler, deps);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Fast path: no hooks but has middleware
|
|
92
|
+
if (!hasHooks) {
|
|
93
|
+
return this.execute(ctx, middlewares, handler, deps);
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
let index = 0;
|
|
97
|
+
const len = middlewares.length;
|
|
75
98
|
|
|
76
99
|
const next: Next = async (currentCtx: Context): Promise<Response> => {
|
|
77
|
-
if (index <
|
|
100
|
+
if (index < len) {
|
|
78
101
|
const middleware = middlewares[index++];
|
|
102
|
+
return middleware(currentCtx, next, deps);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// All middleware executed, now run handler with hooks
|
|
106
|
+
|
|
107
|
+
// === HOOK: beforeValidation ===
|
|
108
|
+
if (hooks.beforeValidation) {
|
|
109
|
+
const hookResult = await hooks.beforeValidation(currentCtx);
|
|
110
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
111
|
+
return hookResult as Response;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
79
114
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
115
|
+
// === HOOK: afterValidation ===
|
|
116
|
+
if (hooks.afterValidation) {
|
|
117
|
+
const hookResult = await hooks.afterValidation(currentCtx);
|
|
118
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
119
|
+
return hookResult as Response;
|
|
85
120
|
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return hookResult;
|
|
94
|
-
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// === HOOK: beforeHandler ===
|
|
124
|
+
if (hooks.beforeHandler) {
|
|
125
|
+
const hookResult = await hooks.beforeHandler(currentCtx);
|
|
126
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
127
|
+
return hookResult as Response;
|
|
95
128
|
}
|
|
129
|
+
}
|
|
96
130
|
|
|
97
|
-
|
|
98
|
-
|
|
131
|
+
// Execute the handler with dependencies
|
|
132
|
+
let result = await handler(currentCtx, deps);
|
|
99
133
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
}
|
|
134
|
+
// If handler returns an Error, throw it
|
|
135
|
+
if (result instanceof Error) {
|
|
136
|
+
(result as any)._isIntentional = true;
|
|
137
|
+
throw result;
|
|
138
|
+
}
|
|
107
139
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
140
|
+
// === HOOK: afterHandler ===
|
|
141
|
+
if (hooks.afterHandler) {
|
|
142
|
+
const transformedResult = await hooks.afterHandler(currentCtx, result);
|
|
143
|
+
if (transformedResult !== undefined) {
|
|
144
|
+
result = transformedResult;
|
|
114
145
|
}
|
|
146
|
+
}
|
|
115
147
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// If handler returns an Error, throw it
|
|
121
|
-
if (result instanceof Error) {
|
|
122
|
-
(result as any)._isIntentional = true;
|
|
123
|
-
throw result;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// === HOOK: afterHandler ===
|
|
127
|
-
if (hooks.afterHandler) {
|
|
128
|
-
const transformedResult = await hooks.afterHandler(currentCtx, result);
|
|
129
|
-
if (transformedResult !== undefined) {
|
|
130
|
-
result = transformedResult;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// If result is a Response, return it
|
|
135
|
-
if (this.isResponse(result)) {
|
|
136
|
-
return result;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Otherwise, wrap in JSON response
|
|
140
|
-
return currentCtx.json(result);
|
|
141
|
-
} catch (error) {
|
|
142
|
-
throw error;
|
|
143
|
-
}
|
|
148
|
+
// If result is a Response, return it
|
|
149
|
+
if (result && typeof result === 'object' && 'statusCode' in result && 'body' in result) {
|
|
150
|
+
return result as Response;
|
|
144
151
|
}
|
|
152
|
+
|
|
153
|
+
// Otherwise, wrap in JSON response
|
|
154
|
+
return currentCtx.json(result);
|
|
145
155
|
};
|
|
146
156
|
|
|
147
157
|
return next(ctx);
|
|
@@ -4,20 +4,26 @@
|
|
|
4
4
|
* Automatically scans a directory and registers routes based on file/folder structure.
|
|
5
5
|
* Designed for class-based routing: 1 file = 1 class = 1 route.
|
|
6
6
|
*
|
|
7
|
-
* Smart filename conventions:
|
|
7
|
+
* Smart filename conventions (only if no body schema):
|
|
8
8
|
* - `index.ts` → GET (default), maps to parent path
|
|
9
9
|
* - `create.ts` → POST, maps to parent path (not /create)
|
|
10
10
|
* - `update.ts` → PUT, maps to parent path (not /update)
|
|
11
11
|
* - `delete.ts` → DELETE, maps to parent path (not /delete)
|
|
12
12
|
* - `patch.ts` → PATCH, maps to parent path (not /patch)
|
|
13
|
-
* - Other files → GET (
|
|
13
|
+
* - Other files → GET (default)
|
|
14
|
+
*
|
|
15
|
+
* Smart body detection (HIGHEST PRIORITY after explicit method):
|
|
16
|
+
* - If schema() returns object with `body` property → POST
|
|
17
|
+
* - Works with inheritance (parent class schema is checked too)
|
|
18
|
+
* - Overrides smart filename detection!
|
|
19
|
+
* - Override by explicitly setting `method` property in class
|
|
14
20
|
*
|
|
15
21
|
* @example
|
|
16
22
|
* ```
|
|
17
23
|
* routes/
|
|
18
24
|
* api/
|
|
19
25
|
* auth/
|
|
20
|
-
* register.ts → POST /api/auth/register (
|
|
26
|
+
* register.ts → POST /api/auth/register (auto-detected via ctx.body!)
|
|
21
27
|
* login.ts → POST /api/auth/login
|
|
22
28
|
* users/
|
|
23
29
|
* index.ts → GET /api/users
|
|
@@ -31,11 +37,18 @@
|
|
|
31
37
|
* Each file exports a class:
|
|
32
38
|
* ```typescript
|
|
33
39
|
* export default class RegisterRoute {
|
|
34
|
-
* method = 'POST'
|
|
40
|
+
* // No need to specify method = 'POST' if schema has body!
|
|
41
|
+
*
|
|
42
|
+
* schema() {
|
|
43
|
+
* return {
|
|
44
|
+
* body: z.object({ ... }) // ← auto-detects POST!
|
|
45
|
+
* };
|
|
46
|
+
* }
|
|
35
47
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
48
|
+
* async handler(ctx: Context) {
|
|
49
|
+
* const data = ctx.body;
|
|
50
|
+
* ...
|
|
51
|
+
* }
|
|
39
52
|
* }
|
|
40
53
|
* ```
|
|
41
54
|
*/
|
|
@@ -429,16 +442,32 @@ export class FileRouter {
|
|
|
429
442
|
|
|
430
443
|
// Determine HTTP method:
|
|
431
444
|
// 1. If class explicitly defines method → use it
|
|
432
|
-
// 2. If
|
|
433
|
-
// 3.
|
|
445
|
+
// 2. If schema has body definition → auto-detect as POST (highest priority for body!)
|
|
446
|
+
// 3. If filename is smart (create/update/delete/patch/index) → auto-detect from filename
|
|
447
|
+
// 4. Otherwise → default GET
|
|
434
448
|
let method: HTTPMethod;
|
|
449
|
+
|
|
435
450
|
if (route.method) {
|
|
436
451
|
// Handle both single method and array of methods (use first one for file-based routing)
|
|
437
452
|
method = Array.isArray(route.method) ? route.method[0] : route.method;
|
|
438
|
-
} else if (isSmartFilename(nameWithoutExt)) {
|
|
439
|
-
method = getSmartMethod(nameWithoutExt);
|
|
440
453
|
} else {
|
|
441
|
-
|
|
454
|
+
// Check schema for body definition first (works with inheritance!)
|
|
455
|
+
const schema = route.schema?.();
|
|
456
|
+
if (this.options.debug) {
|
|
457
|
+
console.log(`🔍 Smart detection for ${filePath}:`);
|
|
458
|
+
console.log(` - schema:`, schema);
|
|
459
|
+
console.log(` - has body:`, !!schema?.body);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (schema?.body) {
|
|
463
|
+
// Schema has body → POST (overrides smart filename!)
|
|
464
|
+
method = 'POST';
|
|
465
|
+
} else if (isSmartFilename(nameWithoutExt)) {
|
|
466
|
+
// Smart filename detection (create→POST, update→PUT, etc.)
|
|
467
|
+
method = getSmartMethod(nameWithoutExt);
|
|
468
|
+
} else {
|
|
469
|
+
method = DEFAULT_METHOD;
|
|
470
|
+
}
|
|
442
471
|
}
|
|
443
472
|
|
|
444
473
|
// Validate method
|