@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/application.ts
CHANGED
|
@@ -1045,145 +1045,211 @@ export class Application<TDeps extends DependencyContainer = {}> {
|
|
|
1045
1045
|
|
|
1046
1046
|
/**
|
|
1047
1047
|
* Handle incoming HTTP request
|
|
1048
|
+
* Optimized hot path for maximum performance
|
|
1048
1049
|
*/
|
|
1049
1050
|
private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1051
|
+
// Use non-null assertion - context pool always returns valid context
|
|
1052
|
+
// acquire is now sync for better performance
|
|
1053
|
+
const ctx = this.contextPool.acquire(req, res) as any;
|
|
1054
|
+
|
|
1053
1055
|
try {
|
|
1054
|
-
//
|
|
1055
|
-
ctx
|
|
1056
|
-
|
|
1057
|
-
// Inject store registry into context
|
|
1058
|
-
if ('setStoreRegistry' in ctx && typeof ctx.setStoreRegistry === 'function') {
|
|
1059
|
-
(ctx as any).setStoreRegistry(this.storeRegistry);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// Set debug mode for request stores
|
|
1063
|
-
if ('setDebugMode' in ctx && typeof ctx.setDebugMode === 'function') {
|
|
1064
|
-
(ctx as any).setDebugMode(this.config.debug ?? false);
|
|
1065
|
-
}
|
|
1056
|
+
// Direct method calls - ContextImpl always has these methods
|
|
1057
|
+
ctx.setStoreRegistry(this.storeRegistry);
|
|
1058
|
+
ctx.setDebugMode(this.config.debug ?? false);
|
|
1066
1059
|
|
|
1067
|
-
// === HOOK: onRequest ===
|
|
1060
|
+
// === HOOK: onRequest (skip check if no hooks) ===
|
|
1068
1061
|
if (this.lifecycleHooks.onRequest) {
|
|
1069
1062
|
const hookResult = await this.lifecycleHooks.onRequest(ctx);
|
|
1070
|
-
if (
|
|
1063
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
1071
1064
|
await this.sendResponse(res, hookResult, ctx);
|
|
1065
|
+
this.cleanupRequest(ctx);
|
|
1072
1066
|
return;
|
|
1073
1067
|
}
|
|
1074
1068
|
}
|
|
1075
1069
|
|
|
1076
|
-
//
|
|
1070
|
+
// Fast path: no versioning configured (most common)
|
|
1077
1071
|
let matchPath = ctx.path;
|
|
1078
1072
|
if (this.versioningConfig) {
|
|
1079
1073
|
const { version, basePath, source } = this.resolveVersion(ctx);
|
|
1080
1074
|
ctx.version = version;
|
|
1081
1075
|
ctx.versionSource = source;
|
|
1082
1076
|
|
|
1083
|
-
|
|
1084
|
-
if (source === 'header' || source === 'query' || source === 'default') {
|
|
1077
|
+
if (source !== 'path') {
|
|
1085
1078
|
matchPath = `/${version}${basePath.startsWith('/') ? basePath : '/' + basePath}`;
|
|
1086
1079
|
}
|
|
1087
|
-
|
|
1088
|
-
if (this.config.debug) {
|
|
1089
|
-
console.log(`[Versioning] ${source} → ${version}, path: ${ctx.path} → ${matchPath}`);
|
|
1090
|
-
}
|
|
1091
1080
|
}
|
|
1092
1081
|
|
|
1093
1082
|
// Find matching route
|
|
1094
1083
|
const match = this.router.match(ctx.method, matchPath);
|
|
1095
1084
|
|
|
1096
1085
|
if (!match) {
|
|
1097
|
-
// Try fallback handler
|
|
1086
|
+
// Try fallback handler
|
|
1098
1087
|
if (this.fallbackHandler) {
|
|
1099
1088
|
const fallbackResponse = await this.fallbackHandler(ctx, this.dependencies);
|
|
1100
1089
|
await this.sendResponse(res, fallbackResponse, ctx);
|
|
1090
|
+
this.cleanupRequest(ctx);
|
|
1101
1091
|
return;
|
|
1102
1092
|
}
|
|
1103
1093
|
|
|
1104
|
-
// 404 Not Found
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
body: JSON.stringify({ error: 'Not Found' })
|
|
1109
|
-
}, ctx);
|
|
1094
|
+
// 404 Not Found - use cached response
|
|
1095
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
1096
|
+
res.end('{"error":"Not Found"}');
|
|
1097
|
+
this.cleanupRequest(ctx);
|
|
1110
1098
|
return;
|
|
1111
1099
|
}
|
|
1112
1100
|
|
|
1113
|
-
// Set route params
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1101
|
+
// Set route params directly
|
|
1102
|
+
ctx.params = match.params;
|
|
1103
|
+
|
|
1104
|
+
// Set serializers for fast JSON response if available
|
|
1105
|
+
if (match._serializer) {
|
|
1106
|
+
ctx.setSerializers(match._serializer);
|
|
1118
1107
|
}
|
|
1119
1108
|
|
|
1120
|
-
//
|
|
1121
|
-
|
|
1109
|
+
// Auto-parse body for POST/PUT/PATCH if body exists
|
|
1110
|
+
// This maintains backwards compatibility while keeping lazy-loading for GET
|
|
1111
|
+
const method = ctx.method;
|
|
1112
|
+
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
|
1113
|
+
// Only parse if there's actually a body (Content-Length > 0 or Transfer-Encoding)
|
|
1114
|
+
const contentLength = req.headers['content-length'];
|
|
1115
|
+
const transferEncoding = req.headers['transfer-encoding'];
|
|
1116
|
+
const hasBody = (contentLength && contentLength !== '0') || transferEncoding;
|
|
1117
|
+
|
|
1118
|
+
if (hasBody) {
|
|
1119
|
+
await ctx.getBody();
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
1122
|
|
|
1123
|
-
//
|
|
1124
|
-
response
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
match.handler
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1123
|
+
// Fast path: no middleware
|
|
1124
|
+
let response: Response;
|
|
1125
|
+
if (this.globalMiddlewares.length === 0 && match.middlewares.length === 0) {
|
|
1126
|
+
// Direct handler execution (skip middleware executor)
|
|
1127
|
+
response = await this.executeHandlerDirect(ctx, match.handler);
|
|
1128
|
+
} else {
|
|
1129
|
+
// Combine global and route-specific middleware
|
|
1130
|
+
const allMiddlewares = this.globalMiddlewares.length === 0
|
|
1131
|
+
? match.middlewares
|
|
1132
|
+
: match.middlewares.length === 0
|
|
1133
|
+
? this.globalMiddlewares
|
|
1134
|
+
: [...this.globalMiddlewares, ...match.middlewares];
|
|
1135
|
+
|
|
1136
|
+
// Execute middleware chain and handler with hooks
|
|
1137
|
+
response = await this.middlewareExecutor.executeWithHooks(
|
|
1138
|
+
ctx,
|
|
1139
|
+
allMiddlewares,
|
|
1140
|
+
match.handler,
|
|
1141
|
+
this.lifecycleHooks,
|
|
1142
|
+
this.dependencies
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1131
1145
|
|
|
1132
1146
|
// === HOOK: onResponse ===
|
|
1133
1147
|
if (this.lifecycleHooks.onResponse) {
|
|
1134
1148
|
const hookResult = await this.lifecycleHooks.onResponse(ctx, response);
|
|
1135
|
-
if (
|
|
1149
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
1136
1150
|
response = hookResult;
|
|
1137
1151
|
}
|
|
1138
1152
|
}
|
|
1139
1153
|
|
|
1140
1154
|
// Send response
|
|
1141
1155
|
await this.sendResponse(res, response, ctx);
|
|
1156
|
+
this.cleanupRequest(ctx);
|
|
1142
1157
|
|
|
1143
1158
|
} catch (error) {
|
|
1159
|
+
await this.handleError(error as Error, ctx, res);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
/**
|
|
1164
|
+
* Execute handler directly without middleware (fast path)
|
|
1165
|
+
*/
|
|
1166
|
+
private async executeHandlerDirect(ctx: any, handler: Handler): Promise<Response> {
|
|
1167
|
+
// === HOOK: beforeHandler ===
|
|
1168
|
+
if (this.lifecycleHooks.beforeHandler) {
|
|
1169
|
+
const hookResult = await this.lifecycleHooks.beforeHandler(ctx);
|
|
1170
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
1171
|
+
return hookResult;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
let result = await handler(ctx, this.dependencies);
|
|
1176
|
+
|
|
1177
|
+
// If handler returns an Error, throw it
|
|
1178
|
+
if (result instanceof Error) {
|
|
1179
|
+
(result as any)._isIntentional = true;
|
|
1180
|
+
throw result;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// === HOOK: afterHandler ===
|
|
1184
|
+
if (this.lifecycleHooks.afterHandler) {
|
|
1185
|
+
const transformedResult = await this.lifecycleHooks.afterHandler(ctx, result);
|
|
1186
|
+
if (transformedResult !== undefined) {
|
|
1187
|
+
result = transformedResult;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// If result is a Response, return it
|
|
1192
|
+
if (result && typeof result === 'object' && 'statusCode' in result && 'body' in result) {
|
|
1193
|
+
return result as Response;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// Otherwise, wrap in JSON response
|
|
1197
|
+
return ctx.json(result);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* Cleanup request resources
|
|
1202
|
+
*/
|
|
1203
|
+
private cleanupRequest(ctx: any): void {
|
|
1204
|
+
// Dispose request-scoped stores
|
|
1205
|
+
if (ctx.disposeRequestStores) {
|
|
1206
|
+
ctx.disposeRequestStores();
|
|
1207
|
+
}
|
|
1208
|
+
// Release context back to pool
|
|
1209
|
+
this.contextPool.release(ctx);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/**
|
|
1213
|
+
* Handle errors
|
|
1214
|
+
*/
|
|
1215
|
+
private async handleError(error: Error, ctx: any, res: ServerResponse): Promise<void> {
|
|
1216
|
+
try {
|
|
1144
1217
|
// === HOOK: onError ===
|
|
1145
1218
|
if (ctx && this.lifecycleHooks.onError) {
|
|
1146
1219
|
try {
|
|
1147
|
-
const hookResult = await this.lifecycleHooks.onError(ctx, error
|
|
1148
|
-
if (
|
|
1220
|
+
const hookResult = await this.lifecycleHooks.onError(ctx, error);
|
|
1221
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
1149
1222
|
await this.sendResponse(res, hookResult, ctx);
|
|
1223
|
+
this.cleanupRequest(ctx);
|
|
1150
1224
|
return;
|
|
1151
1225
|
}
|
|
1152
1226
|
} catch (hookError) {
|
|
1153
|
-
// Hook itself threw an error, continue to default error handler
|
|
1154
1227
|
console.error('onError hook failed:', hookError);
|
|
1155
1228
|
}
|
|
1156
1229
|
}
|
|
1157
1230
|
|
|
1158
1231
|
// Handle errors with default error handler
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
}
|
|
1232
|
+
const errorResponse = await this.errorHandler(error, ctx);
|
|
1233
|
+
|
|
1234
|
+
// === HOOK: onResponse (for error responses) ===
|
|
1235
|
+
if (ctx && this.lifecycleHooks.onResponse) {
|
|
1236
|
+
const hookResult = await this.lifecycleHooks.onResponse(ctx, errorResponse);
|
|
1237
|
+
if (hookResult && typeof hookResult === 'object' && 'statusCode' in hookResult) {
|
|
1238
|
+
await this.sendResponse(res, hookResult, ctx);
|
|
1239
|
+
this.cleanupRequest(ctx);
|
|
1240
|
+
return;
|
|
1169
1241
|
}
|
|
1170
|
-
|
|
1171
|
-
await this.sendResponse(res, errorResponse, ctx!);
|
|
1172
|
-
} catch (handlerError) {
|
|
1173
|
-
// Fallback error response
|
|
1174
|
-
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
1175
|
-
res.end('Internal Server Error');
|
|
1176
|
-
}
|
|
1177
|
-
} finally {
|
|
1178
|
-
// Dispose request-scoped stores
|
|
1179
|
-
if (ctx && 'disposeRequestStores' in ctx && typeof ctx.disposeRequestStores === 'function') {
|
|
1180
|
-
(ctx as any).disposeRequestStores();
|
|
1181
1242
|
}
|
|
1182
1243
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
}
|
|
1244
|
+
await this.sendResponse(res, errorResponse, ctx);
|
|
1245
|
+
} catch (handlerError) {
|
|
1246
|
+
// Fallback error response
|
|
1247
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
1248
|
+
res.end('Internal Server Error');
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
if (ctx) {
|
|
1252
|
+
this.cleanupRequest(ctx);
|
|
1187
1253
|
}
|
|
1188
1254
|
}
|
|
1189
1255
|
|
|
@@ -1202,13 +1268,65 @@ export class Application<TDeps extends DependencyContainer = {}> {
|
|
|
1202
1268
|
|
|
1203
1269
|
/**
|
|
1204
1270
|
* Send HTTP response
|
|
1271
|
+
* Optimized for minimal overhead - key for benchmark performance
|
|
1205
1272
|
*/
|
|
1206
1273
|
private async sendResponse(res: ServerResponse, response: Response, ctx: Context): Promise<void> {
|
|
1207
|
-
//
|
|
1208
|
-
|
|
1274
|
+
// Handle stream responses separately
|
|
1275
|
+
if (response.stream) {
|
|
1276
|
+
res.statusCode = response.statusCode;
|
|
1277
|
+
const headers = response.headers;
|
|
1278
|
+
for (const key in headers) {
|
|
1279
|
+
const value = headers[key];
|
|
1280
|
+
if (value !== undefined) {
|
|
1281
|
+
res.setHeader(key, value);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
response.stream.pipe(res);
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1209
1287
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1288
|
+
const body = response.body;
|
|
1289
|
+
const headers = response.headers;
|
|
1290
|
+
|
|
1291
|
+
// Fast path for JSON responses (most common case)
|
|
1292
|
+
// Use writeHead for single syscall instead of multiple setHeader calls
|
|
1293
|
+
if (headers['Content-Type'] === 'application/json') {
|
|
1294
|
+
// Calculate content length for proper HTTP behavior
|
|
1295
|
+
const contentLength = Buffer.byteLength(body, 'utf8');
|
|
1296
|
+
|
|
1297
|
+
// Check if we need to set cookies
|
|
1298
|
+
let setCookies: string[] | null = null;
|
|
1299
|
+
if ('getSetCookieHeaders' in ctx && typeof ctx.getSetCookieHeaders === 'function') {
|
|
1300
|
+
const cookies = ctx.getSetCookieHeaders();
|
|
1301
|
+
if (cookies.length > 0) {
|
|
1302
|
+
setCookies = cookies;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// Single writeHead call for all headers
|
|
1307
|
+
if (setCookies) {
|
|
1308
|
+
res.writeHead(response.statusCode, {
|
|
1309
|
+
'Content-Type': 'application/json',
|
|
1310
|
+
'Content-Length': contentLength,
|
|
1311
|
+
'Set-Cookie': setCookies
|
|
1312
|
+
});
|
|
1313
|
+
} else {
|
|
1314
|
+
res.writeHead(response.statusCode, {
|
|
1315
|
+
'Content-Type': 'application/json',
|
|
1316
|
+
'Content-Length': contentLength
|
|
1317
|
+
});
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
res.end(body);
|
|
1321
|
+
return;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// General path for other response types
|
|
1325
|
+
res.statusCode = response.statusCode;
|
|
1326
|
+
|
|
1327
|
+
// Set headers using for-in (faster than Object.entries)
|
|
1328
|
+
for (const key in headers) {
|
|
1329
|
+
const value = headers[key];
|
|
1212
1330
|
if (value !== undefined) {
|
|
1213
1331
|
res.setHeader(key, value);
|
|
1214
1332
|
}
|
|
@@ -1222,12 +1340,12 @@ export class Application<TDeps extends DependencyContainer = {}> {
|
|
|
1222
1340
|
}
|
|
1223
1341
|
}
|
|
1224
1342
|
|
|
1225
|
-
//
|
|
1226
|
-
if (
|
|
1227
|
-
|
|
1228
|
-
} else {
|
|
1229
|
-
res.end(response.body);
|
|
1343
|
+
// Set Content-Length for non-stream responses
|
|
1344
|
+
if (body && typeof body === 'string') {
|
|
1345
|
+
res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'));
|
|
1230
1346
|
}
|
|
1347
|
+
|
|
1348
|
+
res.end(body);
|
|
1231
1349
|
}
|
|
1232
1350
|
|
|
1233
1351
|
/**
|
package/src/core/context-pool.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
7
|
-
import { ContextImpl
|
|
7
|
+
import { ContextImpl } from './context';
|
|
8
8
|
import { Context } from './types';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -23,26 +23,25 @@ export class ContextPool {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Acquire a context from the pool or create a new one
|
|
26
|
+
* Body parsing is now lazy - handlers should use ctx.getBody() for POST/PUT/PATCH
|
|
26
27
|
*/
|
|
27
|
-
|
|
28
|
+
acquire(req: IncomingMessage, res: ServerResponse): Context {
|
|
28
29
|
let ctx: ContextImpl;
|
|
29
30
|
|
|
30
31
|
// Try to reuse from pool
|
|
31
32
|
if (this.pool.length > 0) {
|
|
32
33
|
ctx = this.pool.pop()!;
|
|
33
34
|
this.reused++;
|
|
34
|
-
|
|
35
|
+
// Use reinitialize instead of creating new object
|
|
36
|
+
ctx.reinitialize(req, res);
|
|
35
37
|
} else {
|
|
36
38
|
// Create new context
|
|
37
39
|
ctx = new ContextImpl(req, res);
|
|
38
40
|
this.created++;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
const body = await parseBody(req);
|
|
44
|
-
ctx.setBody(body);
|
|
45
|
-
}
|
|
43
|
+
// Body parsing removed - now lazy via ctx.getBody()
|
|
44
|
+
// This matches Fastify's architecture for optimal POST performance
|
|
46
45
|
|
|
47
46
|
return ctx;
|
|
48
47
|
}
|
|
@@ -52,59 +51,12 @@ export class ContextPool {
|
|
|
52
51
|
*/
|
|
53
52
|
release(ctx: Context): void {
|
|
54
53
|
if (this.pool.length < this.maxSize) {
|
|
55
|
-
//
|
|
56
|
-
this.clearContext(ctx as ContextImpl);
|
|
54
|
+
// Just push back - reinitialize will handle reset
|
|
57
55
|
this.pool.push(ctx as ContextImpl);
|
|
58
56
|
}
|
|
59
57
|
// If pool is full, let GC handle it
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
/**
|
|
63
|
-
* Reset context for reuse
|
|
64
|
-
*/
|
|
65
|
-
private async reset(
|
|
66
|
-
ctx: ContextImpl,
|
|
67
|
-
req: IncomingMessage,
|
|
68
|
-
res: ServerResponse
|
|
69
|
-
): Promise<void> {
|
|
70
|
-
// Recreate the context with new request/response
|
|
71
|
-
const newCtx = new ContextImpl(req, res);
|
|
72
|
-
|
|
73
|
-
// Copy properties
|
|
74
|
-
Object.assign(ctx, newCtx);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Clear sensitive data from context
|
|
79
|
-
*/
|
|
80
|
-
private clearContext(ctx: ContextImpl): void {
|
|
81
|
-
ctx.params = {};
|
|
82
|
-
ctx.query = {};
|
|
83
|
-
ctx.body = null;
|
|
84
|
-
|
|
85
|
-
// Clear custom properties added by middleware
|
|
86
|
-
const knownProps = new Set([
|
|
87
|
-
'method', 'path', 'url', 'params', 'query', 'body',
|
|
88
|
-
'headers', 'cookies', 'raw', 'response',
|
|
89
|
-
'json', 'html', 'text', 'redirect', 'stream',
|
|
90
|
-
'setParams', 'setBody', 'getSetCookieHeaders'
|
|
91
|
-
]);
|
|
92
|
-
|
|
93
|
-
for (const key in ctx) {
|
|
94
|
-
if (!knownProps.has(key)) {
|
|
95
|
-
delete (ctx as any)[key];
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Check if request has a body
|
|
102
|
-
*/
|
|
103
|
-
private hasBody(req: IncomingMessage): boolean {
|
|
104
|
-
const method = req.method?.toUpperCase();
|
|
105
|
-
return method === 'POST' || method === 'PUT' || method === 'PATCH';
|
|
106
|
-
}
|
|
107
|
-
|
|
108
60
|
/**
|
|
109
61
|
* Get pool statistics
|
|
110
62
|
*/
|