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.
Files changed (193) hide show
  1. package/README.md +139 -43
  2. package/dist/s3db.cjs +72425 -38970
  3. package/dist/s3db.cjs.map +1 -1
  4. package/dist/s3db.es.js +72177 -38764
  5. package/dist/s3db.es.js.map +1 -1
  6. package/mcp/lib/base-handler.js +157 -0
  7. package/mcp/lib/handlers/connection-handler.js +280 -0
  8. package/mcp/lib/handlers/query-handler.js +533 -0
  9. package/mcp/lib/handlers/resource-handler.js +428 -0
  10. package/mcp/lib/tool-registry.js +336 -0
  11. package/mcp/lib/tools/connection-tools.js +161 -0
  12. package/mcp/lib/tools/query-tools.js +267 -0
  13. package/mcp/lib/tools/resource-tools.js +404 -0
  14. package/package.json +94 -49
  15. package/src/clients/memory-client.class.js +346 -191
  16. package/src/clients/memory-storage.class.js +300 -84
  17. package/src/clients/s3-client.class.js +7 -6
  18. package/src/concerns/geo-encoding.js +19 -2
  19. package/src/concerns/ip.js +59 -9
  20. package/src/concerns/money.js +8 -1
  21. package/src/concerns/password-hashing.js +49 -8
  22. package/src/concerns/plugin-storage.js +186 -18
  23. package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
  24. package/src/database.class.js +139 -29
  25. package/src/errors.js +332 -42
  26. package/src/plugins/api/auth/oidc-auth.js +66 -17
  27. package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
  28. package/src/plugins/api/auth/strategies/factory.class.js +63 -0
  29. package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
  30. package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
  31. package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
  32. package/src/plugins/api/concerns/failban-manager.js +106 -57
  33. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  34. package/src/plugins/api/concerns/route-context.js +601 -0
  35. package/src/plugins/api/concerns/state-machine.js +288 -0
  36. package/src/plugins/api/index.js +180 -41
  37. package/src/plugins/api/routes/auth-routes.js +198 -30
  38. package/src/plugins/api/routes/resource-routes.js +19 -4
  39. package/src/plugins/api/server/health-manager.class.js +163 -0
  40. package/src/plugins/api/server/middleware-chain.class.js +310 -0
  41. package/src/plugins/api/server/router.class.js +472 -0
  42. package/src/plugins/api/server.js +280 -1303
  43. package/src/plugins/api/utils/custom-routes.js +17 -5
  44. package/src/plugins/api/utils/guards.js +76 -17
  45. package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
  46. package/src/plugins/api/utils/openapi-generator.js +7 -6
  47. package/src/plugins/api/utils/template-engine.js +77 -3
  48. package/src/plugins/audit.plugin.js +30 -8
  49. package/src/plugins/backup.plugin.js +110 -14
  50. package/src/plugins/cache/cache.class.js +22 -5
  51. package/src/plugins/cache/filesystem-cache.class.js +116 -19
  52. package/src/plugins/cache/memory-cache.class.js +211 -57
  53. package/src/plugins/cache/multi-tier-cache.class.js +371 -0
  54. package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
  55. package/src/plugins/cache/redis-cache.class.js +552 -0
  56. package/src/plugins/cache/s3-cache.class.js +17 -8
  57. package/src/plugins/cache.plugin.js +176 -61
  58. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
  59. package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
  60. package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
  61. package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
  62. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
  63. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
  64. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
  65. package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
  66. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
  67. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
  68. package/src/plugins/cloud-inventory/index.js +29 -8
  69. package/src/plugins/cloud-inventory/registry.js +64 -42
  70. package/src/plugins/cloud-inventory.plugin.js +240 -138
  71. package/src/plugins/concerns/plugin-dependencies.js +54 -0
  72. package/src/plugins/concerns/resource-names.js +100 -0
  73. package/src/plugins/consumers/index.js +10 -2
  74. package/src/plugins/consumers/sqs-consumer.js +12 -2
  75. package/src/plugins/cookie-farm-suite.plugin.js +278 -0
  76. package/src/plugins/cookie-farm.errors.js +73 -0
  77. package/src/plugins/cookie-farm.plugin.js +869 -0
  78. package/src/plugins/costs.plugin.js +7 -1
  79. package/src/plugins/eventual-consistency/analytics.js +94 -19
  80. package/src/plugins/eventual-consistency/config.js +15 -7
  81. package/src/plugins/eventual-consistency/consolidation.js +29 -11
  82. package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
  83. package/src/plugins/eventual-consistency/helpers.js +39 -14
  84. package/src/plugins/eventual-consistency/install.js +21 -2
  85. package/src/plugins/eventual-consistency/utils.js +32 -10
  86. package/src/plugins/fulltext.plugin.js +38 -11
  87. package/src/plugins/geo.plugin.js +61 -9
  88. package/src/plugins/identity/concerns/config.js +61 -0
  89. package/src/plugins/identity/concerns/mfa-manager.js +15 -2
  90. package/src/plugins/identity/concerns/rate-limit.js +124 -0
  91. package/src/plugins/identity/concerns/resource-schemas.js +9 -1
  92. package/src/plugins/identity/concerns/token-generator.js +29 -4
  93. package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
  94. package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
  95. package/src/plugins/identity/drivers/index.js +18 -0
  96. package/src/plugins/identity/drivers/password-driver.js +122 -0
  97. package/src/plugins/identity/email-service.js +17 -2
  98. package/src/plugins/identity/index.js +413 -69
  99. package/src/plugins/identity/oauth2-server.js +413 -30
  100. package/src/plugins/identity/oidc-discovery.js +16 -8
  101. package/src/plugins/identity/rsa-keys.js +115 -35
  102. package/src/plugins/identity/server.js +166 -45
  103. package/src/plugins/identity/session-manager.js +53 -7
  104. package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
  105. package/src/plugins/identity/ui/routes.js +363 -255
  106. package/src/plugins/importer/index.js +153 -20
  107. package/src/plugins/index.js +9 -2
  108. package/src/plugins/kubernetes-inventory/index.js +6 -0
  109. package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
  110. package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
  111. package/src/plugins/kubernetes-inventory.plugin.js +980 -0
  112. package/src/plugins/metrics.plugin.js +64 -16
  113. package/src/plugins/ml/base-model.class.js +25 -15
  114. package/src/plugins/ml/regression-model.class.js +1 -1
  115. package/src/plugins/ml.errors.js +57 -25
  116. package/src/plugins/ml.plugin.js +28 -4
  117. package/src/plugins/namespace.js +210 -0
  118. package/src/plugins/plugin.class.js +180 -8
  119. package/src/plugins/puppeteer/console-monitor.js +729 -0
  120. package/src/plugins/puppeteer/cookie-manager.js +492 -0
  121. package/src/plugins/puppeteer/network-monitor.js +816 -0
  122. package/src/plugins/puppeteer/performance-manager.js +746 -0
  123. package/src/plugins/puppeteer/proxy-manager.js +478 -0
  124. package/src/plugins/puppeteer/stealth-manager.js +556 -0
  125. package/src/plugins/puppeteer.errors.js +81 -0
  126. package/src/plugins/puppeteer.plugin.js +1327 -0
  127. package/src/plugins/queue-consumer.plugin.js +69 -14
  128. package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
  129. package/src/plugins/recon/concerns/command-runner.js +148 -0
  130. package/src/plugins/recon/concerns/diff-detector.js +372 -0
  131. package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
  132. package/src/plugins/recon/concerns/process-manager.js +338 -0
  133. package/src/plugins/recon/concerns/report-generator.js +478 -0
  134. package/src/plugins/recon/concerns/security-analyzer.js +571 -0
  135. package/src/plugins/recon/concerns/target-normalizer.js +68 -0
  136. package/src/plugins/recon/config/defaults.js +321 -0
  137. package/src/plugins/recon/config/resources.js +370 -0
  138. package/src/plugins/recon/index.js +778 -0
  139. package/src/plugins/recon/managers/dependency-manager.js +174 -0
  140. package/src/plugins/recon/managers/scheduler-manager.js +179 -0
  141. package/src/plugins/recon/managers/storage-manager.js +745 -0
  142. package/src/plugins/recon/managers/target-manager.js +274 -0
  143. package/src/plugins/recon/stages/asn-stage.js +314 -0
  144. package/src/plugins/recon/stages/certificate-stage.js +84 -0
  145. package/src/plugins/recon/stages/dns-stage.js +107 -0
  146. package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
  147. package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
  148. package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
  149. package/src/plugins/recon/stages/http-stage.js +89 -0
  150. package/src/plugins/recon/stages/latency-stage.js +148 -0
  151. package/src/plugins/recon/stages/massdns-stage.js +302 -0
  152. package/src/plugins/recon/stages/osint-stage.js +1373 -0
  153. package/src/plugins/recon/stages/ports-stage.js +169 -0
  154. package/src/plugins/recon/stages/screenshot-stage.js +94 -0
  155. package/src/plugins/recon/stages/secrets-stage.js +514 -0
  156. package/src/plugins/recon/stages/subdomains-stage.js +295 -0
  157. package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
  158. package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
  159. package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
  160. package/src/plugins/recon/stages/whois-stage.js +349 -0
  161. package/src/plugins/recon.plugin.js +75 -0
  162. package/src/plugins/recon.plugin.js.backup +2635 -0
  163. package/src/plugins/relation.errors.js +87 -14
  164. package/src/plugins/replicator.plugin.js +514 -137
  165. package/src/plugins/replicators/base-replicator.class.js +89 -1
  166. package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
  167. package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
  168. package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
  169. package/src/plugins/replicators/mysql-replicator.class.js +52 -17
  170. package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
  171. package/src/plugins/replicators/postgres-replicator.class.js +62 -27
  172. package/src/plugins/replicators/s3db-replicator.class.js +25 -18
  173. package/src/plugins/replicators/schema-sync.helper.js +3 -3
  174. package/src/plugins/replicators/sqs-replicator.class.js +8 -2
  175. package/src/plugins/replicators/turso-replicator.class.js +23 -3
  176. package/src/plugins/replicators/webhook-replicator.class.js +42 -4
  177. package/src/plugins/s3-queue.plugin.js +464 -65
  178. package/src/plugins/scheduler.plugin.js +20 -6
  179. package/src/plugins/state-machine.plugin.js +40 -9
  180. package/src/plugins/tfstate/README.md +126 -126
  181. package/src/plugins/tfstate/base-driver.js +28 -4
  182. package/src/plugins/tfstate/errors.js +65 -10
  183. package/src/plugins/tfstate/filesystem-driver.js +52 -8
  184. package/src/plugins/tfstate/index.js +163 -90
  185. package/src/plugins/tfstate/s3-driver.js +64 -6
  186. package/src/plugins/ttl.plugin.js +72 -17
  187. package/src/plugins/vector/distances.js +18 -12
  188. package/src/plugins/vector/kmeans.js +26 -4
  189. package/src/resource.class.js +115 -19
  190. package/src/testing/factory.class.js +20 -3
  191. package/src/testing/seeder.class.js +7 -1
  192. package/src/clients/memory-client.md +0 -917
  193. 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 Error(
152
- '[MemoryCache] Cannot use both maxMemoryBytes and maxMemoryPercent. ' +
153
- 'Choose one: maxMemoryBytes (absolute) or maxMemoryPercent (0...1 fraction).'
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 Error(
161
- '[MemoryCache] maxMemoryPercent must be between 0 and 1 (e.g., 0.1 for 10%). ' +
162
- `Received: ${config.maxMemoryPercent}`
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 = data;
280
+ let finalData = serialized;
196
281
  let compressed = false;
197
282
  let originalSize = 0;
198
283
  let compressedSize = 0;
199
284
 
200
- // Calculate size first (needed for both compression and memory limit checks)
201
- const serialized = JSON.stringify(data);
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, key)) {
236
- const oldSize = this.meta[key]?.compressedSize || 0;
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
- // Remove the oldest item
244
- const oldestKey = Object.entries(this.meta)
245
- .sort((a, b) => a[1].ts - b[1].ts)[0]?.[0];
246
- if (oldestKey) {
247
- const evictedSize = this.meta[oldestKey]?.compressedSize || 0;
248
- delete this.cache[oldestKey];
249
- delete this.meta[oldestKey];
250
- this.currentMemoryBytes -= evictedSize;
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 (original logic)
259
- if (this.maxSize > 0 && Object.keys(this.cache).length >= this.maxSize) {
260
- // Remove o item mais antigo
261
- const oldestKey = Object.entries(this.meta)
262
- .sort((a, b) => a[1].ts - b[1].ts)[0]?.[0];
263
- if (oldestKey) {
264
- const evictedSize = this.meta[oldestKey]?.compressedSize || 0;
265
- delete this.cache[oldestKey];
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[key] = finalData;
273
- this.meta[key] = {
274
- ts: Date.now(),
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
- if (!Object.prototype.hasOwnProperty.call(this.cache, key)) return null;
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[key];
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[key];
298
- delete this.meta[key];
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[key];
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
- return JSON.parse(decompressed);
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[key];
316
- delete this.meta[key];
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
- // Return uncompressed data
322
- return rawData;
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
- // Decrement memory usage
327
- if (Object.prototype.hasOwnProperty.call(this.cache, key)) {
328
- const itemSize = this.meta[key]?.compressedSize || 0;
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[key];
333
- delete this.meta[key];
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(prefix)) {
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
  /**