s3db.js 13.6.0 → 14.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/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -137,10 +137,31 @@
|
|
|
137
137
|
import zlib from 'node:zlib';
|
|
138
138
|
import os from 'node:os';
|
|
139
139
|
import { Cache } from "./cache.class.js"
|
|
140
|
+
import { CacheError } from '../cache.errors.js';
|
|
140
141
|
|
|
141
142
|
export class MemoryCache extends Cache {
|
|
142
143
|
constructor(config = {}) {
|
|
143
144
|
super(config);
|
|
145
|
+
this.caseSensitive = config.caseSensitive !== undefined ? config.caseSensitive : true;
|
|
146
|
+
this.serializer = typeof config.serializer === 'function' ? config.serializer : JSON.stringify;
|
|
147
|
+
|
|
148
|
+
// Default deserializer with Date reconstruction
|
|
149
|
+
const defaultDeserializer = (str) => {
|
|
150
|
+
return JSON.parse(str, (key, value) => {
|
|
151
|
+
// Reconstruct Date objects from ISO strings
|
|
152
|
+
if (typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(value)) {
|
|
153
|
+
return new Date(value);
|
|
154
|
+
}
|
|
155
|
+
return value;
|
|
156
|
+
});
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
this.deserializer = typeof config.deserializer === 'function' ? config.deserializer : defaultDeserializer;
|
|
160
|
+
this.enableStats = config.enableStats === true;
|
|
161
|
+
this.evictionPolicy = (config.evictionPolicy || 'fifo').toLowerCase();
|
|
162
|
+
if (!['lru', 'fifo'].includes(this.evictionPolicy)) {
|
|
163
|
+
this.evictionPolicy = 'fifo';
|
|
164
|
+
}
|
|
144
165
|
this.cache = {};
|
|
145
166
|
this.meta = {};
|
|
146
167
|
this.maxSize = config.maxSize !== undefined ? config.maxSize : 1000;
|
|
@@ -148,19 +169,26 @@ export class MemoryCache extends Cache {
|
|
|
148
169
|
// Validate that only one memory limit option is used
|
|
149
170
|
if (config.maxMemoryBytes && config.maxMemoryBytes > 0 &&
|
|
150
171
|
config.maxMemoryPercent && config.maxMemoryPercent > 0) {
|
|
151
|
-
throw new
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
172
|
+
throw new CacheError('[MemoryCache] Cannot use both maxMemoryBytes and maxMemoryPercent', {
|
|
173
|
+
driver: 'memory',
|
|
174
|
+
operation: 'constructor',
|
|
175
|
+
statusCode: 400,
|
|
176
|
+
retriable: false,
|
|
177
|
+
suggestion: 'Choose either maxMemoryBytes or maxMemoryPercent to limit memory usage.'
|
|
178
|
+
});
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
// Calculate maxMemoryBytes from percentage if provided
|
|
158
182
|
if (config.maxMemoryPercent && config.maxMemoryPercent > 0) {
|
|
159
183
|
if (config.maxMemoryPercent > 1) {
|
|
160
|
-
throw new
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
184
|
+
throw new CacheError('[MemoryCache] maxMemoryPercent must be between 0 and 1', {
|
|
185
|
+
driver: 'memory',
|
|
186
|
+
operation: 'constructor',
|
|
187
|
+
statusCode: 400,
|
|
188
|
+
retriable: false,
|
|
189
|
+
suggestion: 'Provide a fraction between 0 and 1 (e.g., 0.1 for 10%).',
|
|
190
|
+
maxMemoryPercent: config.maxMemoryPercent
|
|
191
|
+
});
|
|
164
192
|
}
|
|
165
193
|
|
|
166
194
|
const totalMemory = os.totalmem();
|
|
@@ -188,17 +216,82 @@ export class MemoryCache extends Cache {
|
|
|
188
216
|
// Memory tracking
|
|
189
217
|
this.currentMemoryBytes = 0;
|
|
190
218
|
this.evictedDueToMemory = 0;
|
|
219
|
+
|
|
220
|
+
// Monotonic counter for LRU ordering (prevents timestamp collisions)
|
|
221
|
+
this._accessCounter = 0;
|
|
222
|
+
|
|
223
|
+
this.stats = {
|
|
224
|
+
hits: 0,
|
|
225
|
+
misses: 0,
|
|
226
|
+
sets: 0,
|
|
227
|
+
deletes: 0,
|
|
228
|
+
evictions: 0
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
_normalizeKey(key) {
|
|
233
|
+
return this.caseSensitive ? key : key.toLowerCase();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
_recordStat(type) {
|
|
237
|
+
if (!this.enableStats) return;
|
|
238
|
+
if (Object.prototype.hasOwnProperty.call(this.stats, type)) {
|
|
239
|
+
this.stats[type] += 1;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_selectEvictionCandidate() {
|
|
244
|
+
const entries = Object.entries(this.meta);
|
|
245
|
+
if (entries.length === 0) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
if (this.evictionPolicy === 'lru') {
|
|
250
|
+
// Use accessOrder (monotonic counter) for stable LRU ordering
|
|
251
|
+
entries.sort((a, b) => (a[1].accessOrder ?? a[1].insertOrder ?? 0) - (b[1].accessOrder ?? b[1].insertOrder ?? 0));
|
|
252
|
+
} else {
|
|
253
|
+
// Default to FIFO (order of insertion)
|
|
254
|
+
entries.sort((a, b) => (a[1].insertOrder ?? a[1].createdAt ?? a[1].ts) - (b[1].insertOrder ?? b[1].createdAt ?? b[1].ts));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return entries[0]?.[0] || null;
|
|
191
258
|
}
|
|
192
259
|
|
|
193
260
|
async _set(key, data) {
|
|
261
|
+
const normalizedKey = this._normalizeKey(key);
|
|
262
|
+
|
|
263
|
+
// Serialize first (needed for both compression and memory limit checks)
|
|
264
|
+
let serialized;
|
|
265
|
+
try {
|
|
266
|
+
serialized = this.serializer(data);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
throw new CacheError(`Failed to serialize data for key '${key}'`, {
|
|
269
|
+
driver: 'memory',
|
|
270
|
+
operation: 'set',
|
|
271
|
+
statusCode: 500,
|
|
272
|
+
retriable: false,
|
|
273
|
+
suggestion: 'Ensure the custom serializer handles the provided data type.',
|
|
274
|
+
key,
|
|
275
|
+
original: error
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
194
279
|
// Prepare data for storage
|
|
195
|
-
let finalData =
|
|
280
|
+
let finalData = serialized;
|
|
196
281
|
let compressed = false;
|
|
197
282
|
let originalSize = 0;
|
|
198
283
|
let compressedSize = 0;
|
|
199
284
|
|
|
200
|
-
|
|
201
|
-
|
|
285
|
+
if (typeof serialized !== 'string') {
|
|
286
|
+
throw new CacheError('MemoryCache serializer must return a string', {
|
|
287
|
+
driver: 'memory',
|
|
288
|
+
operation: 'set',
|
|
289
|
+
statusCode: 500,
|
|
290
|
+
retriable: false,
|
|
291
|
+
suggestion: 'Update the custom serializer to return a string output.'
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
|
|
202
295
|
originalSize = Buffer.byteLength(serialized, 'utf8');
|
|
203
296
|
|
|
204
297
|
// Apply compression if enabled
|
|
@@ -232,105 +325,144 @@ export class MemoryCache extends Cache {
|
|
|
232
325
|
const itemSize = compressed ? compressedSize : originalSize;
|
|
233
326
|
|
|
234
327
|
// If replacing existing key, subtract its old size from current memory
|
|
235
|
-
if (Object.prototype.hasOwnProperty.call(this.cache,
|
|
236
|
-
const oldSize = this.meta[
|
|
328
|
+
if (Object.prototype.hasOwnProperty.call(this.cache, normalizedKey)) {
|
|
329
|
+
const oldSize = this.meta[normalizedKey]?.compressedSize || 0;
|
|
237
330
|
this.currentMemoryBytes -= oldSize;
|
|
238
331
|
}
|
|
239
332
|
|
|
240
333
|
// Memory-aware eviction: Remove items until we have space
|
|
241
334
|
if (this.maxMemoryBytes > 0) {
|
|
335
|
+
// If item is too large to fit even in empty cache, don't cache it
|
|
336
|
+
if (itemSize > this.maxMemoryBytes) {
|
|
337
|
+
this.evictedDueToMemory++;
|
|
338
|
+
return data;
|
|
339
|
+
}
|
|
340
|
+
|
|
242
341
|
while (this.currentMemoryBytes + itemSize > this.maxMemoryBytes && Object.keys(this.cache).length > 0) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
this.evictedDueToMemory++;
|
|
252
|
-
} else {
|
|
253
|
-
break; // No more items to evict
|
|
254
|
-
}
|
|
342
|
+
const candidate = this._selectEvictionCandidate();
|
|
343
|
+
if (!candidate) break;
|
|
344
|
+
const evictedSize = this.meta[candidate]?.compressedSize || 0;
|
|
345
|
+
delete this.cache[candidate];
|
|
346
|
+
delete this.meta[candidate];
|
|
347
|
+
this.currentMemoryBytes -= evictedSize;
|
|
348
|
+
this.evictedDueToMemory++;
|
|
349
|
+
this._recordStat('evictions');
|
|
255
350
|
}
|
|
256
351
|
}
|
|
257
352
|
|
|
258
|
-
// Item count eviction
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
delete this.
|
|
266
|
-
delete this.meta[oldestKey];
|
|
353
|
+
// Item count eviction: only evict if we're about to exceed maxSize
|
|
354
|
+
// Check length before adding the new item (so maxSize=2 allows 2 items, not 1)
|
|
355
|
+
if (this.maxSize > 0 && !Object.prototype.hasOwnProperty.call(this.cache, normalizedKey) && Object.keys(this.cache).length >= this.maxSize) {
|
|
356
|
+
const candidate = this._selectEvictionCandidate();
|
|
357
|
+
if (candidate) {
|
|
358
|
+
const evictedSize = this.meta[candidate]?.compressedSize || 0;
|
|
359
|
+
delete this.cache[candidate];
|
|
360
|
+
delete this.meta[candidate];
|
|
267
361
|
this.currentMemoryBytes -= evictedSize;
|
|
362
|
+
this._recordStat('evictions');
|
|
268
363
|
}
|
|
269
364
|
}
|
|
270
365
|
|
|
271
366
|
// Store the item
|
|
272
|
-
this.cache[
|
|
273
|
-
|
|
274
|
-
|
|
367
|
+
this.cache[normalizedKey] = finalData;
|
|
368
|
+
const timestamp = Date.now();
|
|
369
|
+
const insertOrder = ++this._accessCounter;
|
|
370
|
+
this.meta[normalizedKey] = {
|
|
371
|
+
ts: timestamp,
|
|
372
|
+
createdAt: timestamp,
|
|
373
|
+
lastAccess: timestamp,
|
|
374
|
+
insertOrder,
|
|
375
|
+
accessOrder: insertOrder,
|
|
275
376
|
compressed,
|
|
276
377
|
originalSize,
|
|
277
|
-
compressedSize: itemSize
|
|
378
|
+
compressedSize: itemSize,
|
|
379
|
+
originalKey: key
|
|
278
380
|
};
|
|
279
381
|
|
|
280
382
|
// Update current memory usage
|
|
281
383
|
this.currentMemoryBytes += itemSize;
|
|
282
384
|
|
|
385
|
+
this._recordStat('sets');
|
|
386
|
+
|
|
283
387
|
return data;
|
|
284
388
|
}
|
|
285
389
|
|
|
286
390
|
async _get(key) {
|
|
287
|
-
|
|
391
|
+
const normalizedKey = this._normalizeKey(key);
|
|
392
|
+
|
|
393
|
+
if (!Object.prototype.hasOwnProperty.call(this.cache, normalizedKey)) {
|
|
394
|
+
this._recordStat('misses');
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
288
397
|
|
|
289
398
|
// Check TTL expiration
|
|
290
399
|
if (this.ttl > 0) {
|
|
291
400
|
const now = Date.now();
|
|
292
|
-
const meta = this.meta[
|
|
293
|
-
if (meta && now - meta.ts > this.ttl) {
|
|
401
|
+
const meta = this.meta[normalizedKey];
|
|
402
|
+
if (meta && now - (meta.createdAt ?? meta.ts) > this.ttl) {
|
|
294
403
|
// Expired - decrement memory before deleting
|
|
295
404
|
const itemSize = meta.compressedSize || 0;
|
|
296
405
|
this.currentMemoryBytes -= itemSize;
|
|
297
|
-
delete this.cache[
|
|
298
|
-
delete this.meta[
|
|
406
|
+
delete this.cache[normalizedKey];
|
|
407
|
+
delete this.meta[normalizedKey];
|
|
408
|
+
this._recordStat('misses');
|
|
299
409
|
return null;
|
|
300
410
|
}
|
|
301
411
|
}
|
|
302
412
|
|
|
303
|
-
const rawData = this.cache[
|
|
304
|
-
|
|
413
|
+
const rawData = this.cache[normalizedKey];
|
|
414
|
+
|
|
305
415
|
// Check if data is compressed
|
|
306
416
|
if (rawData && typeof rawData === 'object' && rawData.__compressed) {
|
|
307
417
|
try {
|
|
308
418
|
// Decompress data
|
|
309
419
|
const compressedBuffer = Buffer.from(rawData.__data, 'base64');
|
|
310
420
|
const decompressed = zlib.gunzipSync(compressedBuffer).toString('utf8');
|
|
311
|
-
|
|
421
|
+
const value = this.deserializer(decompressed);
|
|
422
|
+
this._recordStat('hits');
|
|
423
|
+
if (this.evictionPolicy === 'lru' && this.meta[normalizedKey]) {
|
|
424
|
+
this.meta[normalizedKey].lastAccess = Date.now();
|
|
425
|
+
this.meta[normalizedKey].accessOrder = ++this._accessCounter;
|
|
426
|
+
}
|
|
427
|
+
return value;
|
|
312
428
|
} catch (error) {
|
|
313
429
|
console.warn(`[MemoryCache] Decompression failed for key '${key}':`, error.message);
|
|
314
430
|
// If decompression fails, remove corrupted entry
|
|
315
|
-
delete this.cache[
|
|
316
|
-
delete this.meta[
|
|
431
|
+
delete this.cache[normalizedKey];
|
|
432
|
+
delete this.meta[normalizedKey];
|
|
433
|
+
this._recordStat('misses');
|
|
317
434
|
return null;
|
|
318
435
|
}
|
|
319
436
|
}
|
|
320
437
|
|
|
321
|
-
|
|
322
|
-
|
|
438
|
+
try {
|
|
439
|
+
const value = typeof rawData === 'string' ? this.deserializer(rawData) : rawData;
|
|
440
|
+
this._recordStat('hits');
|
|
441
|
+
if (this.evictionPolicy === 'lru' && this.meta[normalizedKey]) {
|
|
442
|
+
this.meta[normalizedKey].lastAccess = Date.now();
|
|
443
|
+
this.meta[normalizedKey].accessOrder = ++this._accessCounter;
|
|
444
|
+
}
|
|
445
|
+
return value;
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.warn(`[MemoryCache] Deserialization failed for key '${key}':`, error.message);
|
|
448
|
+
delete this.cache[normalizedKey];
|
|
449
|
+
delete this.meta[normalizedKey];
|
|
450
|
+
this._recordStat('misses');
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
323
453
|
}
|
|
324
454
|
|
|
325
455
|
async _del(key) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
456
|
+
const normalizedKey = this._normalizeKey(key);
|
|
457
|
+
|
|
458
|
+
if (Object.prototype.hasOwnProperty.call(this.cache, normalizedKey)) {
|
|
459
|
+
const itemSize = this.meta[normalizedKey]?.compressedSize || 0;
|
|
329
460
|
this.currentMemoryBytes -= itemSize;
|
|
330
461
|
}
|
|
331
462
|
|
|
332
|
-
delete this.cache[
|
|
333
|
-
delete this.meta[
|
|
463
|
+
delete this.cache[normalizedKey];
|
|
464
|
+
delete this.meta[normalizedKey];
|
|
465
|
+
this._recordStat('deletes');
|
|
334
466
|
return true;
|
|
335
467
|
}
|
|
336
468
|
|
|
@@ -339,12 +471,17 @@ export class MemoryCache extends Cache {
|
|
|
339
471
|
this.cache = {};
|
|
340
472
|
this.meta = {};
|
|
341
473
|
this.currentMemoryBytes = 0; // Reset memory counter
|
|
474
|
+
this.evictedDueToMemory = 0;
|
|
475
|
+
if (this.enableStats) {
|
|
476
|
+
this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0, evictions: 0 };
|
|
477
|
+
}
|
|
342
478
|
return true;
|
|
343
479
|
}
|
|
344
480
|
// Remove only keys that start with the prefix
|
|
345
481
|
const removed = [];
|
|
482
|
+
const normalizedPrefix = this._normalizeKey(prefix);
|
|
346
483
|
for (const key of Object.keys(this.cache)) {
|
|
347
|
-
if (key.startsWith(
|
|
484
|
+
if (key.startsWith(normalizedPrefix)) {
|
|
348
485
|
removed.push(key);
|
|
349
486
|
// Decrement memory usage
|
|
350
487
|
const itemSize = this.meta[key]?.compressedSize || 0;
|
|
@@ -363,7 +500,24 @@ export class MemoryCache extends Cache {
|
|
|
363
500
|
}
|
|
364
501
|
|
|
365
502
|
async keys() {
|
|
366
|
-
return Object.keys(this.cache);
|
|
503
|
+
return Object.keys(this.cache).map(key => this.meta[key]?.originalKey || key);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
getStats() {
|
|
507
|
+
if (!this.enableStats) {
|
|
508
|
+
return { enabled: false };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const total = this.stats.hits + this.stats.misses;
|
|
512
|
+
const hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
...this.stats,
|
|
516
|
+
memoryUsageBytes: this.currentMemoryBytes,
|
|
517
|
+
maxMemoryBytes: this.maxMemoryBytes,
|
|
518
|
+
evictedDueToMemory: this.evictedDueToMemory,
|
|
519
|
+
hitRate
|
|
520
|
+
};
|
|
367
521
|
}
|
|
368
522
|
|
|
369
523
|
/**
|