@aetherframework/middleware 1.0.2
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 +693 -0
- package/docs/readme/README.md +679 -0
- package/docs/readme/README_zh.md +680 -0
- package/examples/advanced-router-demo.js +119 -0
- package/examples/advanced-server.js +272 -0
- package/examples/basic-server.js +134 -0
- package/examples/benchmark.js +85 -0
- package/examples/router-demo.js +369 -0
- package/index.js +67 -0
- package/package.json +59 -0
- package/src/core/AetherCompiler.js +118 -0
- package/src/core/AetherContext.js +242 -0
- package/src/core/AetherPipeline.js +375 -0
- package/src/core/AetherRouter.js +347 -0
- package/src/core/AetherStore.js +204 -0
- package/src/middleware/body-parser.js +299 -0
- package/src/middleware/compression.js +248 -0
- package/src/middleware/cors.js +162 -0
- package/src/middleware/json.js +214 -0
- package/src/middleware/jwt.js +929 -0
- package/src/middleware/params.js +227 -0
- package/src/middleware/rate-limit.js +167 -0
- package/src/middleware/router.js +36 -0
- package/src/middleware/security.js +116 -0
- package/src/middleware/session.js +167 -0
- package/src/utils/atomic-ops.js +127 -0
- package/src/utils/env-loader.js +128 -0
- package/src/utils/memory-pool.js +93 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/middleware/middleware/session
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import crypto from "crypto";
|
|
9
|
+
|
|
10
|
+
//Ultimate optimization: Use the most primitive, zero object allocation for-loop for single-point cookie lookup, reducing GC overhead to absolute zero
|
|
11
|
+
function getCookieValue(cookieHeader, name) {
|
|
12
|
+
if (!cookieHeader) return undefined;
|
|
13
|
+
const target = name + "=";
|
|
14
|
+
const len = cookieHeader.length;
|
|
15
|
+
let pos = 0;
|
|
16
|
+
while (pos < len) {
|
|
17
|
+
pos = cookieHeader.indexOf(target, pos);
|
|
18
|
+
if (pos === -1) break;
|
|
19
|
+
// Ensure it's an independent cookie name, not a prefix of another
|
|
20
|
+
if (
|
|
21
|
+
pos === 0 ||
|
|
22
|
+
cookieHeader.charCodeAt(pos - 1) === 32 ||
|
|
23
|
+
cookieHeader.charCodeAt(pos - 1) === 59
|
|
24
|
+
) {
|
|
25
|
+
pos += target.length;
|
|
26
|
+
let end = cookieHeader.indexOf(";", pos);
|
|
27
|
+
if (end === -1) end = len;
|
|
28
|
+
return cookieHeader.substring(pos, end).trim();
|
|
29
|
+
}
|
|
30
|
+
pos += 1;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
//Abandon long UUID, use 16-byte high-performance hexadecimal ID, shorten cookie length, reduce network and compression middleware overhead
|
|
36
|
+
const genId = () => crypto.randomBytes(16).toString("hex");
|
|
37
|
+
|
|
38
|
+
class MemoryStore {
|
|
39
|
+
constructor() {
|
|
40
|
+
this.cache = new Map();
|
|
41
|
+
}
|
|
42
|
+
async get(id) {
|
|
43
|
+
const s = this.cache.get(id);
|
|
44
|
+
if (!s) return null;
|
|
45
|
+
if (Date.now() > s.exp) {
|
|
46
|
+
this.cache.delete(id);
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return s.data;
|
|
50
|
+
}
|
|
51
|
+
async set(id, data, ttl) {
|
|
52
|
+
this.cache.set(id, { data, exp: Date.now() + ttl });
|
|
53
|
+
}
|
|
54
|
+
async delete(id) {
|
|
55
|
+
this.cache.delete(id);
|
|
56
|
+
}
|
|
57
|
+
prune() {
|
|
58
|
+
const now = Date.now();
|
|
59
|
+
for (const [k, v] of this.cache) if (now > v.exp) this.cache.delete(k);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export class SessionManager {
|
|
64
|
+
constructor(options = {}) {
|
|
65
|
+
const { store, ...restOptions } = options;
|
|
66
|
+
this.config = {
|
|
67
|
+
enabled: process.env.SESSION_ENABLED !== "false",
|
|
68
|
+
maxAge: parseInt(process.env.SESSION_MAX_AGE) || 86400000,
|
|
69
|
+
cookieName: process.env.SESSION_COOKIE_NAME || "aether_sid",
|
|
70
|
+
...restOptions,
|
|
71
|
+
};
|
|
72
|
+
this.config.store =
|
|
73
|
+
store && typeof store === "object" ? store : new MemoryStore();
|
|
74
|
+
if (this.config.store instanceof MemoryStore) {
|
|
75
|
+
this.cleanup = setInterval(
|
|
76
|
+
() => this.config.store.prune(),
|
|
77
|
+
60000,
|
|
78
|
+
).unref();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
middleware() {
|
|
83
|
+
if (!this.config.enabled)
|
|
84
|
+
return (ctx, next) => next && (next.next ? next.next() : next());
|
|
85
|
+
|
|
86
|
+
const { store, maxAge, cookieName } = this.config;
|
|
87
|
+
const cookieSuffix = `; HttpOnly; Secure; SameSite=Strict; Max-Age=${Math.floor(maxAge / 1000)}; Path=/`;
|
|
88
|
+
|
|
89
|
+
return async (ctx, next) => {
|
|
90
|
+
ctx.state ??= {};
|
|
91
|
+
|
|
92
|
+
const sid = getCookieValue(ctx.getHeader("cookie"), cookieName);
|
|
93
|
+
let sessionData = sid ? await store.get(sid) : null;
|
|
94
|
+
|
|
95
|
+
// 🚀 Strategy adjustment: If not obtained, first give an empty object, never write to storage early, never set cookie early
|
|
96
|
+
let isNew = false;
|
|
97
|
+
if (!sessionData) {
|
|
98
|
+
sessionData = {};
|
|
99
|
+
isNew = true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const sessionState = { id: sid, data: sessionData, dirty: false };
|
|
103
|
+
|
|
104
|
+
ctx.session = {
|
|
105
|
+
get: (key) => sessionState.data[key],
|
|
106
|
+
set: (key, val) => {
|
|
107
|
+
sessionState.data[key] = val;
|
|
108
|
+
sessionState.dirty = true;
|
|
109
|
+
},
|
|
110
|
+
delete: (key) => {
|
|
111
|
+
delete sessionState.data[key];
|
|
112
|
+
sessionState.dirty = true;
|
|
113
|
+
},
|
|
114
|
+
clear: () => {
|
|
115
|
+
sessionState.data = {};
|
|
116
|
+
sessionState.dirty = true;
|
|
117
|
+
},
|
|
118
|
+
destroy: async () => {
|
|
119
|
+
if (sessionState.id) await store.delete(sessionState.id);
|
|
120
|
+
sessionState.id = null;
|
|
121
|
+
sessionState.data = {};
|
|
122
|
+
sessionState.dirty = false;
|
|
123
|
+
ctx.setHeader(
|
|
124
|
+
"Set-Cookie",
|
|
125
|
+
`${cookieName}=; HttpOnly; Secure; SameSite=Strict; Max-Age=0; Path=/`,
|
|
126
|
+
);
|
|
127
|
+
},
|
|
128
|
+
regenerate: async () => {
|
|
129
|
+
if (sessionState.id) await store.delete(sessionState.id);
|
|
130
|
+
sessionState.id = genId();
|
|
131
|
+
await store.set(sessionState.id, sessionState.data, maxAge);
|
|
132
|
+
ctx.setHeader(
|
|
133
|
+
"Set-Cookie",
|
|
134
|
+
`${cookieName}=${sessionState.id}${cookieSuffix}`,
|
|
135
|
+
);
|
|
136
|
+
sessionState.dirty = false;
|
|
137
|
+
isNew = false;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
//Use the most reliable and well-isolated try...finally to ensure pipeline smoothness, while strictly limiting persistence logic to the "actually modified" checkpoint
|
|
142
|
+
try {
|
|
143
|
+
if (next) {
|
|
144
|
+
next.next ? await next.next() : await next();
|
|
145
|
+
}
|
|
146
|
+
} finally {
|
|
147
|
+
if (sessionState.dirty) {
|
|
148
|
+
// If it's a new session and the route has written data, only now generate the ID and issue the cookie
|
|
149
|
+
if (isNew || !sessionState.id) {
|
|
150
|
+
sessionState.id = genId();
|
|
151
|
+
ctx.setHeader(
|
|
152
|
+
"Set-Cookie",
|
|
153
|
+
`${cookieName}=${sessionState.id}${cookieSuffix}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
await store.set(sessionState.id, sessionState.data, maxAge);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
destroy() {
|
|
163
|
+
if (this.cleanup) clearInterval(this.cleanup);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default SessionManager;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/middleware/utils/atomic-ops
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AtomicCounter {
|
|
10
|
+
constructor(initialValue = 0) {
|
|
11
|
+
this.value = initialValue;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Increment counter
|
|
16
|
+
* @param {number} delta - Amount to increment
|
|
17
|
+
* @returns {number} - New value
|
|
18
|
+
*/
|
|
19
|
+
increment(delta = 1) {
|
|
20
|
+
this.value += delta;
|
|
21
|
+
return this.value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Decrement counter
|
|
26
|
+
* @param {number} delta - Amount to decrement
|
|
27
|
+
* @returns {number} - New value
|
|
28
|
+
*/
|
|
29
|
+
decrement(delta = 1) {
|
|
30
|
+
this.value -= delta;
|
|
31
|
+
return this.value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get current value
|
|
36
|
+
* @returns {number} - Current value
|
|
37
|
+
*/
|
|
38
|
+
get() {
|
|
39
|
+
return this.value;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Set value
|
|
44
|
+
* @param {number} value - New value
|
|
45
|
+
*/
|
|
46
|
+
set(value) {
|
|
47
|
+
this.value = value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compare and swap
|
|
52
|
+
* @param {number} expected - Expected value
|
|
53
|
+
* @param {number} newValue - New value
|
|
54
|
+
* @returns {boolean} - Whether swap succeeded
|
|
55
|
+
*/
|
|
56
|
+
compareAndSwap(expected, newValue) {
|
|
57
|
+
if (this.value === expected) {
|
|
58
|
+
this.value = newValue;
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* AtomicFlag - Thread-safe boolean flag
|
|
67
|
+
*/
|
|
68
|
+
class AtomicFlag {
|
|
69
|
+
constructor(initialValue = false) {
|
|
70
|
+
this.value = initialValue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set flag to true
|
|
75
|
+
* @returns {boolean} - Previous value
|
|
76
|
+
*/
|
|
77
|
+
set() {
|
|
78
|
+
const prev = this.value;
|
|
79
|
+
this.value = true;
|
|
80
|
+
return prev;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Set flag to false
|
|
85
|
+
* @returns {boolean} - Previous value
|
|
86
|
+
*/
|
|
87
|
+
clear() {
|
|
88
|
+
const prev = this.value;
|
|
89
|
+
this.value = false;
|
|
90
|
+
return prev;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Toggle flag
|
|
95
|
+
* @returns {boolean} - New value
|
|
96
|
+
*/
|
|
97
|
+
toggle() {
|
|
98
|
+
this.value = !this.value;
|
|
99
|
+
return this.value;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get current value
|
|
104
|
+
* @returns {boolean} - Current value
|
|
105
|
+
*/
|
|
106
|
+
get() {
|
|
107
|
+
return this.value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Factory functions
|
|
113
|
+
*/
|
|
114
|
+
function createAtomicCounter(initialValue = 0) {
|
|
115
|
+
return new AtomicCounter(initialValue);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function createAtomicFlag(initialValue = false) {
|
|
119
|
+
return new AtomicFlag(initialValue);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default {
|
|
123
|
+
createAtomicCounter,
|
|
124
|
+
createAtomicFlag,
|
|
125
|
+
AtomicCounter,
|
|
126
|
+
AtomicFlag
|
|
127
|
+
};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/middleware/utils/evn-loader
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
/**
|
|
11
|
+
* Parse .env file content
|
|
12
|
+
* @param {string} content - .env file content
|
|
13
|
+
* @returns {Object} - Parsed environment variables
|
|
14
|
+
*/
|
|
15
|
+
function parseEnvContent(content) {
|
|
16
|
+
const env = {};
|
|
17
|
+
const lines = content.split('\n');
|
|
18
|
+
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
// Skip comments and empty lines
|
|
21
|
+
const trimmedLine = line.trim();
|
|
22
|
+
if (!trimmedLine || trimmedLine.startsWith('#')) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Split key=value
|
|
27
|
+
const equalIndex = trimmedLine.indexOf('=');
|
|
28
|
+
if (equalIndex === -1) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const key = trimmedLine.substring(0, equalIndex).trim();
|
|
33
|
+
let value = trimmedLine.substring(equalIndex + 1).trim();
|
|
34
|
+
|
|
35
|
+
// Remove quotes if present
|
|
36
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
37
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
38
|
+
value = value.substring(1, value.length - 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Set environment variable
|
|
42
|
+
env[key] = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return env;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load environment variables from .env file
|
|
50
|
+
* @param {string} envPath - Path to .env file
|
|
51
|
+
* @param {boolean} override - Whether to override existing env vars
|
|
52
|
+
* @returns {Object} - Loaded environment variables
|
|
53
|
+
*/
|
|
54
|
+
function loadEnv(envPath = '.env', override = false) {
|
|
55
|
+
const absolutePath = path.resolve(process.cwd(), envPath);
|
|
56
|
+
|
|
57
|
+
if (!fs.existsSync(absolutePath)) {
|
|
58
|
+
console.warn(`AetherJS: .env file not found at ${absolutePath}`);
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const content = fs.readFileSync(absolutePath, 'utf8');
|
|
64
|
+
const env = parseEnvContent(content);
|
|
65
|
+
|
|
66
|
+
// Set environment variables
|
|
67
|
+
for (const [key, value] of Object.entries(env)) {
|
|
68
|
+
if (override || process.env[key] === undefined) {
|
|
69
|
+
process.env[key] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return env;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`AetherJS: Error loading .env file: ${error.message}`);
|
|
76
|
+
return {};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Watch .env file for changes and reload
|
|
82
|
+
* @param {string} envPath - Path to .env file
|
|
83
|
+
* @param {Function} callback - Callback when env changes
|
|
84
|
+
* @returns {Object} - Watcher object with close method
|
|
85
|
+
*/
|
|
86
|
+
function watchEnv(envPath = '.env', callback) {
|
|
87
|
+
const absolutePath = path.resolve(process.cwd(), envPath);
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(absolutePath)) {
|
|
90
|
+
console.warn(`AetherJS: .env file not found at ${absolutePath}`);
|
|
91
|
+
return { close: () => {} };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let lastContent = fs.readFileSync(absolutePath, 'utf8');
|
|
95
|
+
|
|
96
|
+
const watcher = fs.watch(absolutePath, (eventType) => {
|
|
97
|
+
if (eventType === 'change') {
|
|
98
|
+
try {
|
|
99
|
+
const newContent = fs.readFileSync(absolutePath, 'utf8');
|
|
100
|
+
if (newContent !== lastContent) {
|
|
101
|
+
lastContent = newContent;
|
|
102
|
+
const newEnv = parseEnvContent(newContent);
|
|
103
|
+
|
|
104
|
+
// Update process.env
|
|
105
|
+
for (const [key, value] of Object.entries(newEnv)) {
|
|
106
|
+
process.env[key] = value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (callback) {
|
|
110
|
+
callback(newEnv);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (error) {
|
|
114
|
+
console.error(`AetherJS: Error reloading .env file: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
close: () => watcher.close()
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
export default {
|
|
124
|
+
loadEnv,
|
|
125
|
+
watchEnv,
|
|
126
|
+
parseEnvContent
|
|
127
|
+
};
|
|
128
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license MIT
|
|
3
|
+
* Copyright (c) 2026-present AetherFramework Contributors.
|
|
4
|
+
* SPDX-License-Identifier: MIT
|
|
5
|
+
* @module @aetherframework/middleware/utils/memory-pool
|
|
6
|
+
*/
|
|
7
|
+
class MemoryPool {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.bufferSize = options.bufferSize || 8192; // 8KB default
|
|
10
|
+
this.poolSize = options.poolSize || 100;
|
|
11
|
+
this.buffers = [];
|
|
12
|
+
this.available = [];
|
|
13
|
+
this.stats = {
|
|
14
|
+
allocated: 0,
|
|
15
|
+
reused: 0,
|
|
16
|
+
created: 0
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Initialize pool
|
|
20
|
+
for (let i = 0; i < this.poolSize; i++) {
|
|
21
|
+
const buffer = Buffer.allocUnsafe(this.bufferSize);
|
|
22
|
+
this.buffers.push(buffer);
|
|
23
|
+
this.available.push(i);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get a buffer from the pool
|
|
29
|
+
* @returns {Buffer} - Buffer from pool
|
|
30
|
+
*/
|
|
31
|
+
get() {
|
|
32
|
+
if (this.available.length > 0) {
|
|
33
|
+
const index = this.available.pop();
|
|
34
|
+
this.stats.reused++;
|
|
35
|
+
return this.buffers[index];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Pool exhausted, create new buffer
|
|
39
|
+
const buffer = Buffer.allocUnsafe(this.bufferSize);
|
|
40
|
+
this.buffers.push(buffer);
|
|
41
|
+
this.stats.created++;
|
|
42
|
+
this.stats.allocated++;
|
|
43
|
+
return buffer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Return a buffer to the pool
|
|
48
|
+
* @param {Buffer} buffer - Buffer to return
|
|
49
|
+
*/
|
|
50
|
+
release(buffer) {
|
|
51
|
+
const index = this.buffers.indexOf(buffer);
|
|
52
|
+
if (index !== -1 && !this.available.includes(index)) {
|
|
53
|
+
// Clear buffer content for security
|
|
54
|
+
buffer.fill(0);
|
|
55
|
+
this.available.push(index);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Get pool statistics
|
|
61
|
+
* @returns {Object} - Pool stats
|
|
62
|
+
*/
|
|
63
|
+
getStats() {
|
|
64
|
+
return {
|
|
65
|
+
...this.stats,
|
|
66
|
+
totalBuffers: this.buffers.length,
|
|
67
|
+
availableBuffers: this.available.length,
|
|
68
|
+
utilization: ((this.buffers.length - this.available.length) / this.buffers.length * 100).toFixed(2) + '%'
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Destroy pool and free memory
|
|
74
|
+
*/
|
|
75
|
+
destroy() {
|
|
76
|
+
this.buffers = [];
|
|
77
|
+
this.available = [];
|
|
78
|
+
this.stats = { allocated: 0, reused: 0, created: 0 };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Factory function to create memory pool instances
|
|
84
|
+
* @param {Object} options - Pool configuration
|
|
85
|
+
* @returns {MemoryPool} - Memory pool instance
|
|
86
|
+
*/
|
|
87
|
+
function createMemoryPool(options = {}) {
|
|
88
|
+
return new MemoryPool(options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
export default createMemoryPool;
|