@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/dist/core/context.js
CHANGED
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.ContextImpl = void 0;
|
|
8
8
|
exports.parseBody = parseBody;
|
|
9
|
-
const
|
|
10
|
-
const querystring_1 = require("querystring");
|
|
9
|
+
const fast_querystring_1 = require("fast-querystring");
|
|
11
10
|
const store_1 = require("./store");
|
|
12
11
|
/**
|
|
13
12
|
* Cookie manager implementation
|
|
@@ -65,57 +64,94 @@ class CookieManager {
|
|
|
65
64
|
});
|
|
66
65
|
}
|
|
67
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Pre-cached headers for common content types
|
|
69
|
+
* This avoids object creation on every response
|
|
70
|
+
*/
|
|
71
|
+
const CACHED_HEADERS = {
|
|
72
|
+
JSON: { 'Content-Type': 'application/json' },
|
|
73
|
+
HTML: { 'Content-Type': 'text/html; charset=utf-8' },
|
|
74
|
+
TEXT: { 'Content-Type': 'text/plain; charset=utf-8' },
|
|
75
|
+
};
|
|
68
76
|
/**
|
|
69
77
|
* Response builder implementation
|
|
78
|
+
* Supports fast-json-stringify for optimized JSON serialization
|
|
70
79
|
*/
|
|
71
80
|
class ResponseBuilderImpl {
|
|
72
81
|
_status = 200;
|
|
73
82
|
_headers = {};
|
|
83
|
+
_hasCustomHeaders = false;
|
|
84
|
+
_serializers = null;
|
|
74
85
|
status(code) {
|
|
75
86
|
this._status = code;
|
|
76
87
|
return this;
|
|
77
88
|
}
|
|
78
89
|
header(name, value) {
|
|
79
90
|
this._headers[name] = value;
|
|
91
|
+
this._hasCustomHeaders = true;
|
|
80
92
|
return this;
|
|
81
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Set serializers for this response builder (called by router)
|
|
96
|
+
* @internal
|
|
97
|
+
*/
|
|
98
|
+
setSerializers(serializers) {
|
|
99
|
+
this._serializers = serializers;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get the appropriate serializer for current status code
|
|
103
|
+
*/
|
|
104
|
+
getSerializer() {
|
|
105
|
+
if (!this._serializers)
|
|
106
|
+
return null;
|
|
107
|
+
// Try exact match first
|
|
108
|
+
const exactMatch = this._serializers.get(this._status);
|
|
109
|
+
if (exactMatch)
|
|
110
|
+
return exactMatch;
|
|
111
|
+
// Try status code ranges (2xx, 4xx, 5xx)
|
|
112
|
+
const range = `${Math.floor(this._status / 100)}xx`;
|
|
113
|
+
const rangeMatch = this._serializers.get(range);
|
|
114
|
+
if (rangeMatch)
|
|
115
|
+
return rangeMatch;
|
|
116
|
+
// Try default
|
|
117
|
+
return this._serializers.get('default') || null;
|
|
118
|
+
}
|
|
82
119
|
json(data) {
|
|
120
|
+
// Try to use fast serializer first
|
|
121
|
+
const serializer = this.getSerializer();
|
|
122
|
+
const body = serializer ? serializer(data) : JSON.stringify(data);
|
|
83
123
|
return {
|
|
84
124
|
statusCode: this._status,
|
|
85
|
-
headers:
|
|
86
|
-
...this._headers,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
body: JSON.stringify(data)
|
|
125
|
+
headers: this._hasCustomHeaders
|
|
126
|
+
? { ...this._headers, 'Content-Type': 'application/json' }
|
|
127
|
+
: CACHED_HEADERS.JSON,
|
|
128
|
+
body
|
|
90
129
|
};
|
|
91
130
|
}
|
|
92
131
|
html(content) {
|
|
93
132
|
return {
|
|
94
133
|
statusCode: this._status,
|
|
95
|
-
headers:
|
|
96
|
-
...this._headers,
|
|
97
|
-
|
|
98
|
-
},
|
|
134
|
+
headers: this._hasCustomHeaders
|
|
135
|
+
? { ...this._headers, 'Content-Type': 'text/html; charset=utf-8' }
|
|
136
|
+
: CACHED_HEADERS.HTML,
|
|
99
137
|
body: content
|
|
100
138
|
};
|
|
101
139
|
}
|
|
102
140
|
text(content) {
|
|
103
141
|
return {
|
|
104
142
|
statusCode: this._status,
|
|
105
|
-
headers:
|
|
106
|
-
...this._headers,
|
|
107
|
-
|
|
108
|
-
},
|
|
143
|
+
headers: this._hasCustomHeaders
|
|
144
|
+
? { ...this._headers, 'Content-Type': 'text/plain; charset=utf-8' }
|
|
145
|
+
: CACHED_HEADERS.TEXT,
|
|
109
146
|
body: content
|
|
110
147
|
};
|
|
111
148
|
}
|
|
112
149
|
redirect(url, status = 302) {
|
|
113
150
|
return {
|
|
114
151
|
statusCode: status,
|
|
115
|
-
headers:
|
|
116
|
-
...this._headers,
|
|
117
|
-
'Location': url
|
|
118
|
-
},
|
|
152
|
+
headers: this._hasCustomHeaders
|
|
153
|
+
? { ...this._headers, 'Location': url }
|
|
154
|
+
: { 'Location': url },
|
|
119
155
|
body: ''
|
|
120
156
|
};
|
|
121
157
|
}
|
|
@@ -127,6 +163,33 @@ class ResponseBuilderImpl {
|
|
|
127
163
|
stream: readable
|
|
128
164
|
};
|
|
129
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Reset for reuse (object pooling)
|
|
168
|
+
*/
|
|
169
|
+
reset() {
|
|
170
|
+
this._status = 200;
|
|
171
|
+
this._headers = {};
|
|
172
|
+
this._hasCustomHeaders = false;
|
|
173
|
+
this._serializers = null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Reusable response builder pool
|
|
178
|
+
*/
|
|
179
|
+
const responseBuilderPool = [];
|
|
180
|
+
const RESPONSE_BUILDER_POOL_SIZE = 100;
|
|
181
|
+
function acquireResponseBuilder() {
|
|
182
|
+
if (responseBuilderPool.length > 0) {
|
|
183
|
+
const builder = responseBuilderPool.pop();
|
|
184
|
+
builder.reset();
|
|
185
|
+
return builder;
|
|
186
|
+
}
|
|
187
|
+
return new ResponseBuilderImpl();
|
|
188
|
+
}
|
|
189
|
+
function releaseResponseBuilder(builder) {
|
|
190
|
+
if (responseBuilderPool.length < RESPONSE_BUILDER_POOL_SIZE) {
|
|
191
|
+
responseBuilderPool.push(builder);
|
|
192
|
+
}
|
|
130
193
|
}
|
|
131
194
|
/**
|
|
132
195
|
* Context implementation
|
|
@@ -134,39 +197,328 @@ class ResponseBuilderImpl {
|
|
|
134
197
|
class ContextImpl {
|
|
135
198
|
method;
|
|
136
199
|
path;
|
|
137
|
-
|
|
200
|
+
_url = null; // Lazy URL creation
|
|
201
|
+
_host = 'localhost';
|
|
138
202
|
params = {};
|
|
139
|
-
|
|
140
|
-
|
|
203
|
+
_query = null; // Lazy query parsing
|
|
204
|
+
_queryString = ''; // Raw query string for lazy parsing
|
|
141
205
|
headers;
|
|
142
|
-
|
|
206
|
+
_cookieHeader;
|
|
207
|
+
_cookies = null; // Lazy cookie parsing
|
|
143
208
|
raw;
|
|
144
209
|
response;
|
|
210
|
+
// Lazy body parsing - key optimization!
|
|
211
|
+
_parsedBody = undefined;
|
|
212
|
+
_bodyPromise = null;
|
|
213
|
+
_bodyParsed = false;
|
|
145
214
|
// Store registry reference (set by Application)
|
|
146
215
|
_storeRegistry;
|
|
147
|
-
// Request-scoped store registry
|
|
148
|
-
_requestStoreRegistry;
|
|
149
|
-
// Request-scoped simple key-value storage
|
|
150
|
-
_data =
|
|
216
|
+
// Request-scoped store registry - now lazy!
|
|
217
|
+
_requestStoreRegistry = null;
|
|
218
|
+
// Request-scoped simple key-value storage - now lazy!
|
|
219
|
+
_data = null;
|
|
151
220
|
// Debug mode
|
|
152
221
|
_debug = false;
|
|
153
222
|
constructor(req, res) {
|
|
154
223
|
this.raw = { req, res };
|
|
155
|
-
// Parse method
|
|
156
|
-
this.method = (req.method
|
|
157
|
-
//
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
224
|
+
// Parse method - use direct access, avoid optional chaining overhead
|
|
225
|
+
this.method = (req.method ? req.method.toUpperCase() : 'GET');
|
|
226
|
+
// Fast URL parsing - just extract path, delay query parsing
|
|
227
|
+
const url = req.url || '/';
|
|
228
|
+
const queryIndex = url.indexOf('?');
|
|
229
|
+
if (queryIndex === -1) {
|
|
230
|
+
this.path = url;
|
|
231
|
+
this._queryString = '';
|
|
232
|
+
this._query = null; // Will be {} when accessed
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
this.path = url.substring(0, queryIndex);
|
|
236
|
+
// Store query string for lazy parsing
|
|
237
|
+
this._queryString = url.substring(queryIndex + 1);
|
|
238
|
+
this._query = null; // Parse lazily
|
|
239
|
+
}
|
|
240
|
+
// Store host for lazy URL creation
|
|
241
|
+
this._host = req.headers.host || 'localhost';
|
|
242
|
+
// URL is now lazy - don't create here!
|
|
243
|
+
this._url = null;
|
|
244
|
+
// Parse headers (direct reference, no copy)
|
|
163
245
|
this.headers = req.headers;
|
|
164
|
-
//
|
|
165
|
-
this.
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
246
|
+
// Store cookie header for lazy parsing
|
|
247
|
+
this._cookieHeader = req.headers.cookie;
|
|
248
|
+
this._cookies = null;
|
|
249
|
+
// Get response builder from pool
|
|
250
|
+
this.response = acquireResponseBuilder();
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Lazy URL getter - only create URL object when accessed
|
|
254
|
+
* Most handlers don't need the full URL object
|
|
255
|
+
*/
|
|
256
|
+
get url() {
|
|
257
|
+
if (!this._url) {
|
|
258
|
+
this._url = new URL(this.path, `http://${this._host}`);
|
|
259
|
+
}
|
|
260
|
+
return this._url;
|
|
261
|
+
}
|
|
262
|
+
set url(value) {
|
|
263
|
+
this._url = value;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Lazy query getter - only parse query string when accessed
|
|
267
|
+
* Most simple endpoints like /json don't need query parsing
|
|
268
|
+
* Inline fast-querystring for minimal overhead
|
|
269
|
+
*/
|
|
270
|
+
get query() {
|
|
271
|
+
if (this._query === null) {
|
|
272
|
+
// Inline fast-querystring call directly - no method call overhead
|
|
273
|
+
this._query = this._queryString
|
|
274
|
+
? (0, fast_querystring_1.parse)(this._queryString)
|
|
275
|
+
: {};
|
|
276
|
+
}
|
|
277
|
+
return this._query;
|
|
278
|
+
}
|
|
279
|
+
set query(value) {
|
|
280
|
+
this._query = value;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Lazy cookies getter - only parse cookies when accessed
|
|
284
|
+
*/
|
|
285
|
+
get cookies() {
|
|
286
|
+
if (!this._cookies) {
|
|
287
|
+
this._cookies = new CookieManager(this._cookieHeader);
|
|
288
|
+
}
|
|
289
|
+
return this._cookies;
|
|
290
|
+
}
|
|
291
|
+
set cookies(value) {
|
|
292
|
+
this._cookies = value;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Reinitialize context for pooling (avoids new object creation)
|
|
296
|
+
*/
|
|
297
|
+
reinitialize(req, res) {
|
|
298
|
+
this.raw = { req, res };
|
|
299
|
+
this.method = (req.method ? req.method.toUpperCase() : 'GET');
|
|
300
|
+
// Fast URL parsing - delay query parsing
|
|
301
|
+
const url = req.url || '/';
|
|
302
|
+
const queryIndex = url.indexOf('?');
|
|
303
|
+
if (queryIndex === -1) {
|
|
304
|
+
this.path = url;
|
|
305
|
+
this._queryString = '';
|
|
306
|
+
this._query = null;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
this.path = url.substring(0, queryIndex);
|
|
310
|
+
this._queryString = url.substring(queryIndex + 1);
|
|
311
|
+
this._query = null; // Parse lazily
|
|
312
|
+
}
|
|
313
|
+
// Lazy URL - don't create here
|
|
314
|
+
this._host = req.headers.host || 'localhost';
|
|
315
|
+
this._url = null;
|
|
316
|
+
this.headers = req.headers;
|
|
317
|
+
// Lazy cookies
|
|
318
|
+
this._cookieHeader = req.headers.cookie;
|
|
319
|
+
this._cookies = null;
|
|
320
|
+
// Reuse or get new response builder from pool
|
|
321
|
+
if (this.response && typeof this.response.reset === 'function') {
|
|
322
|
+
this.response.reset();
|
|
323
|
+
}
|
|
324
|
+
else {
|
|
325
|
+
this.response = acquireResponseBuilder();
|
|
326
|
+
}
|
|
327
|
+
// Reset body state
|
|
328
|
+
this._parsedBody = undefined;
|
|
329
|
+
this._bodyPromise = null;
|
|
330
|
+
this._bodyParsed = false;
|
|
331
|
+
// Reset params
|
|
332
|
+
this.params = {};
|
|
333
|
+
// Lazy data and store - just null them, create on access
|
|
334
|
+
if (this._data) {
|
|
335
|
+
this._data.clear();
|
|
336
|
+
}
|
|
337
|
+
this._requestStoreRegistry = null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Lazy body getter - parses body on first access
|
|
341
|
+
* This is the KEY optimization that fixes POST performance!
|
|
342
|
+
*/
|
|
343
|
+
get body() {
|
|
344
|
+
// If already parsed synchronously, return it
|
|
345
|
+
if (this._bodyParsed) {
|
|
346
|
+
return this._parsedBody;
|
|
347
|
+
}
|
|
348
|
+
// Return undefined if not parsed yet
|
|
349
|
+
// Use getBody() for async access
|
|
350
|
+
return this._parsedBody;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Set body directly (for backwards compatibility)
|
|
354
|
+
*/
|
|
355
|
+
set body(value) {
|
|
356
|
+
this._parsedBody = value;
|
|
357
|
+
this._bodyParsed = true;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Check if body is ready for sync access (no await needed)
|
|
361
|
+
*/
|
|
362
|
+
get isBodyReady() {
|
|
363
|
+
return this._bodyParsed;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Wait for body to be parsed
|
|
367
|
+
* Use this if you need to ensure body is available for sync access
|
|
368
|
+
* @example
|
|
369
|
+
* ```typescript
|
|
370
|
+
* app.post('/data', async (ctx) => {
|
|
371
|
+
* await ctx.waitForBody();
|
|
372
|
+
* console.log(ctx.body); // Now safe to access synchronously
|
|
373
|
+
* });
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
async waitForBody() {
|
|
377
|
+
return this.getBody();
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Async body getter - use this in handlers for POST/PUT/PATCH
|
|
381
|
+
* @example
|
|
382
|
+
* ```typescript
|
|
383
|
+
* app.post('/data', async (ctx) => {
|
|
384
|
+
* const body = await ctx.getBody();
|
|
385
|
+
* return { received: body };
|
|
386
|
+
* });
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
async getBody() {
|
|
390
|
+
// Already parsed
|
|
391
|
+
if (this._bodyParsed) {
|
|
392
|
+
return this._parsedBody;
|
|
393
|
+
}
|
|
394
|
+
// Already parsing (dedup concurrent calls)
|
|
395
|
+
if (this._bodyPromise) {
|
|
396
|
+
return this._bodyPromise;
|
|
397
|
+
}
|
|
398
|
+
// Start parsing with optimized parser
|
|
399
|
+
this._bodyPromise = this.parseBodyOptimized();
|
|
400
|
+
this._parsedBody = await this._bodyPromise;
|
|
401
|
+
this._bodyParsed = true;
|
|
402
|
+
this._bodyPromise = null;
|
|
403
|
+
return this._parsedBody;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Ultra-optimized body parser inspired by Fastify's approach
|
|
407
|
+
* Key optimizations:
|
|
408
|
+
* 1. Pre-check content-type before reading data
|
|
409
|
+
* 2. Use direct string concatenation with setEncoding
|
|
410
|
+
* 3. Minimal closure allocation
|
|
411
|
+
* 4. Fast-path for JSON (most common case)
|
|
412
|
+
*/
|
|
413
|
+
parseBodyOptimized() {
|
|
414
|
+
const req = this.raw.req;
|
|
415
|
+
const contentType = req.headers['content-type'];
|
|
416
|
+
// Fast path: determine parser type once, before data collection
|
|
417
|
+
const isJSON = contentType ? contentType.charCodeAt(0) === 97 && contentType.startsWith('application/json') : false;
|
|
418
|
+
const isForm = !isJSON && contentType ? contentType.includes('x-www-form-urlencoded') : false;
|
|
419
|
+
return new Promise((resolve, reject) => {
|
|
420
|
+
// Set encoding for string mode - avoids Buffer.toString() overhead
|
|
421
|
+
req.setEncoding('utf8');
|
|
422
|
+
let body = '';
|
|
423
|
+
const onData = (chunk) => {
|
|
424
|
+
body += chunk;
|
|
425
|
+
};
|
|
426
|
+
const onEnd = () => {
|
|
427
|
+
// Cleanup listeners immediately
|
|
428
|
+
req.removeListener('data', onData);
|
|
429
|
+
req.removeListener('end', onEnd);
|
|
430
|
+
req.removeListener('error', onError);
|
|
431
|
+
if (!body) {
|
|
432
|
+
resolve({});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
if (isJSON) {
|
|
437
|
+
resolve(JSON.parse(body));
|
|
438
|
+
}
|
|
439
|
+
else if (isForm) {
|
|
440
|
+
resolve((0, fast_querystring_1.parse)(body));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
resolve(body);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
catch (e) {
|
|
447
|
+
reject(e);
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
const onError = (err) => {
|
|
451
|
+
req.removeListener('data', onData);
|
|
452
|
+
req.removeListener('end', onEnd);
|
|
453
|
+
req.removeListener('error', onError);
|
|
454
|
+
reject(err);
|
|
455
|
+
};
|
|
456
|
+
req.on('data', onData);
|
|
457
|
+
req.on('end', onEnd);
|
|
458
|
+
req.on('error', onError);
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Internal body parser - optimized for performance
|
|
463
|
+
* Uses string accumulation instead of Buffer.concat for better perf
|
|
464
|
+
* @deprecated Use parseBodyOptimized instead
|
|
465
|
+
*/
|
|
466
|
+
parseBodyInternal() {
|
|
467
|
+
return new Promise((resolve, reject) => {
|
|
468
|
+
const req = this.raw.req;
|
|
469
|
+
const contentType = req.headers['content-type'] || '';
|
|
470
|
+
// Use setEncoding to get strings directly - faster than Buffer.toString()
|
|
471
|
+
req.setEncoding('utf8');
|
|
472
|
+
let body = '';
|
|
473
|
+
req.on('data', (chunk) => {
|
|
474
|
+
body += chunk;
|
|
475
|
+
});
|
|
476
|
+
req.on('end', () => {
|
|
477
|
+
if (!body) {
|
|
478
|
+
resolve({});
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
// Inline content type check for hot path (JSON)
|
|
483
|
+
if (contentType.includes('application/json')) {
|
|
484
|
+
resolve(JSON.parse(body));
|
|
485
|
+
}
|
|
486
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
487
|
+
resolve((0, fast_querystring_1.parse)(body));
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
resolve(body);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch (error) {
|
|
494
|
+
reject(error);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
req.on('error', reject);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Parse body based on content type
|
|
502
|
+
*/
|
|
503
|
+
parseContentType(body, contentType) {
|
|
504
|
+
if (contentType.includes('application/json')) {
|
|
505
|
+
return body ? JSON.parse(body) : {};
|
|
506
|
+
}
|
|
507
|
+
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
508
|
+
return (0, fast_querystring_1.parse)(body);
|
|
509
|
+
}
|
|
510
|
+
else if (contentType.includes('text/')) {
|
|
511
|
+
return body;
|
|
512
|
+
}
|
|
513
|
+
return body;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Clear body state (for pooling)
|
|
517
|
+
*/
|
|
518
|
+
clearBody() {
|
|
519
|
+
this._parsedBody = undefined;
|
|
520
|
+
this._bodyPromise = null;
|
|
521
|
+
this._bodyParsed = false;
|
|
170
522
|
}
|
|
171
523
|
// Convenience methods
|
|
172
524
|
json(data, status) {
|
|
@@ -215,6 +567,24 @@ class ContextImpl {
|
|
|
215
567
|
}
|
|
216
568
|
return this._storeRegistry.get(StoreClass);
|
|
217
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Lazy getter for request store registry
|
|
572
|
+
*/
|
|
573
|
+
getOrCreateRequestStoreRegistry() {
|
|
574
|
+
if (!this._requestStoreRegistry) {
|
|
575
|
+
this._requestStoreRegistry = new store_1.RequestStoreRegistry(this._debug);
|
|
576
|
+
}
|
|
577
|
+
return this._requestStoreRegistry;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Lazy getter for request data map
|
|
581
|
+
*/
|
|
582
|
+
getOrCreateData() {
|
|
583
|
+
if (!this._data) {
|
|
584
|
+
this._data = new Map();
|
|
585
|
+
}
|
|
586
|
+
return this._data;
|
|
587
|
+
}
|
|
218
588
|
/**
|
|
219
589
|
* Access a request-scoped store by its class
|
|
220
590
|
* Store only exists for this request, disposed after response
|
|
@@ -244,7 +614,7 @@ class ContextImpl {
|
|
|
244
614
|
* ```
|
|
245
615
|
*/
|
|
246
616
|
requestStore(StoreClass) {
|
|
247
|
-
return this.
|
|
617
|
+
return this.getOrCreateRequestStoreRegistry().get(StoreClass);
|
|
248
618
|
}
|
|
249
619
|
/**
|
|
250
620
|
* Set a value in request-scoped storage
|
|
@@ -264,7 +634,7 @@ class ContextImpl {
|
|
|
264
634
|
* ```
|
|
265
635
|
*/
|
|
266
636
|
set(key, value) {
|
|
267
|
-
this.
|
|
637
|
+
this.getOrCreateData().set(key, value);
|
|
268
638
|
}
|
|
269
639
|
/**
|
|
270
640
|
* Get a value from request-scoped storage
|
|
@@ -279,7 +649,7 @@ class ContextImpl {
|
|
|
279
649
|
* ```
|
|
280
650
|
*/
|
|
281
651
|
get(key) {
|
|
282
|
-
return this._data
|
|
652
|
+
return this._data?.get(key);
|
|
283
653
|
}
|
|
284
654
|
/**
|
|
285
655
|
* Set store registry (called by Application)
|
|
@@ -294,23 +664,32 @@ class ContextImpl {
|
|
|
294
664
|
*/
|
|
295
665
|
setDebugMode(debug) {
|
|
296
666
|
this._debug = debug;
|
|
297
|
-
//
|
|
298
|
-
this._requestStoreRegistry =
|
|
667
|
+
// Reset request store registry - will be created lazily with new debug mode
|
|
668
|
+
this._requestStoreRegistry = null;
|
|
299
669
|
}
|
|
300
670
|
/**
|
|
301
671
|
* Dispose request-scoped stores and data (called after response)
|
|
302
672
|
* @internal
|
|
303
673
|
*/
|
|
304
674
|
disposeRequestStores() {
|
|
305
|
-
this._requestStoreRegistry
|
|
306
|
-
|
|
675
|
+
if (this._requestStoreRegistry) {
|
|
676
|
+
this._requestStoreRegistry.dispose();
|
|
677
|
+
this._requestStoreRegistry = null;
|
|
678
|
+
}
|
|
679
|
+
if (this._data) {
|
|
680
|
+
this._data.clear();
|
|
681
|
+
}
|
|
682
|
+
// Release response builder back to pool
|
|
683
|
+
if (this.response && typeof this.response.reset === 'function') {
|
|
684
|
+
releaseResponseBuilder(this.response);
|
|
685
|
+
}
|
|
307
686
|
}
|
|
308
687
|
/**
|
|
309
688
|
* Get request store registry for advanced usage
|
|
310
689
|
* @internal
|
|
311
690
|
*/
|
|
312
691
|
getRequestStoreRegistry() {
|
|
313
|
-
return this.
|
|
692
|
+
return this.getOrCreateRequestStoreRegistry();
|
|
314
693
|
}
|
|
315
694
|
/**
|
|
316
695
|
* Set route parameters (called by router)
|
|
@@ -319,21 +698,38 @@ class ContextImpl {
|
|
|
319
698
|
this.params = params;
|
|
320
699
|
}
|
|
321
700
|
/**
|
|
322
|
-
* Set
|
|
701
|
+
* Set response serializers for fast JSON serialization
|
|
702
|
+
* Called by router when route has response schema
|
|
703
|
+
* @internal
|
|
704
|
+
*/
|
|
705
|
+
setSerializers(serializers) {
|
|
706
|
+
if (this.response && typeof this.response.setSerializers === 'function') {
|
|
707
|
+
this.response.setSerializers(serializers);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Set request body (called after parsing or by middleware)
|
|
712
|
+
* @deprecated Use ctx.getBody() for async body access
|
|
323
713
|
*/
|
|
324
714
|
setBody(body) {
|
|
325
|
-
this.
|
|
715
|
+
this._parsedBody = body;
|
|
716
|
+
this._bodyParsed = true;
|
|
326
717
|
}
|
|
327
718
|
/**
|
|
328
719
|
* Get all Set-Cookie headers
|
|
329
720
|
*/
|
|
330
721
|
getSetCookieHeaders() {
|
|
331
|
-
|
|
722
|
+
// Use _cookies directly to avoid creating CookieManager if not needed
|
|
723
|
+
if (!this._cookies) {
|
|
724
|
+
return [];
|
|
725
|
+
}
|
|
726
|
+
return this._cookies.getSetCookieHeaders();
|
|
332
727
|
}
|
|
333
728
|
}
|
|
334
729
|
exports.ContextImpl = ContextImpl;
|
|
335
730
|
/**
|
|
336
731
|
* Parse request body based on Content-Type
|
|
732
|
+
* @deprecated Use ctx.getBody() instead for lazy parsing
|
|
337
733
|
*/
|
|
338
734
|
async function parseBody(req) {
|
|
339
735
|
return new Promise((resolve, reject) => {
|
|
@@ -348,7 +744,7 @@ async function parseBody(req) {
|
|
|
348
744
|
resolve(body ? JSON.parse(body) : {});
|
|
349
745
|
}
|
|
350
746
|
else if (contentType.includes('application/x-www-form-urlencoded')) {
|
|
351
|
-
resolve((0,
|
|
747
|
+
resolve((0, fast_querystring_1.parse)(body));
|
|
352
748
|
}
|
|
353
749
|
else if (contentType.includes('text/')) {
|
|
354
750
|
resolve(body);
|