@aetherframework/middleware 1.0.2 → 1.0.5

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.
@@ -1,374 +1,350 @@
1
- /**
1
+ /**
2
2
  * @license MIT
3
3
  * Copyright (c) 2026-present AetherFramework Contributors.
4
4
  * SPDX-License-Identifier: MIT
5
5
  * @module @aetherframework/middleware/core/AetherPipeline
6
6
  */
7
+
7
8
  import { EventEmitter } from "events";
8
9
  import AetherContext from "./AetherContext.js";
9
10
 
11
+ // ==========================================
12
+ // [V8-OPT] PRE-ALLOCATED STATIC BUFFERS
13
+ // ==========================================
10
14
  const STATIC_RESPONSES = new Map([
11
- [200, Buffer.from(JSON.stringify({ status: "ok" }))],
12
- [404, Buffer.from(JSON.stringify({ error: "Not Found" }))],
13
- [500, Buffer.from(JSON.stringify({ error: "Internal Server Error" }))],
15
+ [200, Buffer.from('{"status":"ok"}')],
16
+ [404, Buffer.from('{"error":"Not Found"}')],
17
+ [500, Buffer.from('{"error":"Internal Server Error"}')],
14
18
  ]);
15
19
 
16
- const CONTEXT_POOL = [];
17
- const IN_POOL_CHECK = new Set();
18
- const CONTEXT_POOL_SIZE = 4096;
20
+ // ==========================================
21
+ // [V8-OPT] O(1) HIGH-PERFORMANCE CACHE
22
+ // ==========================================
23
+ class FastCache {
24
+ constructor(maxSize = 2000) {
25
+ this.maxSize = maxSize;
26
+ this.cache = new Map();
27
+ }
19
28
 
20
- class AetherPipeline extends EventEmitter {
21
- constructor() {
22
- super();
23
- this._middlewares = [];
24
- this._compiled = null;
25
- this._compiledSync = null;
26
- this._cache = new Map();
27
- this._cacheMaxSize = 1000;
28
- this.enableMetrics = false;
29
-
30
- this._stats = {
31
- totalRequests: 0,
32
- averageLatency: 0,
33
- errorCount: 0,
34
- cacheHits: 0,
35
- cacheMisses: 0,
36
- poolHits: 0,
37
- poolMisses: 0,
38
- };
29
+ get(key) {
30
+ return this.cache.get(key);
31
+ }
32
+
33
+ set(key, value) {
34
+ if (this.cache.size >= this.maxSize) {
35
+ const firstKey = this.cache.keys().next().value;
36
+ if (firstKey) this.cache.delete(firstKey);
37
+ }
38
+ this.cache.set(key, value);
39
+ }
39
40
 
40
- this._initObjectPools();
41
+ clear() {
42
+ this.cache.clear();
41
43
  }
42
44
 
43
- _initObjectPools() {
44
- for (let i = 0; i < CONTEXT_POOL_SIZE; i++) {
45
+ get size() { return this.cache.size; }
46
+ }
47
+
48
+ // ==========================================
49
+ // [V8-OPT] LOCK-FREE CONTEXT POOL
50
+ // ==========================================
51
+ class ContextPool {
52
+ constructor(size = 8192) {
53
+ this.pool = new Array(size);
54
+ this.index = size;
55
+
56
+ for (let i = 0; i < size; i++) {
45
57
  const ctx = new AetherContext(null, null);
46
- CONTEXT_POOL.push(ctx);
47
- IN_POOL_CHECK.add(ctx);
58
+ ctx._inPool = true;
59
+ this.pool[i] = ctx;
48
60
  }
49
61
  }
50
62
 
51
- _getContext(request, response) {
52
- if (CONTEXT_POOL.length > 0) {
53
- const context = CONTEXT_POOL.pop();
54
- IN_POOL_CHECK.delete(context);
55
- context._reset(request, response);
56
- if (this.enableMetrics) this._stats.poolHits++;
57
- return context;
63
+ get(req, res) {
64
+ let ctx;
65
+ if (this.index > 0) {
66
+ ctx = this.pool[--this.index];
67
+ } else {
68
+ ctx = new AetherContext(null, null);
69
+ ctx._inPool = false;
58
70
  }
59
- if (this.enableMetrics) this._stats.poolMisses++;
60
- return new AetherContext(request, response);
71
+
72
+ // [PERF] Call _reset WITH arguments when ACQUIRING
73
+ if (typeof ctx._reset === 'function') {
74
+ ctx._reset(req, res);
75
+ } else {
76
+ ctx.req = req;
77
+ ctx.res = res;
78
+ ctx.statusCode = 200;
79
+ ctx._terminated = false;
80
+ }
81
+
82
+ ctx._inPool = false;
83
+ return ctx;
61
84
  }
62
85
 
63
- _returnContext(context) {
64
- if (!context || IN_POOL_CHECK.has(context)) return;
65
-
66
- if (CONTEXT_POOL.length < CONTEXT_POOL_SIZE) {
67
- context.req = null;
68
- context.res = null;
69
- context._body = null;
70
-
71
- // Safely clean up Headers
72
- if (context._headers && typeof context._headers.clear === "function") {
73
- context._headers.clear();
74
- } else {
75
- context._headers = null;
86
+ release(ctx) {
87
+ if (!ctx || ctx._inPool) return;
88
+
89
+ if (this.index < this.pool.length) {
90
+ ctx.req = null;
91
+ ctx.res = null;
92
+ ctx._body = null;
93
+ ctx._queryCache = null;
94
+ ctx._ipCache = null;
95
+ ctx.statusCode = 200;
96
+ ctx._terminated = false;
97
+ ctx.params = null;
98
+ ctx.route = null;
99
+
100
+ // [V8-OPT] Clear headers without breaking Hidden Class
101
+ if (ctx._headersCount > 0) {
102
+ for (let i = 0; i < ctx._headersCount; i++) {
103
+ ctx._headersObj[ctx._headersKeys[i]] = undefined;
104
+ }
105
+ ctx._headersCount = 0;
106
+ } else if (ctx._headers) {
107
+ for (const key in ctx._headers) {
108
+ ctx._headers[key] = undefined;
109
+ }
110
+ }
111
+
112
+ if (ctx._stateObj) {
113
+ for (const key in ctx._stateObj) {
114
+ ctx._stateObj[key] = undefined;
115
+ }
76
116
  }
77
117
 
78
- CONTEXT_POOL.push(context);
79
- IN_POOL_CHECK.add(context);
118
+ ctx._inPool = true;
119
+ this.pool[this.index++] = ctx;
80
120
  }
81
121
  }
122
+ }
123
+
124
+ const contextPool = new ContextPool(8192);
125
+
126
+ // ==========================================
127
+ // AETHER PIPELINE CORE - STATISTICS REMOVED
128
+ // ==========================================
129
+ class AetherPipeline extends EventEmitter {
130
+ constructor() {
131
+ super();
132
+ this.middlewares = [];
133
+ this.cache = new FastCache(2000);
134
+ this._compiledChain = null;
135
+ }
82
136
 
83
137
  use(middleware) {
84
138
  if (typeof middleware !== "function") {
85
139
  throw new TypeError("Middleware must be a function");
86
140
  }
87
- this._middlewares.push(middleware);
88
- this._compiled = null;
89
- this._compiledSync = null;
141
+ this.middlewares.push(middleware);
142
+ this._compiledChain = null;
90
143
  return this;
91
144
  }
92
145
 
93
- /**
94
- *Core Control 1: Standard V8-level high-concurrency asynchronous onion model compiler
95
- * Ensure perfect timing wait chain, absolutely prevent asynchronous middleware (like CORS/security headers) from experiencing asynchronous drift
96
- */
97
- compile() {
98
- if (this._compiled) return this._compiled;
99
- const middlewares = this._middlewares;
146
+ _compileChain() {
147
+ if (this._compiledChain) return this._compiledChain;
148
+
149
+ const middlewares = this.middlewares;
100
150
  const len = middlewares.length;
101
-
102
- this._compiled = async function executePipeline(context) {
151
+
152
+ if (len === 0) {
153
+ this._compiledChain = async (ctx) => {};
154
+ return this._compiledChain;
155
+ }
156
+
157
+ this._compiledChain = async function execute(ctx) {
103
158
  async function dispatch(i) {
104
- // 1. Safety boundary check: If context is terminated or connection is disconnected, return directly
105
- if (
106
- context.isTerminated() ||
107
- (context._response && context._response.writableEnded)
108
- ) {
109
- if (context._finalize) context._finalize();
110
- return;
111
- }
112
-
113
- // 2. Pipeline end: Safely trigger final _finalize
159
+ if (ctx._terminated || (ctx.res && ctx.res.writableEnded)) return;
114
160
  if (i >= len) {
115
- if (context._finalize) context._finalize();
161
+ if (typeof ctx._finalize === 'function') ctx._finalize();
116
162
  return;
117
163
  }
118
-
164
+
119
165
  const mw = middlewares[i];
120
-
121
- // 3. Strictly bind current middleware with next subsequent chain's asynchronous timing
122
- await mw(context, function next() {
166
+ await mw(ctx, function next() {
123
167
  return dispatch(i + 1);
124
168
  });
125
169
  }
126
-
127
- // 🚀 Start the first middleware and strictly wait for the entire chain lifecycle to end
170
+
128
171
  await dispatch(0);
129
172
  };
130
-
131
- return this._compiled;
132
- }
133
-
134
- _compileSync() {
135
- if (this._compiledSync) return this._compiledSync;
136
- const middlewares = this._middlewares;
137
- const len = middlewares.length;
138
-
139
- const allSync = middlewares.every((mw) => {
140
- const funcStr = mw.toString();
141
- return (
142
- !funcStr.includes("async ") &&
143
- !funcStr.includes(".then") &&
144
- !funcStr.includes("await ")
145
- );
146
- });
147
-
148
- if (!allSync) return null;
149
-
150
- this._compiledSync = function executePipelineSync(context) {
151
- for (let i = 0; i < len; i++) {
152
- middlewares[i](context, () => {});
153
- if (context.isTerminated() || context.res?.writableEnded) return;
154
- }
155
- if (context._finalize) context._finalize();
156
- };
157
-
158
- return this._compiledSync;
173
+
174
+ return this._compiledChain;
159
175
  }
160
176
 
161
177
  async handle(request, response) {
162
- this._stats.totalRequests++;
163
178
  const url = request.url;
164
179
  const method = request.method;
165
180
 
166
- // 1. Root high-speed channel (with fallback CORS headers)
167
- if (method === "GET" && url === "/") {
168
- const socket = response.socket;
169
- if (socket) socket.cork();
170
- response.writeHead(200, [
171
- "Content-Type",
172
- "application/json; charset=utf-8",
173
- "Content-Length",
174
- "15",
175
- "Connection",
176
- "keep-alive",
177
- "Access-Control-Allow-Origin",
178
- "*",
179
- ]);
180
- response.end(STATIC_RESPONSES.get(200));
181
- if (socket) socket.uncork();
182
- return { cacheHit: true, static: true };
183
- }
184
-
185
- // 2. Cache route hit
186
- let methodCache = this._cache.get(method);
187
- if (!methodCache) {
188
- methodCache = new Map();
189
- this._cache.set(method, methodCache);
190
- }
191
- const cached = methodCache.get(url);
192
-
193
- if (cached) {
194
- if (this.enableMetrics) this._stats.cacheHits++;
195
- const socket = response.socket;
196
- if (socket) socket.cork();
197
- response.writeHead(cached.status, cached.headers);
198
- response.end(cached.buffer);
199
- if (socket) socket.uncork();
200
- return { cacheHit: true };
181
+ // 1. [V8-OPT] Ultra-fast cache check without statistics
182
+ if (method === 'GET') {
183
+ const cached = this.cache.get(url);
184
+ if (cached) {
185
+ const socket = response.socket;
186
+ if (socket && !socket.destroyed) {
187
+ socket.cork();
188
+ response.writeHead(cached.status, cached.headers);
189
+ response.end(cached.buffer);
190
+ socket.uncork();
191
+ }
192
+ return;
193
+ }
201
194
  }
195
+
202
196
 
203
- if (this.enableMetrics) this._stats.cacheMisses++;
204
-
205
- // 3. Context construction and core scheduling
206
- const context = this._getContext(request, response);
197
+ // 2. Get context from pool
198
+ const ctx = contextPool.get(request, response);
207
199
 
208
200
  try {
209
- //Core Control 2: Prioritize reading cached synchronous pipeline to avoid frequent character scanning causing CPU spikes under high concurrency
210
- const pipelineSync = this._compiledSync || this._compileSync();
211
-
212
- if (pipelineSync) {
213
- pipelineSync(context);
214
- } else {
215
- // Strictly lock asynchronous middleware timing
216
- await this.compile()(context);
201
+ // 3. Execute middleware chain
202
+ const chain = this._compileChain();
203
+ await chain(ctx);
204
+
205
+ // 4. [CRITICAL] Cache GET responses BEFORE checking writableEnded
206
+ if (method === 'GET' && ctx.statusCode === 200 && ctx._body) {
207
+ this._cacheResponse(url, ctx);
217
208
  }
218
209
 
219
- // 4. Dynamically capture all response headers, merge and store in high-speed cache
220
- if (method === "GET" && response && response.statusCode < 400) {
221
- this._setResponseCache(methodCache, url, context, response);
210
+ // 5. Send response if not already sent by the middleware chain
211
+ if (!ctx._terminated && !response.headersSent) {
212
+ this._sendResponse(ctx);
222
213
  }
223
214
 
224
- this._returnContext(context);
225
- return { cacheHit: false };
226
215
  } catch (error) {
227
- if (this.enableMetrics) this._stats.errorCount++;
228
- try {
229
- if (response && !response.headersSent) {
230
- response.writeHead(500, [
231
- "Content-Type",
232
- "application/json; charset=utf-8",
233
- "Access-Control-Allow-Origin",
234
- "*",
235
- ]);
236
- response.end(STATIC_RESPONSES.get(500));
237
- }
238
- } catch (e) {}
239
- if (context) this._returnContext(context);
240
- throw error;
216
+ if (!response.headersSent) {
217
+ try {
218
+ const socket = response.socket;
219
+ if (socket && !socket.destroyed) {
220
+ socket.cork();
221
+ response.writeHead(500, [
222
+ "Content-Type", "application/json; charset=utf-8",
223
+ "Content-Length", String(STATIC_RESPONSES.get(500).length),
224
+ "Connection", "keep-alive"
225
+ ]);
226
+ response.end(STATIC_RESPONSES.get(500));
227
+ socket.uncork();
228
+ }
229
+ } catch (e) {}
230
+ }
231
+ } finally {
232
+ contextPool.release(ctx);
241
233
  }
242
234
  }
243
235
 
244
- /**
245
- *Core Control 3: Ultimate comprehensive defense extraction algorithm —— Intercept multi-source Headers without blind spots
246
- */
247
- _setResponseCache(methodCache, url, context, response) {
248
- // Establish a standard flat array, native two-element storage format is most efficient
236
+ _sendResponse(ctx) {
237
+ const response = ctx.res;
238
+ if (!response || response.headersSent) return;
239
+
240
+ const statusCode = ctx.statusCode || 200;
241
+ let body = ctx._body;
242
+
249
243
  const rawHeaders = ["Connection", "keep-alive"];
250
244
  const lowerKeys = new Set(["connection"]);
251
245
 
252
- // ==========================================
253
- // Strategy A: Forcefully synchronously extract AetherContext's high-performance private storage
254
- // ==========================================
255
- if (context && context._headersCount > 0) {
256
- for (let i = 0; i < context._headersCount; i++) {
257
- const key = context._headersKeys[i];
258
- const val = context._headersObj[key];
259
- if (val !== undefined && key) {
260
- const kLower = key.toLowerCase();
261
- if (!lowerKeys.has(kLower)) {
262
- rawHeaders.push(key, String(val));
263
- lowerKeys.add(kLower);
264
- }
246
+ if (ctx._headersCount > 0) {
247
+ for (let i = 0; i < ctx._headersCount; i++) {
248
+ const key = ctx._headersKeys[i];
249
+ const val = ctx._headersObj[key];
250
+ if (val !== undefined) {
251
+ rawHeaders.push(key, String(val));
252
+ lowerKeys.add(key.toLowerCase());
265
253
  }
266
254
  }
267
- }
268
-
269
- // ==========================================
270
- // Strategy B: Deep scan various non-standard Context properties that may evolve into generic dictionaries
271
- // ==========================================
272
- const potentialDicts = [
273
- context?._headers,
274
- context?.headers,
275
- context?.res?.headers,
276
- ];
277
- for (const dict of potentialDicts) {
278
- if (dict && typeof dict === "object" && !(dict instanceof Set)) {
279
- for (const key in dict) {
280
- if (Object.prototype.hasOwnProperty.call(dict, key)) {
281
- const kLower = key.toLowerCase();
282
- if (!lowerKeys.has(kLower) && dict[key] !== undefined) {
283
- rawHeaders.push(key, String(dict[key]));
284
- lowerKeys.add(kLower);
285
- }
286
- }
255
+ } else if (ctx._headers) {
256
+ for (const key in ctx._headers) {
257
+ const val = ctx._headers[key];
258
+ if (val !== undefined) {
259
+ rawHeaders.push(key, String(val));
260
+ lowerKeys.add(key.toLowerCase());
287
261
  }
288
262
  }
289
263
  }
290
264
 
291
- // ==========================================
292
- // Strategy C: Ultimate gap filling: Synchronously capture Node.js native response's standard response headers (security upgraded version)
293
- // ==========================================
294
- if (response) {
295
- // 1. Intercept standard getHeaders() - Standard entry point for most modern Node.js
296
- if (typeof response.getHeaders === "function") {
297
- const nodeHeaders = response.getHeaders();
298
- if (nodeHeaders) {
299
- for (const key in nodeHeaders) {
300
- const kLower = key.toLowerCase();
301
- if (!lowerKeys.has(kLower)) {
302
- const val = nodeHeaders[key];
303
- rawHeaders.push(
304
- key,
305
- Array.isArray(val) ? val.join(", ") : String(val),
306
- );
307
- lowerKeys.add(kLower);
308
- }
309
- }
265
+ if (!lowerKeys.has("content-type")) {
266
+ rawHeaders.push("Content-Type", "application/json; charset=utf-8");
267
+ }
268
+
269
+ if (body !== undefined && body !== null) {
270
+ const bodyStr = typeof body === 'string' ? body :
271
+ Buffer.isBuffer(body) ? body :
272
+ JSON.stringify(body);
273
+
274
+ const bodyBuffer = Buffer.isBuffer(bodyStr) ? bodyStr : Buffer.from(bodyStr);
275
+ rawHeaders.push("Content-Length", String(bodyBuffer.length));
276
+
277
+ const socket = response.socket;
278
+ if (socket) socket.cork();
279
+ response.writeHead(statusCode, rawHeaders);
280
+ response.end(bodyBuffer);
281
+ if (socket) socket.uncork();
282
+ } else {
283
+ rawHeaders.push("Content-Length", "0");
284
+ const socket = response.socket;
285
+ if (socket) socket.cork();
286
+ response.writeHead(statusCode, rawHeaders);
287
+ response.end();
288
+ if (socket) socket.uncork();
289
+ }
290
+
291
+ ctx._terminated = true;
292
+ }
293
+
294
+ _cacheResponse(url, ctx) {
295
+ let bodyBuffer;
296
+ const body = ctx._body;
297
+
298
+ if (Buffer.isBuffer(body)) {
299
+ bodyBuffer = body;
300
+ } else if (typeof body === 'string') {
301
+ bodyBuffer = Buffer.from(body);
302
+ } else {
303
+ bodyBuffer = Buffer.from(JSON.stringify(body));
304
+ }
305
+
306
+ const rawHeaders = ["Connection", "keep-alive"];
307
+ const lowerKeys = new Set(["connection"]);
308
+
309
+ if (ctx._headersCount > 0) {
310
+ for (let i = 0; i < ctx._headersCount; i++) {
311
+ const key = ctx._headersKeys[i];
312
+ const val = ctx._headersObj[key];
313
+ if (val !== undefined) {
314
+ rawHeaders.push(key, String(val));
315
+ lowerKeys.add(key.toLowerCase());
310
316
  }
311
317
  }
312
-
313
- // 2. Intercept standard getHeaderNames() - As standard supplement for HTTP/2 or special streaming protocols
314
- if (typeof response.getHeaderNames === "function") {
315
- const names = response.getHeaderNames();
316
- if (Array.isArray(names)) {
317
- for (const key of names) {
318
- const kLower = key.toLowerCase();
319
- if (!lowerKeys.has(kLower)) {
320
- const val = response.getHeader(key);
321
- if (val !== undefined && val !== null) {
322
- rawHeaders.push(
323
- key,
324
- Array.isArray(val) ? val.join(", ") : String(val),
325
- );
326
- lowerKeys.add(kLower);
327
- }
328
- }
329
- }
318
+ } else if (ctx._headers) {
319
+ for (const key in ctx._headers) {
320
+ const val = ctx._headers[key];
321
+ if (val !== undefined) {
322
+ rawHeaders.push(key, String(val));
323
+ lowerKeys.add(key.toLowerCase());
330
324
  }
331
325
  }
332
-
333
- // 🟢 Removed response._headers detection code that would cause high-version Node.js crashes and deprecation warnings
334
326
  }
335
327
 
336
- // ==========================================
337
- // Strategy D: Extract Body and convert to high-speed persistent Buffer
338
- // ==========================================
339
- let body = context._body || "";
340
- const buffer = Buffer.isBuffer(body)
341
- ? body
342
- : Buffer.from(typeof body === "string" ? body : JSON.stringify(body));
343
-
344
- // Complete length header
345
- if (!lowerKeys.has("content-length")) {
346
- rawHeaders.push("Content-Length", String(buffer.length));
328
+ if (!lowerKeys.has("content-type")) {
329
+ rawHeaders.push("Content-Type", "application/json; charset=utf-8");
347
330
  }
331
+ rawHeaders.push("Content-Length", String(bodyBuffer.length));
348
332
 
349
- methodCache.set(url, {
333
+ this.cache.set(url, {
350
334
  headers: rawHeaders,
351
- status: context.statusCode || response.statusCode || 200,
352
- buffer,
353
- timestamp: Date.now(),
335
+ status: ctx.statusCode || 200,
336
+ buffer: bodyBuffer
354
337
  });
355
-
356
- if (methodCache.size > this._cacheMaxSize) {
357
- const firstKey = methodCache.keys().next().value;
358
- methodCache.delete(firstKey);
359
- }
360
338
  }
361
339
 
362
- getStats() {
363
- return { ...this._stats, poolSize: CONTEXT_POOL.length };
364
- }
340
+
341
+
365
342
  clearCache() {
366
- this._cache.clear();
343
+ this.cache.clear();
367
344
  }
368
- precompile() {
369
- this.compile();
370
- this._compileSync();
371
- return this;
345
+
346
+ useRouter(router) {
347
+ return this.use(router.middleware());
372
348
  }
373
349
  }
374
350