@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.
Files changed (72) 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/core/application.d.ts +14 -0
  12. package/dist/core/application.d.ts.map +1 -1
  13. package/dist/core/application.js +173 -71
  14. package/dist/core/application.js.map +1 -1
  15. package/dist/core/context-pool.d.ts +2 -13
  16. package/dist/core/context-pool.d.ts.map +1 -1
  17. package/dist/core/context-pool.js +7 -45
  18. package/dist/core/context-pool.js.map +1 -1
  19. package/dist/core/context.d.ts +108 -5
  20. package/dist/core/context.d.ts.map +1 -1
  21. package/dist/core/context.js +449 -53
  22. package/dist/core/context.js.map +1 -1
  23. package/dist/core/index.d.ts +1 -0
  24. package/dist/core/index.d.ts.map +1 -1
  25. package/dist/core/index.js +9 -1
  26. package/dist/core/index.js.map +1 -1
  27. package/dist/core/middleware.d.ts +6 -0
  28. package/dist/core/middleware.d.ts.map +1 -1
  29. package/dist/core/middleware.js +83 -84
  30. package/dist/core/middleware.js.map +1 -1
  31. package/dist/core/performance/fast-json.d.ts +149 -0
  32. package/dist/core/performance/fast-json.d.ts.map +1 -0
  33. package/dist/core/performance/fast-json.js +473 -0
  34. package/dist/core/performance/fast-json.js.map +1 -0
  35. package/dist/core/router/file-router.d.ts +20 -7
  36. package/dist/core/router/file-router.d.ts.map +1 -1
  37. package/dist/core/router/file-router.js +41 -13
  38. package/dist/core/router/file-router.js.map +1 -1
  39. package/dist/core/router/index.d.ts +6 -0
  40. package/dist/core/router/index.d.ts.map +1 -1
  41. package/dist/core/router/index.js +33 -6
  42. package/dist/core/router/index.js.map +1 -1
  43. package/dist/core/router/radix-tree.d.ts +4 -1
  44. package/dist/core/router/radix-tree.d.ts.map +1 -1
  45. package/dist/core/router/radix-tree.js +7 -3
  46. package/dist/core/router/radix-tree.js.map +1 -1
  47. package/dist/core/serializer.d.ts +251 -0
  48. package/dist/core/serializer.d.ts.map +1 -0
  49. package/dist/core/serializer.js +290 -0
  50. package/dist/core/serializer.js.map +1 -0
  51. package/dist/core/types.d.ts +39 -1
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/core/types.js.map +1 -1
  54. package/dist/index.d.ts +1 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +12 -2
  57. package/dist/index.js.map +1 -1
  58. package/package.json +3 -1
  59. package/src/advanced/playground/generatePlaygroundHTML.ts +107 -0
  60. package/src/advanced/playground/playground.ts +225 -145
  61. package/src/advanced/playground/types.ts +29 -0
  62. package/src/core/application.ts +202 -84
  63. package/src/core/context-pool.ts +8 -56
  64. package/src/core/context.ts +497 -53
  65. package/src/core/index.ts +14 -0
  66. package/src/core/middleware.ts +99 -89
  67. package/src/core/router/file-router.ts +41 -12
  68. package/src/core/router/index.ts +213 -180
  69. package/src/core/router/radix-tree.ts +20 -4
  70. package/src/core/serializer.ts +397 -0
  71. package/src/core/types.ts +43 -1
  72. package/src/index.ts +17 -0
@@ -4,222 +4,255 @@
4
4
 
5
5
  import { RadixTree } from './radix-tree';
6
6
  import {
7
- HTTPMethod,
8
- Handler,
9
- Middleware,
10
- RouteConfig,
11
- RouteMatch,
12
- SchemaConfig,
13
- RouteMeta,
14
- Context
7
+ HTTPMethod,
8
+ Handler,
9
+ Middleware,
10
+ RouteConfig,
11
+ RouteMatch,
12
+ SchemaConfig,
13
+ RouteMeta,
14
+ Context
15
15
  } from '../types';
16
+ import { SerializerFunction, createSerializer, JSONSchema } from '../serializer';
16
17
 
