aetherframework-middleware 1.0.0
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/.env.example +88 -0
- package/LICENSE +21 -0
- package/README.md +578 -0
- package/examples/advanced-server.js +272 -0
- package/examples/basic-server.js +134 -0
- package/examples/benchmark.js +85 -0
- package/index.js +59 -0
- package/package.json +62 -0
- package/src/core/AetherCompiler.js +118 -0
- package/src/core/AetherContext.js +240 -0
- package/src/core/AetherPipeline.js +371 -0
- package/src/core/AetherStore.js +200 -0
- package/src/middleware/body-parser.js +295 -0
- package/src/middleware/compression.js +243 -0
- package/src/middleware/cors.js +155 -0
- package/src/middleware/json.js +207 -0
- package/src/middleware/jwt.js +222 -0
- package/src/middleware/rate-limit.js +232 -0
- package/src/middleware/security.js +114 -0
- package/src/middleware/session.js +167 -0
- package/src/utils/atomic-ops.js +125 -0
- package/src/utils/env-loader.js +124 -0
- package/src/utils/memory-pool.js +93 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// AetherPipeline.js - Ultra-high performance middleware pipeline (Fixed Headers & High Concurrency)
|
|
2
|
+
|
|
3
|
+
import { EventEmitter } from "events";
|
|
4
|
+
import AetherContext from "./AetherContext.js";
|
|
5
|
+
|
|
6
|
+
const STATIC_RESPONSES = new Map([
|
|
7
|
+
[200, Buffer.from(JSON.stringify({ status: "ok" }))],
|
|
8
|
+
[404, Buffer.from(JSON.stringify({ error: "Not Found" }))],
|
|
9
|
+
[500, Buffer.from(JSON.stringify({ error: "Internal Server Error" }))],
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
const CONTEXT_POOL = [];
|
|
13
|
+
const IN_POOL_CHECK = new Set();
|
|
14
|
+
const CONTEXT_POOL_SIZE = 4096;
|
|
15
|
+
|
|
16
|
+
class AetherPipeline extends EventEmitter {
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this._middlewares = [];
|
|
20
|
+
this._compiled = null;
|
|
21
|
+
this._compiledSync = null;
|
|
22
|
+
this._cache = new Map();
|
|
23
|
+
this._cacheMaxSize = 1000;
|
|
24
|
+
this.enableMetrics = false;
|
|
25
|
+
|
|
26
|
+
this._stats = {
|
|
27
|
+
totalRequests: 0,
|
|
28
|
+
averageLatency: 0,
|
|
29
|
+
errorCount: 0,
|
|
30
|
+
cacheHits: 0,
|
|
31
|
+
cacheMisses: 0,
|
|
32
|
+
poolHits: 0,
|
|
33
|
+
poolMisses: 0,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
this._initObjectPools();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_initObjectPools() {
|
|
40
|
+
for (let i = 0; i < CONTEXT_POOL_SIZE; i++) {
|
|
41
|
+
const ctx = new AetherContext(null, null);
|
|
42
|
+
CONTEXT_POOL.push(ctx);
|
|
43
|
+
IN_POOL_CHECK.add(ctx);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
_getContext(request, response) {
|
|
48
|
+
if (CONTEXT_POOL.length > 0) {
|
|
49
|
+
const context = CONTEXT_POOL.pop();
|
|
50
|
+
IN_POOL_CHECK.delete(context);
|
|
51
|
+
context._reset(request, response);
|
|
52
|
+
if (this.enableMetrics) this._stats.poolHits++;
|
|
53
|
+
return context;
|
|
54
|
+
}
|
|
55
|
+
if (this.enableMetrics) this._stats.poolMisses++;
|
|
56
|
+
return new AetherContext(request, response);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
_returnContext(context) {
|
|
60
|
+
if (!context || IN_POOL_CHECK.has(context)) return;
|
|
61
|
+
|
|
62
|
+
if (CONTEXT_POOL.length < CONTEXT_POOL_SIZE) {
|
|
63
|
+
context.req = null;
|
|
64
|
+
context.res = null;
|
|
65
|
+
context._body = null;
|
|
66
|
+
|
|
67
|
+
// Safely clean up Headers
|
|
68
|
+
if (context._headers && typeof context._headers.clear === "function") {
|
|
69
|
+
context._headers.clear();
|
|
70
|
+
} else {
|
|
71
|
+
context._headers = null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
CONTEXT_POOL.push(context);
|
|
75
|
+
IN_POOL_CHECK.add(context);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
use(middleware) {
|
|
80
|
+
if (typeof middleware !== "function") {
|
|
81
|
+
throw new TypeError("Middleware must be a function");
|
|
82
|
+
}
|
|
83
|
+
this._middlewares.push(middleware);
|
|
84
|
+
this._compiled = null;
|
|
85
|
+
this._compiledSync = null;
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
*Core Control 1: Standard V8-level high-concurrency asynchronous onion model compiler
|
|
91
|
+
* Ensure perfect timing wait chain, absolutely prevent asynchronous middleware (like CORS/security headers) from experiencing asynchronous drift
|
|
92
|
+
*/
|
|
93
|
+
compile() {
|
|
94
|
+
if (this._compiled) return this._compiled;
|
|
95
|
+
const middlewares = this._middlewares;
|
|
96
|
+
const len = middlewares.length;
|
|
97
|
+
|
|
98
|
+
this._compiled = async function executePipeline(context) {
|
|
99
|
+
async function dispatch(i) {
|
|
100
|
+
// 1. Safety boundary check: If context is terminated or connection is disconnected, return directly
|
|
101
|
+
if (
|
|
102
|
+
context.isTerminated() ||
|
|
103
|
+
(context._response && context._response.writableEnded)
|
|
104
|
+
) {
|
|
105
|
+
if (context._finalize) context._finalize();
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// 2. Pipeline end: Safely trigger final _finalize
|
|
110
|
+
if (i >= len) {
|
|
111
|
+
if (context._finalize) context._finalize();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mw = middlewares[i];
|
|
116
|
+
|
|
117
|
+
// 3. Strictly bind current middleware with next subsequent chain's asynchronous timing
|
|
118
|
+
await mw(context, function next() {
|
|
119
|
+
return dispatch(i + 1);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 🚀 Start the first middleware and strictly wait for the entire chain lifecycle to end
|
|
124
|
+
await dispatch(0);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
return this._compiled;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
_compileSync() {
|
|
131
|
+
if (this._compiledSync) return this._compiledSync;
|
|
132
|
+
const middlewares = this._middlewares;
|
|
133
|
+
const len = middlewares.length;
|
|
134
|
+
|
|
135
|
+
const allSync = middlewares.every((mw) => {
|
|
136
|
+
const funcStr = mw.toString();
|
|
137
|
+
return (
|
|
138
|
+
!funcStr.includes("async ") &&
|
|
139
|
+
!funcStr.includes(".then") &&
|
|
140
|
+
!funcStr.includes("await ")
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!allSync) return null;
|
|
145
|
+
|
|
146
|
+
this._compiledSync = function executePipelineSync(context) {
|
|
147
|
+
for (let i = 0; i < len; i++) {
|
|
148
|
+
middlewares[i](context, () => {});
|
|
149
|
+
if (context.isTerminated() || context.res?.writableEnded) return;
|
|
150
|
+
}
|
|
151
|
+
if (context._finalize) context._finalize();
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
return this._compiledSync;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async handle(request, response) {
|
|
158
|
+
this._stats.totalRequests++;
|
|
159
|
+
const url = request.url;
|
|
160
|
+
const method = request.method;
|
|
161
|
+
|
|
162
|
+
// 1. Root high-speed channel (with fallback CORS headers)
|
|
163
|
+
if (method === "GET" && url === "/") {
|
|
164
|
+
const socket = response.socket;
|
|
165
|
+
if (socket) socket.cork();
|
|
166
|
+
response.writeHead(200, [
|
|
167
|
+
"Content-Type",
|
|
168
|
+
"application/json; charset=utf-8",
|
|
169
|
+
"Content-Length",
|
|
170
|
+
"15",
|
|
171
|
+
"Connection",
|
|
172
|
+
"keep-alive",
|
|
173
|
+
"Access-Control-Allow-Origin",
|
|
174
|
+
"*",
|
|
175
|
+
]);
|
|
176
|
+
response.end(STATIC_RESPONSES.get(200));
|
|
177
|
+
if (socket) socket.uncork();
|
|
178
|
+
return { cacheHit: true, static: true };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 2. Cache route hit
|
|
182
|
+
let methodCache = this._cache.get(method);
|
|
183
|
+
if (!methodCache) {
|
|
184
|
+
methodCache = new Map();
|
|
185
|
+
this._cache.set(method, methodCache);
|
|
186
|
+
}
|
|
187
|
+
const cached = methodCache.get(url);
|
|
188
|
+
|
|
189
|
+
if (cached) {
|
|
190
|
+
if (this.enableMetrics) this._stats.cacheHits++;
|
|
191
|
+
const socket = response.socket;
|
|
192
|
+
if (socket) socket.cork();
|
|
193
|
+
response.writeHead(cached.status, cached.headers);
|
|
194
|
+
response.end(cached.buffer);
|
|
195
|
+
if (socket) socket.uncork();
|
|
196
|
+
return { cacheHit: true };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (this.enableMetrics) this._stats.cacheMisses++;
|
|
200
|
+
|
|
201
|
+
// 3. Context construction and core scheduling
|
|
202
|
+
const context = this._getContext(request, response);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
//Core Control 2: Prioritize reading cached synchronous pipeline to avoid frequent character scanning causing CPU spikes under high concurrency
|
|
206
|
+
const pipelineSync = this._compiledSync || this._compileSync();
|
|
207
|
+
|
|
208
|
+
if (pipelineSync) {
|
|
209
|
+
pipelineSync(context);
|
|
210
|
+
} else {
|
|
211
|
+
// Strictly lock asynchronous middleware timing
|
|
212
|
+
await this.compile()(context);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 4. Dynamically capture all response headers, merge and store in high-speed cache
|
|
216
|
+
if (method === "GET" && response && response.statusCode < 400) {
|
|
217
|
+
this._setResponseCache(methodCache, url, context, response);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this._returnContext(context);
|
|
221
|
+
return { cacheHit: false };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (this.enableMetrics) this._stats.errorCount++;
|
|
224
|
+
try {
|
|
225
|
+
if (response && !response.headersSent) {
|
|
226
|
+
response.writeHead(500, [
|
|
227
|
+
"Content-Type",
|
|
228
|
+
"application/json; charset=utf-8",
|
|
229
|
+
"Access-Control-Allow-Origin",
|
|
230
|
+
"*",
|
|
231
|
+
]);
|
|
232
|
+
response.end(STATIC_RESPONSES.get(500));
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {}
|
|
235
|
+
if (context) this._returnContext(context);
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
*Core Control 3: Ultimate comprehensive defense extraction algorithm —— Intercept multi-source Headers without blind spots
|
|
242
|
+
*/
|
|
243
|
+
_setResponseCache(methodCache, url, context, response) {
|
|
244
|
+
// Establish a standard flat array, native two-element storage format is most efficient
|
|
245
|
+
const rawHeaders = ["Connection", "keep-alive"];
|
|
246
|
+
const lowerKeys = new Set(["connection"]);
|
|
247
|
+
|
|
248
|
+
// ==========================================
|
|
249
|
+
// Strategy A: Forcefully synchronously extract AetherContext's high-performance private storage
|
|
250
|
+
// ==========================================
|
|
251
|
+
if (context && context._headersCount > 0) {
|
|
252
|
+
for (let i = 0; i < context._headersCount; i++) {
|
|
253
|
+
const key = context._headersKeys[i];
|
|
254
|
+
const val = context._headersObj[key];
|
|
255
|
+
if (val !== undefined && key) {
|
|
256
|
+
const kLower = key.toLowerCase();
|
|
257
|
+
if (!lowerKeys.has(kLower)) {
|
|
258
|
+
rawHeaders.push(key, String(val));
|
|
259
|
+
lowerKeys.add(kLower);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ==========================================
|
|
266
|
+
// Strategy B: Deep scan various non-standard Context properties that may evolve into generic dictionaries
|
|
267
|
+
// ==========================================
|
|
268
|
+
const potentialDicts = [
|
|
269
|
+
context?._headers,
|
|
270
|
+
context?.headers,
|
|
271
|
+
context?.res?.headers,
|
|
272
|
+
];
|
|
273
|
+
for (const dict of potentialDicts) {
|
|
274
|
+
if (dict && typeof dict === "object" && !(dict instanceof Set)) {
|
|
275
|
+
for (const key in dict) {
|
|
276
|
+
if (Object.prototype.hasOwnProperty.call(dict, key)) {
|
|
277
|
+
const kLower = key.toLowerCase();
|
|
278
|
+
if (!lowerKeys.has(kLower) && dict[key] !== undefined) {
|
|
279
|
+
rawHeaders.push(key, String(dict[key]));
|
|
280
|
+
lowerKeys.add(kLower);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ==========================================
|
|
288
|
+
// Strategy C: Ultimate gap filling: Synchronously capture Node.js native response's standard response headers (security upgraded version)
|
|
289
|
+
// ==========================================
|
|
290
|
+
if (response) {
|
|
291
|
+
// 1. Intercept standard getHeaders() - Standard entry point for most modern Node.js
|
|
292
|
+
if (typeof response.getHeaders === "function") {
|
|
293
|
+
const nodeHeaders = response.getHeaders();
|
|
294
|
+
if (nodeHeaders) {
|
|
295
|
+
for (const key in nodeHeaders) {
|
|
296
|
+
const kLower = key.toLowerCase();
|
|
297
|
+
if (!lowerKeys.has(kLower)) {
|
|
298
|
+
const val = nodeHeaders[key];
|
|
299
|
+
rawHeaders.push(
|
|
300
|
+
key,
|
|
301
|
+
Array.isArray(val) ? val.join(", ") : String(val),
|
|
302
|
+
);
|
|
303
|
+
lowerKeys.add(kLower);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 2. Intercept standard getHeaderNames() - As standard supplement for HTTP/2 or special streaming protocols
|
|
310
|
+
if (typeof response.getHeaderNames === "function") {
|
|
311
|
+
const names = response.getHeaderNames();
|
|
312
|
+
if (Array.isArray(names)) {
|
|
313
|
+
for (const key of names) {
|
|
314
|
+
const kLower = key.toLowerCase();
|
|
315
|
+
if (!lowerKeys.has(kLower)) {
|
|
316
|
+
const val = response.getHeader(key);
|
|
317
|
+
if (val !== undefined && val !== null) {
|
|
318
|
+
rawHeaders.push(
|
|
319
|
+
key,
|
|
320
|
+
Array.isArray(val) ? val.join(", ") : String(val),
|
|
321
|
+
);
|
|
322
|
+
lowerKeys.add(kLower);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// 🟢 Removed response._headers detection code that would cause high-version Node.js crashes and deprecation warnings
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// ==========================================
|
|
333
|
+
// Strategy D: Extract Body and convert to high-speed persistent Buffer
|
|
334
|
+
// ==========================================
|
|
335
|
+
let body = context._body || "";
|
|
336
|
+
const buffer = Buffer.isBuffer(body)
|
|
337
|
+
? body
|
|
338
|
+
: Buffer.from(typeof body === "string" ? body : JSON.stringify(body));
|
|
339
|
+
|
|
340
|
+
// Complete length header
|
|
341
|
+
if (!lowerKeys.has("content-length")) {
|
|
342
|
+
rawHeaders.push("Content-Length", String(buffer.length));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
methodCache.set(url, {
|
|
346
|
+
headers: rawHeaders,
|
|
347
|
+
status: context.statusCode || response.statusCode || 200,
|
|
348
|
+
buffer,
|
|
349
|
+
timestamp: Date.now(),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (methodCache.size > this._cacheMaxSize) {
|
|
353
|
+
const firstKey = methodCache.keys().next().value;
|
|
354
|
+
methodCache.delete(firstKey);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getStats() {
|
|
359
|
+
return { ...this._stats, poolSize: CONTEXT_POOL.length };
|
|
360
|
+
}
|
|
361
|
+
clearCache() {
|
|
362
|
+
this._cache.clear();
|
|
363
|
+
}
|
|
364
|
+
precompile() {
|
|
365
|
+
this.compile();
|
|
366
|
+
this._compileSync();
|
|
367
|
+
return this;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export default AetherPipeline;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// AetherStore.js - High-performance storage engine for AetherJS
|
|
2
|
+
// Supports multiple backends with atomic operations and LRU cache
|
|
3
|
+
|
|
4
|
+
import { EventEmitter } from 'events';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Memory storage backend with LRU cache
|
|
8
|
+
*/
|
|
9
|
+
class MemoryStore extends EventEmitter {
|
|
10
|
+
constructor(options = {}) {
|
|
11
|
+
super();
|
|
12
|
+
this.maxSize = options.maxSize || 10000;
|
|
13
|
+
this.ttl = options.ttl || 3600000; // 1 hour
|
|
14
|
+
this.store = new Map();
|
|
15
|
+
this.lru = []; // List of keys in access order
|
|
16
|
+
this.stats = {
|
|
17
|
+
hits: 0,
|
|
18
|
+
misses: 0,
|
|
19
|
+
sets: 0,
|
|
20
|
+
deletes: 0,
|
|
21
|
+
size: 0
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Start cleanup interval
|
|
25
|
+
this.cleanupInterval = setInterval(() => this._cleanup(), 60000).unref();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async get(key) {
|
|
29
|
+
const entry = this.store.get(key);
|
|
30
|
+
|
|
31
|
+
if (!entry) {
|
|
32
|
+
this.stats.misses++;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check if expired
|
|
37
|
+
if (entry.expires && Date.now() > entry.expires) {
|
|
38
|
+
this.store.delete(key);
|
|
39
|
+
this._removeFromLRU(key);
|
|
40
|
+
this.stats.misses++;
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Update LRU
|
|
45
|
+
this._updateLRU(key);
|
|
46
|
+
this.stats.hits++;
|
|
47
|
+
|
|
48
|
+
return entry.value;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async set(key, value, ttl = this.ttl) {
|
|
52
|
+
// If key exists, update LRU
|
|
53
|
+
if (this.store.has(key)) {
|
|
54
|
+
this._updateLRU(key);
|
|
55
|
+
} else {
|
|
56
|
+
// Check capacity
|
|
57
|
+
if (this.store.size >= this.maxSize) {
|
|
58
|
+
this._evict();
|
|
59
|
+
}
|
|
60
|
+
this.lru.push(key);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const expires = ttl ? Date.now() + ttl : null;
|
|
64
|
+
|
|
65
|
+
this.store.set(key, {
|
|
66
|
+
value,
|
|
67
|
+
expires,
|
|
68
|
+
createdAt: Date.now(),
|
|
69
|
+
accessedAt: Date.now()
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.stats.sets++;
|
|
73
|
+
this.stats.size = this.store.size;
|
|
74
|
+
|
|
75
|
+
this.emit('set', { key, value });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async delete(key) {
|
|
79
|
+
const deleted = this.store.delete(key);
|
|
80
|
+
if (deleted) {
|
|
81
|
+
this._removeFromLRU(key);
|
|
82
|
+
this.stats.deletes++;
|
|
83
|
+
this.stats.size = this.store.size;
|
|
84
|
+
this.emit('delete', { key });
|
|
85
|
+
}
|
|
86
|
+
return deleted;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async clear() {
|
|
90
|
+
this.store.clear();
|
|
91
|
+
this.lru = [];
|
|
92
|
+
this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0, size: 0 };
|
|
93
|
+
this.emit('clear');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async has(key) {
|
|
97
|
+
const entry = this.store.get(key);
|
|
98
|
+
if (!entry) return false;
|
|
99
|
+
|
|
100
|
+
if (entry.expires && Date.now() > entry.expires) {
|
|
101
|
+
this.store.delete(key);
|
|
102
|
+
this._removeFromLRU(key);
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this._updateLRU(key);
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async keys() {
|
|
111
|
+
return Array.from(this.store.keys());
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async size() {
|
|
115
|
+
return this.store.size;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update LRU order
|
|
120
|
+
* @param {string} key
|
|
121
|
+
*/
|
|
122
|
+
_updateLRU(key) {
|
|
123
|
+
const index = this.lru.indexOf(key);
|
|
124
|
+
if (index > -1) {
|
|
125
|
+
this.lru.splice(index, 1);
|
|
126
|
+
}
|
|
127
|
+
this.lru.push(key);
|
|
128
|
+
|
|
129
|
+
// Update accessed time
|
|
130
|
+
const entry = this.store.get(key);
|
|
131
|
+
if (entry) {
|
|
132
|
+
entry.accessedAt = Date.now();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Remove key from LRU list
|
|
138
|
+
* @param {string} key
|
|
139
|
+
*/
|
|
140
|
+
_removeFromLRU(key) {
|
|
141
|
+
const index = this.lru.indexOf(key);
|
|
142
|
+
if (index > -1) {
|
|
143
|
+
this.lru.splice(index, 1);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Evict least recently used item
|
|
149
|
+
*/
|
|
150
|
+
_evict() {
|
|
151
|
+
if (this.lru.length === 0) return;
|
|
152
|
+
|
|
153
|
+
const oldestKey = this.lru.shift();
|
|
154
|
+
this.store.delete(oldestKey);
|
|
155
|
+
this.stats.size = this.store.size;
|
|
156
|
+
this.emit('evict', { key: oldestKey });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Cleanup expired items
|
|
161
|
+
*/
|
|
162
|
+
_cleanup() {
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
const keysToDelete = [];
|
|
165
|
+
|
|
166
|
+
for (const [key, entry] of this.store.entries()) {
|
|
167
|
+
if (entry.expires && now > entry.expires) {
|
|
168
|
+
keysToDelete.push(key);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const key of keysToDelete) {
|
|
173
|
+
this.store.delete(key);
|
|
174
|
+
this._removeFromLRU(key);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (keysToDelete.length > 0) {
|
|
178
|
+
this.stats.size = this.store.size;
|
|
179
|
+
this.emit('cleanup', { count: keysToDelete.length });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
destroy() {
|
|
184
|
+
clearInterval(this.cleanupInterval);
|
|
185
|
+
this.clear();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Factory function to create store instances
|
|
191
|
+
* @param {Object} options - Store configuration
|
|
192
|
+
* @returns {MemoryStore} - Store instance
|
|
193
|
+
*/
|
|
194
|
+
function createAetherStore(options = {}) {
|
|
195
|
+
// In a full implementation, this would switch between Memory, Redis, etc.
|
|
196
|
+
// For now, we return the high-performance MemoryStore
|
|
197
|
+
return new MemoryStore(options);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export default createAetherStore;
|