vladx 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/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle — Сборщик модулей
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
6
|
+
import { dirname, join, relative } from 'path';
|
|
7
|
+
|
|
8
|
+
export class Bundle {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.entry = options.entry;
|
|
11
|
+
this.output = options.output || 'bundle.vx';
|
|
12
|
+
this.format = options.format || 'esm'; // esm, cjs, iife, umd
|
|
13
|
+
this.minify = options.minify || false;
|
|
14
|
+
this.sourceMap = options.sourceMap || false;
|
|
15
|
+
this.moduleSystem = options.moduleSystem;
|
|
16
|
+
this.analyzed = new Map();
|
|
17
|
+
this.dependencyGraph = new Map();
|
|
18
|
+
this.importStatements = new Set();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Собрать модули в один файл
|
|
23
|
+
*/
|
|
24
|
+
async build() {
|
|
25
|
+
if (!this.entry) {
|
|
26
|
+
throw new Error('Entry point is required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const modules = await this.analyzeDependencies(this.entry);
|
|
30
|
+
const sorted = this.topologicalSort(modules);
|
|
31
|
+
const bundled = this.generateBundle(sorted);
|
|
32
|
+
|
|
33
|
+
if (this.sourceMap) {
|
|
34
|
+
const map = this.generateSourceMap(sorted);
|
|
35
|
+
bundled.sourceMap = map;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return bundled;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Анализировать зависимости
|
|
43
|
+
*/
|
|
44
|
+
async analyzeDependencies(entryPath) {
|
|
45
|
+
const modules = new Map();
|
|
46
|
+
|
|
47
|
+
const analyze = async (filePath, parentPath = null) => {
|
|
48
|
+
if (modules.has(filePath)) {
|
|
49
|
+
return modules.get(filePath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const source = readFileSync(filePath, 'utf-8');
|
|
53
|
+
const module = {
|
|
54
|
+
path: filePath,
|
|
55
|
+
source,
|
|
56
|
+
imports: [],
|
|
57
|
+
exports: [],
|
|
58
|
+
dependencies: new Set()
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
modules.set(filePath, module);
|
|
62
|
+
|
|
63
|
+
// Найти import и export statements
|
|
64
|
+
const importRegex = /(?:импорт|import)\s+([^;]+);/g;
|
|
65
|
+
const exportRegex = /(?:экспорт|export)\s+(?:поумолчанию|default)?\s+([^;]+);/g;
|
|
66
|
+
|
|
67
|
+
let match;
|
|
68
|
+
while ((match = importRegex.exec(source)) !== null) {
|
|
69
|
+
const importSpec = match[1].trim();
|
|
70
|
+
const depPath = this.extractImportPath(importSpec);
|
|
71
|
+
|
|
72
|
+
if (depPath) {
|
|
73
|
+
const resolvedPath = await this.resolvePath(depPath, filePath);
|
|
74
|
+
module.imports.push({
|
|
75
|
+
spec: importSpec,
|
|
76
|
+
path: depPath,
|
|
77
|
+
resolvedPath
|
|
78
|
+
});
|
|
79
|
+
module.dependencies.add(resolvedPath);
|
|
80
|
+
|
|
81
|
+
// Рекурсивно анализируем зависимости
|
|
82
|
+
if (!modules.has(resolvedPath)) {
|
|
83
|
+
await analyze(resolvedPath, filePath);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
while ((match = exportRegex.exec(source)) !== null) {
|
|
89
|
+
module.exports.push(match[1].trim());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return module;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
await analyze(entryPath);
|
|
96
|
+
return modules;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Извлечь путь из import statement
|
|
101
|
+
*/
|
|
102
|
+
extractImportPath(importSpec) {
|
|
103
|
+
const fromMatch = importSpec.match(/(?:из|from)\s+['"]([^'"]+)['"]/);
|
|
104
|
+
return fromMatch ? fromMatch[1] : null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Разрешить путь модуля
|
|
109
|
+
*/
|
|
110
|
+
async resolvePath(importPath, currentPath) {
|
|
111
|
+
if (importPath.startsWith('./') || importPath.startsWith('../')) {
|
|
112
|
+
const resolved = join(dirname(currentPath), importPath);
|
|
113
|
+
|
|
114
|
+
// Проверяем расширения
|
|
115
|
+
const extensions = ['.vx', '.js'];
|
|
116
|
+
for (const ext of extensions) {
|
|
117
|
+
if (existsSync(resolved + ext)) {
|
|
118
|
+
return resolved + ext;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Проверяем index.vx
|
|
123
|
+
const indexFile = join(resolved, 'index.vx');
|
|
124
|
+
if (existsSync(indexFile)) {
|
|
125
|
+
return indexFile;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return resolved;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Для пакетов из node_modules
|
|
132
|
+
const nodeModulesPath = join(dirname(currentPath), 'node_modules', importPath);
|
|
133
|
+
if (existsSync(nodeModulesPath + '.vx')) {
|
|
134
|
+
return nodeModulesPath + '.vx';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
throw new Error(`Не удалось разрешить модуль: ${importPath}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Топологическая сортировка зависимостей
|
|
142
|
+
*/
|
|
143
|
+
topologicalSort(modules) {
|
|
144
|
+
const visited = new Set();
|
|
145
|
+
const result = [];
|
|
146
|
+
const visiting = new Set();
|
|
147
|
+
|
|
148
|
+
const visit = (module) => {
|
|
149
|
+
if (visiting.has(module.path)) {
|
|
150
|
+
throw new Error(`Циклическая зависимость: ${module.path}`);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (visited.has(module.path)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
visiting.add(module.path);
|
|
158
|
+
visited.add(module.path);
|
|
159
|
+
|
|
160
|
+
for (const dep of module.dependencies) {
|
|
161
|
+
const depModule = modules.get(dep);
|
|
162
|
+
if (depModule) {
|
|
163
|
+
visit(depModule);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
visiting.delete(module.path);
|
|
168
|
+
result.push(module);
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
for (const module of modules.values()) {
|
|
172
|
+
if (!visited.has(module.path)) {
|
|
173
|
+
visit(module);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return result.reverse();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Сгенерировать бандл
|
|
182
|
+
*/
|
|
183
|
+
generateBundle(modules) {
|
|
184
|
+
let output = '';
|
|
185
|
+
const outputDir = dirname(this.output);
|
|
186
|
+
|
|
187
|
+
switch (this.format) {
|
|
188
|
+
case 'esm':
|
|
189
|
+
output = this.generateESM(modules, outputDir);
|
|
190
|
+
break;
|
|
191
|
+
case 'cjs':
|
|
192
|
+
output = this.generateCJS(modules, outputDir);
|
|
193
|
+
break;
|
|
194
|
+
case 'iife':
|
|
195
|
+
output = this.generateIIFE(modules, outputDir);
|
|
196
|
+
break;
|
|
197
|
+
case 'umd':
|
|
198
|
+
output = this.generateUMD(modules, outputDir);
|
|
199
|
+
break;
|
|
200
|
+
default:
|
|
201
|
+
throw new Error(`Неподдерживаемый формат: ${this.format}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (this.minify) {
|
|
205
|
+
output = this.minify(output);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
code: output,
|
|
210
|
+
modules: modules.size,
|
|
211
|
+
format: this.format
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Сгенерировать ESM формат
|
|
217
|
+
*/
|
|
218
|
+
generateESM(modules, outputDir) {
|
|
219
|
+
let output = '';
|
|
220
|
+
|
|
221
|
+
// Все модули в одном файле с комментариями
|
|
222
|
+
output += '// VladX Bundle\n';
|
|
223
|
+
output += '// Generated by VladX Bundler\n\n';
|
|
224
|
+
|
|
225
|
+
for (const module of modules) {
|
|
226
|
+
const relativePath = relative(outputDir, module.path);
|
|
227
|
+
output += `// Module: ${relativePath}\n`;
|
|
228
|
+
output += this.rewriteImports(module, modules, outputDir);
|
|
229
|
+
output += '\n';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return output;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Сгенерировать CJS формат
|
|
237
|
+
*/
|
|
238
|
+
generateCJS(modules, outputDir) {
|
|
239
|
+
let output = '';
|
|
240
|
+
const moduleMap = new Map();
|
|
241
|
+
let index = 0;
|
|
242
|
+
|
|
243
|
+
// Создаем map модулей
|
|
244
|
+
for (const module of modules) {
|
|
245
|
+
const id = `module_${index++}`;
|
|
246
|
+
moduleMap.set(module.path, id);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// IIFE обертка
|
|
250
|
+
output += '(function() {\n';
|
|
251
|
+
output += ' const modules = {};\n\n';
|
|
252
|
+
|
|
253
|
+
// Определяем модули
|
|
254
|
+
for (const module of modules) {
|
|
255
|
+
const id = moduleMap.get(module.path);
|
|
256
|
+
const rewritten = this.rewriteImportsCJS(module, moduleMap, outputDir);
|
|
257
|
+
output += ` ${id} = function(module, exports) {\n`;
|
|
258
|
+
output += ` ${rewritten}\n`;
|
|
259
|
+
output += ` };\n\n`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Инициализируем модули
|
|
263
|
+
for (const module of modules) {
|
|
264
|
+
const id = moduleMap.get(module.path);
|
|
265
|
+
output += ` ${id}(${id}, ${id}.exports = {});\n`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
output += '})();\n';
|
|
269
|
+
|
|
270
|
+
return output;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Сгенерировать IIFE формат
|
|
275
|
+
*/
|
|
276
|
+
generateIIFE(modules, outputDir) {
|
|
277
|
+
let output = '';
|
|
278
|
+
|
|
279
|
+
output += '(function() {\n';
|
|
280
|
+
output += ' "use strict";\n\n';
|
|
281
|
+
|
|
282
|
+
for (const module of modules) {
|
|
283
|
+
const rewritten = this.rewriteImports(module, modules, outputDir);
|
|
284
|
+
output += ` ${rewrapped}\n\n`;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
output += '})();\n';
|
|
288
|
+
|
|
289
|
+
return output;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Сгенерировать UMD формат
|
|
294
|
+
*/
|
|
295
|
+
generateUMD(modules, outputDir) {
|
|
296
|
+
let output = '';
|
|
297
|
+
|
|
298
|
+
output += '(function(root, factory) {\n';
|
|
299
|
+
output += ' if (typeof define === "function" && define.amd) {\n';
|
|
300
|
+
output += ' define([], factory);\n';
|
|
301
|
+
output += ' } else if (typeof module === "object" && module.exports) {\n';
|
|
302
|
+
output += ' module.exports = factory();\n';
|
|
303
|
+
output += ' } else {\n';
|
|
304
|
+
output += ' root.VladX = factory();\n';
|
|
305
|
+
output += ' }\n';
|
|
306
|
+
output += '})(this, function() {\n\n';
|
|
307
|
+
|
|
308
|
+
output += this.generateCJS(modules, outputDir);
|
|
309
|
+
|
|
310
|
+
output += '\n});\n';
|
|
311
|
+
|
|
312
|
+
return output;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Переписать импорты для ESM
|
|
317
|
+
*/
|
|
318
|
+
rewriteImports(module, modules, outputDir) {
|
|
319
|
+
let source = module.source;
|
|
320
|
+
|
|
321
|
+
for (const imp of module.imports) {
|
|
322
|
+
if (modules.has(imp.resolvedPath)) {
|
|
323
|
+
// Internal module - replace path
|
|
324
|
+
const relativePath = relative(outputDir, imp.resolvedPath);
|
|
325
|
+
source = source.replace(imp.spec, imp.spec.replace(imp.path, relativePath));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return source;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Переписать импорты для CJS
|
|
334
|
+
*/
|
|
335
|
+
rewriteImportsCJS(module, moduleMap, outputDir) {
|
|
336
|
+
let source = module.source;
|
|
337
|
+
|
|
338
|
+
for (const imp of module.imports) {
|
|
339
|
+
if (moduleMap.has(imp.resolvedPath)) {
|
|
340
|
+
const id = moduleMap.get(imp.resolvedPath);
|
|
341
|
+
source = source.replace(imp.spec, id);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return source;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Минификация
|
|
350
|
+
*/
|
|
351
|
+
minify(code) {
|
|
352
|
+
let minified = code;
|
|
353
|
+
|
|
354
|
+
// Удаляем комментарии
|
|
355
|
+
minified = minified.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
356
|
+
minified = minified.replace(/\/\/.*$/gm, '');
|
|
357
|
+
|
|
358
|
+
// Удаляем лишние пробелы и переводы строк
|
|
359
|
+
minified = minified.replace(/\s+/g, ' ');
|
|
360
|
+
minified = minified.trim();
|
|
361
|
+
|
|
362
|
+
// Удаляем пробелы вокруг операторов
|
|
363
|
+
minified = minified.replace(/\s*([{};:,=+*/%&|^~!?<>])\s*/g, '$1');
|
|
364
|
+
|
|
365
|
+
return minified;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Сгенерировать source map
|
|
370
|
+
*/
|
|
371
|
+
generateSourceMap(modules) {
|
|
372
|
+
const mappings = [];
|
|
373
|
+
|
|
374
|
+
let generatedLine = 1;
|
|
375
|
+
let generatedColumn = 0;
|
|
376
|
+
|
|
377
|
+
for (const module of modules) {
|
|
378
|
+
const lines = module.source.split('\n');
|
|
379
|
+
|
|
380
|
+
for (let i = 0; i < lines.length; i++) {
|
|
381
|
+
mappings.push({
|
|
382
|
+
generated: {
|
|
383
|
+
line: generatedLine,
|
|
384
|
+
column: generatedColumn
|
|
385
|
+
},
|
|
386
|
+
original: {
|
|
387
|
+
line: i + 1,
|
|
388
|
+
column: 0
|
|
389
|
+
},
|
|
390
|
+
source: module.path,
|
|
391
|
+
name: null
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
generatedLine++;
|
|
395
|
+
generatedColumn = 0;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
version: 3,
|
|
401
|
+
mappings,
|
|
402
|
+
sources: Array.from(modules.values()).map(m => m.path),
|
|
403
|
+
names: []
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Записать бандл в файл
|
|
409
|
+
*/
|
|
410
|
+
async write(bundled) {
|
|
411
|
+
writeFileSync(this.output, bundled.code, 'utf-8');
|
|
412
|
+
|
|
413
|
+
if (this.sourceMap && bundled.sourceMap) {
|
|
414
|
+
const mapPath = this.output + '.map';
|
|
415
|
+
writeFileSync(mapPath, JSON.stringify(bundled.sourceMap, null, 2), 'utf-8');
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return this.output;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export default Bundle;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CacheManager — Управление кэшированием для повышения производительности
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export class CacheManager {
|
|
6
|
+
constructor(options = {}) {
|
|
7
|
+
this.maxSize = options.maxSize || 1000;
|
|
8
|
+
this.ttl = options.ttl || 300000; // 5 минут по умолчанию
|
|
9
|
+
this.cache = new Map();
|
|
10
|
+
this.stats = {
|
|
11
|
+
hits: 0,
|
|
12
|
+
misses: 0,
|
|
13
|
+
evictions: 0
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Получить значение из кэша
|
|
19
|
+
*/
|
|
20
|
+
get(key) {
|
|
21
|
+
const entry = this.cache.get(key);
|
|
22
|
+
|
|
23
|
+
if (!entry) {
|
|
24
|
+
this.stats.misses++;
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Проверка TTL
|
|
29
|
+
if (this.ttl > 0 && Date.now() - entry.timestamp > this.ttl) {
|
|
30
|
+
this.cache.delete(key);
|
|
31
|
+
this.stats.misses++;
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.stats.hits++;
|
|
36
|
+
return entry.value;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Установить значение в кэш
|
|
41
|
+
*/
|
|
42
|
+
set(key, value) {
|
|
43
|
+
// Проверка размера и LRU eviction
|
|
44
|
+
if (this.cache.size >= this.maxSize) {
|
|
45
|
+
this.evictLRU();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.cache.set(key, {
|
|
49
|
+
value,
|
|
50
|
+
timestamp: Date.now(),
|
|
51
|
+
lastAccess: Date.now()
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Удалить из кэша по ключу
|
|
57
|
+
*/
|
|
58
|
+
delete(key) {
|
|
59
|
+
return this.cache.delete(key);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Очистить весь кэш
|
|
64
|
+
*/
|
|
65
|
+
clear() {
|
|
66
|
+
this.cache.clear();
|
|
67
|
+
this.stats = {
|
|
68
|
+
hits: 0,
|
|
69
|
+
misses: 0,
|
|
70
|
+
evictions: 0
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Evict наименее используемый элемент (LRU)
|
|
76
|
+
*/
|
|
77
|
+
evictLRU() {
|
|
78
|
+
let lruKey = null;
|
|
79
|
+
let lruTime = Infinity;
|
|
80
|
+
|
|
81
|
+
for (const [key, entry] of this.cache) {
|
|
82
|
+
if (entry.lastAccess < lruTime) {
|
|
83
|
+
lruTime = entry.lastAccess;
|
|
84
|
+
lruKey = key;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (lruKey) {
|
|
89
|
+
this.cache.delete(lruKey);
|
|
90
|
+
this.stats.evictions++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Получить статистику кэша
|
|
96
|
+
*/
|
|
97
|
+
getStats() {
|
|
98
|
+
const total = this.stats.hits + this.stats.misses;
|
|
99
|
+
return {
|
|
100
|
+
...this.stats,
|
|
101
|
+
hitRate: total > 0 ? (this.stats.hits / total * 100).toFixed(2) + '%' : '0%',
|
|
102
|
+
size: this.cache.size,
|
|
103
|
+
maxSize: this.maxSize
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Очистка устаревших записей
|
|
109
|
+
*/
|
|
110
|
+
prune() {
|
|
111
|
+
if (this.ttl <= 0) return;
|
|
112
|
+
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const keysToDelete = [];
|
|
115
|
+
|
|
116
|
+
for (const [key, entry] of this.cache) {
|
|
117
|
+
if (now - entry.timestamp > this.ttl) {
|
|
118
|
+
keysToDelete.push(key);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
keysToDelete.forEach(key => this.cache.delete(key));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export default CacheManager;
|