@engjts/nexus 0.1.6 → 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.
Files changed (78) hide show
  1. package/BENCHMARK_REPORT.md +343 -0
  2. package/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
  3. package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
  4. package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
  5. package/dist/advanced/playground/playground.d.ts +19 -0
  6. package/dist/advanced/playground/playground.d.ts.map +1 -1
  7. package/dist/advanced/playground/playground.js +70 -0
  8. package/dist/advanced/playground/playground.js.map +1 -1
  9. package/dist/advanced/playground/types.d.ts +20 -0
  10. package/dist/advanced/playground/types.d.ts.map +1 -1
  11. package/dist/cli/templates/generators.d.ts.map +1 -1
  12. package/dist/cli/templates/generators.js +16 -13
  13. package/dist/cli/templates/generators.js.map +1 -1
  14. package/dist/cli/templates/index.js +25 -25
  15. package/dist/core/application.d.ts +14 -0
  16. package/dist/core/application.d.ts.map +1 -1
  17. package/dist/core/application.js +173 -71
  18. package/dist/core/application.js.map +1 -1
  19. package/dist/core/context-pool.d.ts +2 -13
  20. package/dist/core/context-pool.d.ts.map +1 -1
  21. package/dist/core/context-pool.js +7 -45
  22. package/dist/core/context-pool.js.map +1 -1
  23. package/dist/core/context.d.ts +108 -5
  24. package/dist/core/context.d.ts.map +1 -1
  25. package/dist/core/context.js +449 -53
  26. package/dist/core/context.js.map +1 -1
  27. package/dist/core/index.d.ts +1 -0
  28. package/dist/core/index.d.ts.map +1 -1
  29. package/dist/core/index.js +9 -1
  30. package/dist/core/index.js.map +1 -1
  31. package/dist/core/middleware.d.ts +6 -0
  32. package/dist/core/middleware.d.ts.map +1 -1
  33. package/dist/core/middleware.js +83 -84
  34. package/dist/core/middleware.js.map +1 -1
  35. package/dist/core/performance/fast-json.d.ts +149 -0
  36. package/dist/core/performance/fast-json.d.ts.map +1 -0
  37. package/dist/core/performance/fast-json.js +473 -0
  38. package/dist/core/performance/fast-json.js.map +1 -0
  39. package/dist/core/router/file-router.d.ts +20 -7
  40. package/dist/core/router/file-router.d.ts.map +1 -1
  41. package/dist/core/router/file-router.js +41 -13
  42. package/dist/core/router/file-router.js.map +1 -1
  43. package/dist/core/router/index.d.ts +6 -0
  44. package/dist/core/router/index.d.ts.map +1 -1
  45. package/dist/core/router/index.js +33 -6
  46. package/dist/core/router/index.js.map +1 -1
  47. package/dist/core/router/radix-tree.d.ts +4 -1
  48. package/dist/core/router/radix-tree.d.ts.map +1 -1
  49. package/dist/core/router/radix-tree.js +7 -3
  50. package/dist/core/router/radix-tree.js.map +1 -1
  51. package/dist/core/serializer.d.ts +251 -0
  52. package/dist/core/serializer.d.ts.map +1 -0
  53. package/dist/core/serializer.js +290 -0
  54. package/dist/core/serializer.js.map +1 -0
  55. package/dist/core/types.d.ts +39 -1
  56. package/dist/core/types.d.ts.map +1 -1
  57. package/dist/core/types.js.map +1 -1
  58. package/dist/index.d.ts +1 -0
  59. package/dist/index.d.ts.map +1 -1
  60. package/dist/index.js +12 -2
  61. package/dist/index.js.map +1 -1
  62. package/package.json +3 -1
  63. package/src/advanced/playground/generatePlaygroundHTML.ts +107 -0
  64. package/src/advanced/playground/playground.ts +225 -145
  65. package/src/advanced/playground/types.ts +29 -0
  66. package/src/cli/templates/generators.ts +16 -13
  67. package/src/cli/templates/index.ts +25 -25
  68. package/src/core/application.ts +202 -84
  69. package/src/core/context-pool.ts +8 -56
  70. package/src/core/context.ts +497 -53
  71. package/src/core/index.ts +14 -0
  72. package/src/core/middleware.ts +99 -89
  73. package/src/core/router/file-router.ts +41 -12
  74. package/src/core/router/index.ts +213 -180
  75. package/src/core/router/radix-tree.ts +20 -4
  76. package/src/core/serializer.ts +397 -0
  77. package/src/core/types.ts +43 -1
  78. 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';
@@ -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 < middlewares.length) {
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 < middlewares.length) {
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
- try {
81
- // Execute middleware with next function and dependencies
82
- return await middleware(currentCtx, next, deps);
83
- } catch (error) {
84
- throw error;
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
- } else {
87
- // All middleware executed, now run handler with hooks
88
-
89
- // === HOOK: beforeValidation ===
90
- if (hooks.beforeValidation) {
91
- const hookResult = await hooks.beforeValidation(currentCtx);
92
- if (this.isResponse(hookResult)) {
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
- // TODO: Schema validation happens here (if route has schema)
98
- // For now, validation is handled in middleware or by the handler itself
131
+ // Execute the handler with dependencies
132
+ let result = await handler(currentCtx, deps);
99
133
 
100
- // === HOOK: afterValidation ===
101
- if (hooks.afterValidation) {
102
- const hookResult = await hooks.afterValidation(currentCtx);
103
- if (this.isResponse(hookResult)) {
104
- return hookResult;
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
- // === HOOK: beforeHandler ===
109
- if (hooks.beforeHandler) {
110
- const hookResult = await hooks.beforeHandler(currentCtx);
111
- if (this.isResponse(hookResult)) {
112
- return hookResult;
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
- // Execute the handler with dependencies
117
- try {
118
- let result = await handler(currentCtx, deps);
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 (or specify `method` in class)
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 (method = 'POST' in class)
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' // Optional if filename is create/update/delete
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
- * schema() { ... }
37
- * meta() { ... }
38
- * async handler(ctx: Context) { ... }
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 filename is smart (create/update/delete/patch/index) → auto-detect
433
- // 3. Otherwisedefault GET
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
- method = DEFAULT_METHOD;
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