17
18
  /**
18
19
  * Route with validation schema and metadata
19
20
  */
20
21
  export interface RouteEntry {
21
- handler: Handler;
22
- middlewares: Middleware[];
23
- schema?: SchemaConfig;
24
- meta?: RouteMeta;
22
+ handler: Handler;
23
+ middlewares: Middleware[];
24
+ schema?: SchemaConfig;
25
+ meta?: RouteMeta;
26
+ serializers?: Map<number | string, SerializerFunction>;
25
27
  }
26
28
 
27
29
  /**
28
30
  * Router class
29
31
  */
30
32
  export class Router {
31
- private trees: Map<HTTPMethod, RadixTree> = new Map();
32
- private routes: Array<{ method: HTTPMethod; path: string; config: RouteEntry }> = [];
33
- private prefix: string = '';
34
-
35
- constructor(prefix: string = '') {
36
- this.prefix = prefix;
37
- // Initialize trees for each HTTP method
38
- const methods: HTTPMethod[] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
39
- for (const method of methods) {
40
- this.trees.set(method, new RadixTree());
41
- }
42
- }
33
+ private trees: Map<HTTPMethod, RadixTree> = new Map();
34
+ private routes: Array<{ method: HTTPMethod; path: string; config: RouteEntry }> = [];
35
+ private prefix: string = '';
43
36
 
44
- /**
45
- * Register a route
46
- */
47
- addRoute(config: RouteConfig): void {
48
- const { method, path, handler, middlewares = [], schema, meta } = config;
49
- const fullPath = this.prefix ? `${this.prefix}${path}` : path;
37
+ constructor(prefix: string = '') {
38
+ this.prefix = prefix;
39
+ // Initialize trees for each HTTP method
40
+ const methods: HTTPMethod[] = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
41
+ for (const method of methods) {
42
+ this.trees.set(method, new RadixTree());
43
+ }
44
+ }
50
45
 
51
- const tree = this.trees.get(method);
52
- if (!tree) {
53
- throw new Error(`Unsupported HTTP method: ${method}`);
54
- }
46
+ /**
47
+ * Register a route
48
+ */
49
+ addRoute(config: RouteConfig): void {
50
+ const { method, path, handler, middlewares = [], schema, meta } = config;
51
+ const fullPath = this.prefix ? `${this.prefix}${path}` : path;
55
52
 
56
- // Wrap handler with schema validation if provided
57
- const wrappedHandler = schema ? this.wrapWithValidation(handler, schema) : handler;
53
+ const tree = this.trees.get(method);
54
+ if (!tree) {
55
+ throw new Error(`Unsupported HTTP method: ${method}`);
56
+ }
58
57
 
59
- tree.insert(fullPath, wrappedHandler, middlewares);
58
+ // Wrap handler with schema validation if provided
59
+ const wrappedHandler = schema ? this.wrapWithValidation(handler, schema) : handler;
60
60
 
61
- // Store for introspection (including schema and meta for documentation)
62
- this.routes.push({
63
- method,
64
- path: fullPath,
65
- config: { handler: wrappedHandler, middlewares, schema, meta }
66
- });
61
+ // Compile response serializers if response schema is provided
62
+ let serializers: Map<number | string, SerializerFunction> | undefined;
63
+ if (schema?.response) {
64
+ serializers = this.compileResponseSerializers(schema.response);
67
65
  }
68
66
 
69
- /**
70
- * Find a matching route
71
- */
72
- match(method: string, path: string): RouteMatch | null {
73
- const tree = this.trees.get(method as HTTPMethod);
74
- if (!tree) {
75
- return null;
76
- }
67
+ tree.insert(fullPath, wrappedHandler, middlewares, serializers);
77
68
 
78
- const result = tree.search(path);
79
- if (!result) {
80
- return null;
81
- }
69
+ // Store for introspection (including schema, meta, and serializers for documentation)
70
+ this.routes.push({
71
+ method,
72
+ path: fullPath,
73
+ config: { handler: wrappedHandler, middlewares, schema, meta, serializers }
74
+ });
75
+ }
82
76
 
83
- return {
84
- handler: result.handler,
85
- params: result.params,
86
- middlewares: result.middlewares,
87
- schema: undefined // Schema already applied in wrapped handler
88
- };
89
- }
77
+ /**
78
+ * Compile response schemas into fast serializers
79
+ */
80
+ private compileResponseSerializers(
81
+ responseSchema: Record<string | number, any>
82
+ ): Map<number | string, SerializerFunction> {
83
+ const serializers = new Map<number | string, SerializerFunction>();
90
84
 
91
- /**
92
- * Get all registered routes with full metadata
93
- */
94
- getRoutes(): Array<{ method: string; path: string; schema?: SchemaConfig; meta?: RouteMeta }> {
95
- return this.routes.map(r => ({
96
- method: r.method,
97
- path: r.path,
98
- schema: r.config.schema,
99
- meta: r.config.meta
100
- }));
85
+ for (const [statusKey, schema] of Object.entries(responseSchema)) {
86
+ if (schema && typeof schema === 'object') {
87
+ try {
88
+ const serializer = createSerializer(schema as JSONSchema);
89
+ serializers.set(statusKey, serializer);
90
+ } catch (e) {
91
+ // If compilation fails, skip this serializer (will fall back to JSON.stringify)
92
+ console.warn(`[Router] Failed to compile serializer for status ${statusKey}:`, e);
93
+ }
94
+ }
101
95
  }
102
96
 
103
- /**
104
- * Get raw route configs for merging into Application
105
- */
106
- getRawRoutes(): Array<{ method: HTTPMethod; path: string; config: RouteEntry }> {
107
- return this.routes;
108
- }
97
+ return serializers;
98
+ }
109
99
 
110
- /**
111
- * Get internal radix trees for merging
112
- */
113
- getTrees(): Map<HTTPMethod, RadixTree> {
114
- return this.trees;
100
+ /**
101
+ * Find a matching route
102
+ */
103
+ match(method: string, path: string): RouteMatch | null {
104
+ const tree = this.trees.get(method as HTTPMethod);
105
+ if (!tree) {
106
+ return null;
115
107
  }
116
108
 
117
- /**
118
- * Mount another router with a prefix (group routes)
119
- *
120
- * @example
121
- * ```typescript
122
- * const userRoutes = new Router();
123
- * userRoutes.get('/', getAllUsers);
124
- * userRoutes.get('/:id', getUserById);
125
- *
126
- * const router = new Router();
127
- * router.group('/api/users', userRoutes);
128
- * ```
129
- */
130
- group(prefix: string, router: Router): void {
131
- const routes = router.getRawRoutes();
132
- for (const route of routes) {
133
- const fullPath = `${prefix}${route.path}`;
134
- this.addRoute({
135
- method: route.method,
136
- path: fullPath,
137
- handler: route.config.handler,
138
- middlewares: route.config.middlewares,
139
- schema: route.config.schema,
140
- meta: route.config.meta
141
- });
142
- }
109
+ const result = tree.search(path);
110
+ if (!result) {
111
+ return null;
143
112
  }
144
113
 
145
- /**
146
- * Convenience methods for HTTP verbs
147
- */
148
- get(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
149
- this.addRoute({ method: 'GET', path, handler, ...options });
150
- }
114
+ return {
115
+ handler: result.handler,
116
+ params: result.params,
117
+ middlewares: result.middlewares,
118
+ schema: undefined, // Schema already applied in wrapped handler
119
+ _serializer: result.serializers || undefined
120
+ };
121
+ }
151
122
 
152
- post(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
153
- this.addRoute({ method: 'POST', path, handler, ...options });
154
- }
123
+ /**
124
+ * Get all registered routes with full metadata
125
+ */
126
+ getRoutes(): Array<{ method: string; path: string; schema?: SchemaConfig; meta?: RouteMeta }> {
127
+ return this.routes.map(r => ({
128
+ method: r.method,
129
+ path: r.path,
130
+ schema: r.config.schema,
131
+ meta: r.config.meta
132
+ }));
133
+ }
155
134
 
156
- put(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
157
- this.addRoute({ method: 'PUT', path, handler, ...options });
158
- }
135
+ /**
136
+ * Get raw route configs for merging into Application
137
+ */
138
+ getRawRoutes(): Array<{ method: HTTPMethod; path: string; config: RouteEntry }> {
139
+ return this.routes;
140
+ }
159
141
 
160
- delete(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
161
- this.addRoute({ method: 'DELETE', path, handler, ...options });
162
- }
142
+ /**
143
+ * Get internal radix trees for merging
144
+ */
145
+ getTrees(): Map<HTTPMethod, RadixTree> {
146
+ return this.trees;
147
+ }
163
148
 
164
- patch(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
165
- this.addRoute({ method: 'PATCH', path, handler, ...options });
149
+ /**
150
+ * Mount another router with a prefix (group routes)
151
+ *
152
+ * @example
153
+ * ```typescript
154
+ * const userRoutes = new Router();
155
+ * userRoutes.get('/', getAllUsers);
156
+ * userRoutes.get('/:id', getUserById);
157
+ *
158
+ * const router = new Router();
159
+ * router.group('/api/users', userRoutes);
160
+ * ```
161
+ */
162
+ group(prefix: string, router: Router): void {
163
+ const routes = router.getRawRoutes();
164
+ for (const route of routes) {
165
+ const fullPath = `${prefix}${route.path}`;
166
+ this.addRoute({
167
+ method: route.method,
168
+ path: fullPath,
169
+ handler: route.config.handler,
170
+ middlewares: route.config.middlewares,
171
+ schema: route.config.schema,
172
+ meta: route.config.meta
173
+ });
166
174
  }
175
+ }
176
+
177
+ /**
178
+ * Convenience methods for HTTP verbs
179
+ */
180
+ get(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
181
+ this.addRoute({ method: 'GET', path, handler, ...options });
182
+ }
167
183
 
168
- /**
169
- * Wrap handler with schema validation
170
- */
171
- private wrapWithValidation(handler: Handler, schema: SchemaConfig): Handler {
172
- return async (ctx: Context) => {
173
- try {
174
- // Validate params
175
- if (schema.params) {
176
- ctx.params = await schema.params.parseAsync(ctx.params);
177
- }
178
-
179
- // Validate query
180
- if (schema.query) {
181
- ctx.query = await schema.query.parseAsync(ctx.query);
182
- }
183
-
184
- // Validate body
185
- if (schema.body) {
186
- ctx.body = await schema.body.parseAsync(ctx.body);
187
- }
188
-
189
- // Validate headers
190
- if (schema.headers) {
191
- ctx.headers = await schema.headers.parseAsync(ctx.headers);
192
- }
193
-
194
- // Call original handler with validated data
195
- return handler(ctx, {});
196
- } catch (error: any) {
197
- // Zod validation error
198
- if (error.name === 'ZodError') {
199
- // Use custom error handler if provided
200
- if (schema.onValidationError) {
201
- const customResponse = schema.onValidationError(error.errors, ctx);
202
- // If it's already a Response object, return it
203
- if (customResponse?.statusCode) {
204
- return customResponse;
205
- }
206
- // Otherwise wrap it as JSON response
207
- return ctx.json(customResponse, 400);
208
- }
209
-
210
- // Default error response - extract first error message
211
- const firstError = error.errors[0];
212
- const message = firstError?.message || 'Validation failed';
213
-
214
- return ctx.json({
215
- success: false,
216
- message
217
- }, 400);
218
- }
219
- throw error;
184
+ post(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
185
+ this.addRoute({ method: 'POST', path, handler, ...options });
186
+ }
187
+
188
+ put(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
189
+ this.addRoute({ method: 'PUT', path, handler, ...options });
190
+ }
191
+
192
+ delete(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
193
+ this.addRoute({ method: 'DELETE', path, handler, ...options });
194
+ }
195
+
196
+ patch(path: string, handler: Handler, options?: Partial<RouteConfig>): void {
197
+ this.addRoute({ method: 'PATCH', path, handler, ...options });
198
+ }
199
+
200
+ /**
201
+ * Wrap handler with schema validation
202
+ */
203
+ private wrapWithValidation(handler: Handler, schema: SchemaConfig): Handler {
204
+ return async (ctx: Context) => {
205
+ try {
206
+ // Validate params
207
+ if (schema.params) {
208
+ ctx.params = await schema.params.parseAsync(ctx.params);
209
+ }
210
+
211
+ // Validate query
212
+ if (schema.query) {
213
+ ctx.query = await schema.query.parseAsync(ctx.query);
214
+ }
215
+
216
+ // Validate body - MUST await getBody() first to parse the request body
217
+ if (schema.body) {
218
+ const rawBody = await ctx.getBody();
219
+ ctx.body = await schema.body.parseAsync(rawBody);
220
+ }
221
+
222
+ // Validate headers
223
+ if (schema.headers) {
224
+ ctx.headers = await schema.headers.parseAsync(ctx.headers);
225
+ }
226
+
227
+ // Call original handler with validated data
228
+ return handler(ctx, {});
229
+ } catch (error: any) {
230
+ // Zod validation error
231
+ if (error.name === 'ZodError') {
232
+ // Use custom error handler if provided
233
+ if (schema.onValidationError) {
234
+ const customResponse = schema.onValidationError(error.errors, ctx);
235
+ // If it's already a Response object, return it
236
+ if (customResponse?.statusCode) {
237
+ return customResponse;
220
238
  }
221
- };
222
- }
239
+ // Otherwise wrap it as JSON response
240
+ return ctx.json(customResponse, 400);
241
+ }
242
+
243
+ // Default error response - extract first error message
244
+ const firstError = error.errors[0];
245
+ const message = firstError?.message || 'Validation failed';
246
+
247
+ return ctx.json({
248
+ success: false,
249
+ message
250
+ }, 400);
251
+ }
252
+ throw error;
253
+ }
254
+ };
255
+ }
223
256
  }
224
257
 
225
258
  // Re-export file router
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Handler, Middleware } from '../types';
7
+ import { SerializerFunction } from '../serializer';
7
8
 
8
9
  /**
9
10
  * Node in the radix tree
@@ -30,6 +31,9 @@ export class RadixNode {
30
31
  // Parameter name for param nodes
31
32
  paramName?: string;
32
33
 
34
+ // Compiled serializers for response (status code → serializer)
35
+ serializers: Map<number | string, SerializerFunction> | null = null;
36
+
33
37
  constructor(segment: string, fullPath: string) {
34
38
  this.segment = segment;
35
39
  this.fullPath = fullPath;
@@ -103,7 +107,12 @@ export class RadixTree {
103
107
  /**
104
108
  * Insert a route into the tree
105
109
  */
106
- insert(path: string, handler: Handler, middlewares: Middleware[] = []): void {
110
+ insert(
111
+ path: string,
112
+ handler: Handler,
113
+ middlewares: Middleware[] = [],
114
+ serializers?: Map<number | string, SerializerFunction>
115
+ ): void {
107
116
  const segments = this.splitPath(path);
108
117
  let current = this.root;
109
118
 
@@ -120,15 +129,21 @@ export class RadixTree {
120
129
  current = child;
121
130
  }
122
131
 
123
- // Set handler and middleware at terminal node
132
+ // Set handler, middleware, and serializers at terminal node
124
133
  current.handler = handler;
125
134
  current.middlewares = middlewares;
135
+ current.serializers = serializers || null;
126
136
  }
127
137
 
128
138
  /**
129
139
  * Search for a route match
130
140
  */
131
- search(path: string): { handler: Handler; params: Record<string, string>; middlewares: Middleware[] } | null {
141
+ search(path: string): {
142
+ handler: Handler;
143
+ params: Record<string, string>;
144
+ middlewares: Middleware[];
145
+ serializers: Map<number | string, SerializerFunction> | null;
146
+ } | null {
132
147
  const segments = this.splitPath(path);
133
148
  const params: Record<string, string> = {};
134
149
 
@@ -138,7 +153,8 @@ export class RadixTree {
138
153
  return {
139
154
  handler: result.handler!,
140
155
  params,
141
- middlewares: result.middlewares
156
+ middlewares: result.middlewares,
157
+ serializers: result.serializers
142
158
  };
143
159
  }
144
160