s3db.js 7.2.1 → 7.3.1

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.
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Partition-Aware Filesystem Cache Implementation
3
+ *
4
+ * Extends FilesystemCache to provide intelligent caching for s3db.js partitions.
5
+ * Creates hierarchical directory structures that mirror partition organization.
6
+ *
7
+ * @example
8
+ * // Basic partition-aware caching
9
+ * const cache = new PartitionAwareFilesystemCache({
10
+ * directory: './cache',
11
+ * partitionStrategy: 'hierarchical',
12
+ * preloadRelated: true
13
+ * });
14
+ *
15
+ * @example
16
+ * // Advanced configuration with analytics
17
+ * const cache = new PartitionAwareFilesystemCache({
18
+ * directory: './data/cache',
19
+ * partitionStrategy: 'incremental',
20
+ * trackUsage: true,
21
+ * preloadThreshold: 10,
22
+ * maxCacheSize: '1GB'
23
+ * });
24
+ */
25
+ import path from 'path';
26
+ import fs from 'fs';
27
+ import { promisify } from 'util';
28
+ import { FilesystemCache } from './filesystem-cache.class.js';
29
+ import tryFn from '../../concerns/try-fn.js';
30
+
31
+ const mkdir = promisify(fs.mkdir);
32
+ const rmdir = promisify(fs.rmdir);
33
+ const readdir = promisify(fs.readdir);
34
+ const stat = promisify(fs.stat);
35
+ const writeFile = promisify(fs.writeFile);
36
+ const readFile = promisify(fs.readFile);
37
+
38
+ export class PartitionAwareFilesystemCache extends FilesystemCache {
39
+ constructor({
40
+ partitionStrategy = 'hierarchical', // 'hierarchical', 'flat', 'temporal'
41
+ trackUsage = true,
42
+ preloadRelated = false,
43
+ preloadThreshold = 10,
44
+ maxCacheSize = null,
45
+ usageStatsFile = 'partition-usage.json',
46
+ ...config
47
+ }) {
48
+ super(config);
49
+
50
+ this.partitionStrategy = partitionStrategy;
51
+ this.trackUsage = trackUsage;
52
+ this.preloadRelated = preloadRelated;
53
+ this.preloadThreshold = preloadThreshold;
54
+ this.maxCacheSize = maxCacheSize;
55
+ this.usageStatsFile = path.join(this.directory, usageStatsFile);
56
+
57
+ // Partition usage statistics
58
+ this.partitionUsage = new Map();
59
+ this.loadUsageStats();
60
+ }
61
+
62
+ /**
63
+ * Generate partition-aware cache key
64
+ */
65
+ _getPartitionCacheKey(resource, action, partition, partitionValues = {}, params = {}) {
66
+ const keyParts = [`resource=${resource}`, `action=${action}`];
67
+
68
+ if (partition && Object.keys(partitionValues).length > 0) {
69
+ keyParts.push(`partition=${partition}`);
70
+
71
+ // Sort fields for consistent keys
72
+ const sortedFields = Object.entries(partitionValues).sort(([a], [b]) => a.localeCompare(b));
73
+ for (const [field, value] of sortedFields) {
74
+ if (value !== null && value !== undefined) {
75
+ keyParts.push(`${field}=${value}`);
76
+ }
77
+ }
78
+ }
79
+
80
+ // Add params hash if exists
81
+ if (Object.keys(params).length > 0) {
82
+ const paramsStr = Object.entries(params)
83
+ .sort(([a], [b]) => a.localeCompare(b))
84
+ .map(([k, v]) => `${k}=${v}`)
85
+ .join('|');
86
+ keyParts.push(`params=${Buffer.from(paramsStr).toString('base64')}`);
87
+ }
88
+
89
+ return keyParts.join('/') + this.fileExtension;
90
+ }
91
+
92
+ /**
93
+ * Get directory path for partition cache
94
+ */
95
+ _getPartitionDirectory(resource, partition, partitionValues = {}) {
96
+ const basePath = path.join(this.directory, `resource=${resource}`);
97
+
98
+ if (!partition) {
99
+ return basePath;
100
+ }
101
+
102
+ if (this.partitionStrategy === 'flat') {
103
+ // Flat structure: all partitions in same level
104
+ return path.join(basePath, 'partitions');
105
+ }
106
+
107
+ if (this.partitionStrategy === 'temporal' && this._isTemporalPartition(partition, partitionValues)) {
108
+ // Temporal structure: organize by time hierarchy
109
+ return this._getTemporalDirectory(basePath, partition, partitionValues);
110
+ }
111
+
112
+ // Hierarchical structure (default)
113
+ const pathParts = [basePath, `partition=${partition}`];
114
+
115
+ const sortedFields = Object.entries(partitionValues).sort(([a], [b]) => a.localeCompare(b));
116
+ for (const [field, value] of sortedFields) {
117
+ if (value !== null && value !== undefined) {
118
+ pathParts.push(`${field}=${this._sanitizePathValue(value)}`);
119
+ }
120
+ }
121
+
122
+ return path.join(...pathParts);
123
+ }
124
+
125
+ /**
126
+ * Enhanced set method with partition awareness
127
+ */
128
+ async _set(key, data, options = {}) {
129
+ const { resource, action, partition, partitionValues, params } = options;
130
+
131
+ if (resource && partition) {
132
+ // Use partition-aware storage
133
+ const partitionKey = this._getPartitionCacheKey(resource, action, partition, partitionValues, params);
134
+ const partitionDir = this._getPartitionDirectory(resource, partition, partitionValues);
135
+
136
+ await this._ensureDirectory(partitionDir);
137
+
138
+ const filePath = path.join(partitionDir, this._sanitizeFileName(partitionKey));
139
+
140
+ // Track usage if enabled
141
+ if (this.trackUsage) {
142
+ await this._trackPartitionUsage(resource, partition, partitionValues);
143
+ }
144
+
145
+ // Store with partition metadata
146
+ const partitionData = {
147
+ data,
148
+ metadata: {
149
+ resource,
150
+ partition,
151
+ partitionValues,
152
+ timestamp: Date.now(),
153
+ ttl: this.ttl
154
+ }
155
+ };
156
+
157
+ return this._writeFileWithMetadata(filePath, partitionData);
158
+ }
159
+
160
+ // Fallback to standard set
161
+ return super._set(key, data);
162
+ }
163
+
164
+ /**
165
+ * Enhanced get method with partition awareness
166
+ */
167
+ async _get(key, options = {}) {
168
+ const { resource, action, partition, partitionValues, params } = options;
169
+
170
+ if (resource && partition) {
171
+ const partitionKey = this._getPartitionCacheKey(resource, action, partition, partitionValues, params);
172
+ const partitionDir = this._getPartitionDirectory(resource, partition, partitionValues);
173
+ const filePath = path.join(partitionDir, this._sanitizeFileName(partitionKey));
174
+
175
+ if (!await this._fileExists(filePath)) {
176
+ // Try preloading related partitions
177
+ if (this.preloadRelated) {
178
+ await this._preloadRelatedPartitions(resource, partition, partitionValues);
179
+ }
180
+ return null;
181
+ }
182
+
183
+ const result = await this._readFileWithMetadata(filePath);
184
+
185
+ if (result && this.trackUsage) {
186
+ await this._trackPartitionUsage(resource, partition, partitionValues);
187
+ }
188
+
189
+ return result?.data || null;
190
+ }
191
+
192
+ // Fallback to standard get
193
+ return super._get(key);
194
+ }
195
+
196
+ /**
197
+ * Clear cache for specific partition
198
+ */
199
+ async clearPartition(resource, partition, partitionValues = {}) {
200
+ const partitionDir = this._getPartitionDirectory(resource, partition, partitionValues);
201
+
202
+ const [ok, err] = await tryFn(async () => {
203
+ if (await this._fileExists(partitionDir)) {
204
+ await rmdir(partitionDir, { recursive: true });
205
+ }
206
+ });
207
+
208
+ if (!ok) {
209
+ console.warn(`Failed to clear partition cache: ${err.message}`);
210
+ }
211
+
212
+ // Clear from usage stats
213
+ const usageKey = this._getUsageKey(resource, partition, partitionValues);
214
+ this.partitionUsage.delete(usageKey);
215
+ await this._saveUsageStats();
216
+
217
+ return ok;
218
+ }
219
+
220
+ /**
221
+ * Clear all partitions for a resource
222
+ */
223
+ async clearResourcePartitions(resource) {
224
+ const resourceDir = path.join(this.directory, `resource=${resource}`);
225
+
226
+ const [ok, err] = await tryFn(async () => {
227
+ if (await this._fileExists(resourceDir)) {
228
+ await rmdir(resourceDir, { recursive: true });
229
+ }
230
+ });
231
+
232
+ // Clear usage stats for resource
233
+ for (const [key] of this.partitionUsage.entries()) {
234
+ if (key.startsWith(`${resource}/`)) {
235
+ this.partitionUsage.delete(key);
236
+ }
237
+ }
238
+ await this._saveUsageStats();
239
+
240
+ return ok;
241
+ }
242
+
243
+ /**
244
+ * Get partition cache statistics
245
+ */
246
+ async getPartitionStats(resource, partition = null) {
247
+ const stats = {
248
+ totalFiles: 0,
249
+ totalSize: 0,
250
+ partitions: {},
251
+ usage: {}
252
+ };
253
+
254
+ const resourceDir = path.join(this.directory, `resource=${resource}`);
255
+
256
+ if (!await this._fileExists(resourceDir)) {
257
+ return stats;
258
+ }
259
+
260
+ await this._calculateDirectoryStats(resourceDir, stats);
261
+
262
+ // Add usage statistics
263
+ for (const [key, usage] of this.partitionUsage.entries()) {
264
+ if (key.startsWith(`${resource}/`)) {
265
+ const partitionName = key.split('/')[1];
266
+ if (!partition || partitionName === partition) {
267
+ stats.usage[partitionName] = usage;
268
+ }
269
+ }
270
+ }
271
+
272
+ return stats;
273
+ }
274
+
275
+ /**
276
+ * Get cache recommendations based on usage patterns
277
+ */
278
+ async getCacheRecommendations(resource) {
279
+ const recommendations = [];
280
+ const now = Date.now();
281
+ const dayMs = 24 * 60 * 60 * 1000;
282
+
283
+ for (const [key, usage] of this.partitionUsage.entries()) {
284
+ if (key.startsWith(`${resource}/`)) {
285
+ const [, partition] = key.split('/');
286
+ const daysSinceLastAccess = (now - usage.lastAccess) / dayMs;
287
+ const accessesPerDay = usage.count / Math.max(1, daysSinceLastAccess);
288
+
289
+ let recommendation = 'keep';
290
+ let priority = usage.count;
291
+
292
+ if (daysSinceLastAccess > 30) {
293
+ recommendation = 'archive';
294
+ priority = 0;
295
+ } else if (accessesPerDay < 0.1) {
296
+ recommendation = 'reduce_ttl';
297
+ priority = 1;
298
+ } else if (accessesPerDay > 10) {
299
+ recommendation = 'preload';
300
+ priority = 100;
301
+ }
302
+
303
+ recommendations.push({
304
+ partition,
305
+ recommendation,
306
+ priority,
307
+ usage: accessesPerDay,
308
+ lastAccess: new Date(usage.lastAccess).toISOString()
309
+ });
310
+ }
311
+ }
312
+
313
+ return recommendations.sort((a, b) => b.priority - a.priority);
314
+ }
315
+
316
+ /**
317
+ * Preload frequently accessed partitions
318
+ */
319
+ async warmPartitionCache(resource, options = {}) {
320
+ const { partitions = [], maxFiles = 1000 } = options;
321
+ let warmedCount = 0;
322
+
323
+ for (const partition of partitions) {
324
+ const usageKey = `${resource}/${partition}`;
325
+ const usage = this.partitionUsage.get(usageKey);
326
+
327
+ if (usage && usage.count >= this.preloadThreshold) {
328
+ // This would integrate with the actual resource to preload data
329
+ console.log(`🔥 Warming cache for ${resource}/${partition} (${usage.count} accesses)`);
330
+ warmedCount++;
331
+ }
332
+
333
+ if (warmedCount >= maxFiles) break;
334
+ }
335
+
336
+ return warmedCount;
337
+ }
338
+
339
+ // Private helper methods
340
+
341
+ async _trackPartitionUsage(resource, partition, partitionValues) {
342
+ const usageKey = this._getUsageKey(resource, partition, partitionValues);
343
+ const current = this.partitionUsage.get(usageKey) || {
344
+ count: 0,
345
+ firstAccess: Date.now(),
346
+ lastAccess: Date.now()
347
+ };
348
+
349
+ current.count++;
350
+ current.lastAccess = Date.now();
351
+ this.partitionUsage.set(usageKey, current);
352
+
353
+ // Periodically save stats
354
+ if (current.count % 10 === 0) {
355
+ await this._saveUsageStats();
356
+ }
357
+ }
358
+
359
+ _getUsageKey(resource, partition, partitionValues) {
360
+ const valuePart = Object.entries(partitionValues)
361
+ .sort(([a], [b]) => a.localeCompare(b))
362
+ .map(([k, v]) => `${k}=${v}`)
363
+ .join('|');
364
+
365
+ return `${resource}/${partition}/${valuePart}`;
366
+ }
367
+
368
+ async _preloadRelatedPartitions(resource, partition, partitionValues) {
369
+ // This would implement intelligent preloading based on:
370
+ // - Temporal patterns (load next/previous time periods)
371
+ // - Geographic patterns (load adjacent regions)
372
+ // - Categorical patterns (load related categories)
373
+
374
+ console.log(`🎯 Preloading related partitions for ${resource}/${partition}`);
375
+
376
+ // Example: for date partitions, preload next day
377
+ if (partitionValues.timestamp || partitionValues.date) {
378
+ // Implementation would go here
379
+ }
380
+ }
381
+
382
+ _isTemporalPartition(partition, partitionValues) {
383
+ const temporalFields = ['date', 'timestamp', 'createdAt', 'updatedAt'];
384
+ return Object.keys(partitionValues).some(field =>
385
+ temporalFields.some(tf => field.toLowerCase().includes(tf))
386
+ );
387
+ }
388
+
389
+ _getTemporalDirectory(basePath, partition, partitionValues) {
390
+ // Create year/month/day hierarchy for temporal data
391
+ const dateValue = Object.values(partitionValues)[0];
392
+ if (typeof dateValue === 'string' && dateValue.match(/^\d{4}-\d{2}-\d{2}/)) {
393
+ const [year, month, day] = dateValue.split('-');
394
+ return path.join(basePath, 'temporal', year, month, day);
395
+ }
396
+
397
+ return path.join(basePath, `partition=${partition}`);
398
+ }
399
+
400
+ _sanitizePathValue(value) {
401
+ return String(value).replace(/[<>:"/\\|?*]/g, '_');
402
+ }
403
+
404
+ _sanitizeFileName(filename) {
405
+ return filename.replace(/[<>:"/\\|?*]/g, '_');
406
+ }
407
+
408
+ async _calculateDirectoryStats(dir, stats) {
409
+ const [ok, err, files] = await tryFn(() => readdir(dir));
410
+ if (!ok) return;
411
+
412
+ for (const file of files) {
413
+ const filePath = path.join(dir, file);
414
+ const [statOk, statErr, fileStat] = await tryFn(() => stat(filePath));
415
+
416
+ if (statOk) {
417
+ if (fileStat.isDirectory()) {
418
+ await this._calculateDirectoryStats(filePath, stats);
419
+ } else {
420
+ stats.totalFiles++;
421
+ stats.totalSize += fileStat.size;
422
+ }
423
+ }
424
+ }
425
+ }
426
+
427
+ async loadUsageStats() {
428
+ const [ok, err, content] = await tryFn(async () => {
429
+ const data = await readFile(this.usageStatsFile, 'utf8');
430
+ return JSON.parse(data);
431
+ });
432
+
433
+ if (ok && content) {
434
+ this.partitionUsage = new Map(Object.entries(content));
435
+ }
436
+ }
437
+
438
+ async _saveUsageStats() {
439
+ const statsObject = Object.fromEntries(this.partitionUsage);
440
+
441
+ await tryFn(async () => {
442
+ await writeFile(
443
+ this.usageStatsFile,
444
+ JSON.stringify(statsObject, null, 2),
445
+ 'utf8'
446
+ );
447
+ });
448
+ }
449
+
450
+ async _writeFileWithMetadata(filePath, data) {
451
+ const content = JSON.stringify(data);
452
+
453
+ const [ok, err] = await tryFn(async () => {
454
+ await writeFile(filePath, content, {
455
+ encoding: this.encoding,
456
+ mode: this.fileMode
457
+ });
458
+ });
459
+
460
+ if (!ok) {
461
+ throw new Error(`Failed to write cache file: ${err.message}`);
462
+ }
463
+
464
+ return true;
465
+ }
466
+
467
+ async _readFileWithMetadata(filePath) {
468
+ const [ok, err, content] = await tryFn(async () => {
469
+ return await readFile(filePath, this.encoding);
470
+ });
471
+
472
+ if (!ok || !content) return null;
473
+
474
+ try {
475
+ return JSON.parse(content);
476
+ } catch (error) {
477
+ return { data: content }; // Fallback for non-JSON data
478
+ }
479
+ }
480
+ }
@@ -4,6 +4,8 @@ import { sha256 } from "../concerns/crypto.js";
4
4
  import Plugin from "./plugin.class.js";
5
5
  import S3Cache from "./cache/s3-cache.class.js";
6
6
  import MemoryCache from "./cache/memory-cache.class.js";
7
+ import { FilesystemCache } from "./cache/filesystem-cache.class.js";
8
+ import { PartitionAwareFilesystemCache } from "./cache/partition-aware-filesystem-cache.class.js";
7
9
  import tryFn from "../concerns/try-fn.js";
8
10
 
9
11
  export class CachePlugin extends Plugin {
@@ -12,6 +14,10 @@ export class CachePlugin extends Plugin {
12
14
  this.driver = options.driver;
13
15
  this.config = {
14
16
  includePartitions: options.includePartitions !== false,
17
+ partitionStrategy: options.partitionStrategy || 'hierarchical',
18
+ partitionAware: options.partitionAware !== false,
19
+ trackUsage: options.trackUsage !== false,
20
+ preloadRelated: options.preloadRelated !== false,
15
21
  ...options
16
22
  };
17
23
  }
@@ -27,6 +33,18 @@ export class CachePlugin extends Plugin {
27
33
  this.driver = this.config.driver;
28
34
  } else if (this.config.driverType === 'memory') {
29
35
  this.driver = new MemoryCache(this.config.memoryOptions || {});
36
+ } else if (this.config.driverType === 'filesystem') {
37
+ // Use partition-aware filesystem cache if enabled
38
+ if (this.config.partitionAware) {
39
+ this.driver = new PartitionAwareFilesystemCache({
40
+ partitionStrategy: this.config.partitionStrategy,
41
+ trackUsage: this.config.trackUsage,
42
+ preloadRelated: this.config.preloadRelated,
43
+ ...this.config.filesystemOptions
44
+ });
45
+ } else {
46
+ this.driver = new FilesystemCache(this.config.filesystemOptions || {});
47
+ }
30
48
  } else {
31
49
  // Default to S3Cache, sempre passa o client do database
32
50
  this.driver = new S3Cache({ client: this.database.client, ...(this.config.s3Options || {}) });
@@ -89,6 +107,25 @@ export class CachePlugin extends Plugin {
89
107
  return this.generateCacheKey(resource, action, params, partition, partitionValues);
90
108
  };
91
109
 
110
+ // Add partition-aware methods if using PartitionAwareFilesystemCache
111
+ if (this.driver instanceof PartitionAwareFilesystemCache) {
112
+ resource.clearPartitionCache = async (partition, partitionValues = {}) => {
113
+ return await this.driver.clearPartition(resource.name, partition, partitionValues);
114
+ };
115
+
116
+ resource.getPartitionCacheStats = async (partition = null) => {
117
+ return await this.driver.getPartitionStats(resource.name, partition);
118
+ };
119
+
120
+ resource.getCacheRecommendations = async () => {
121
+ return await this.driver.getCacheRecommendations(resource.name);
122
+ };
123
+
124
+ resource.warmPartitionCache = async (partitions = [], options = {}) => {
125
+ return await this.driver.warmPartitionCache(resource.name, { partitions, ...options });
126
+ };
127
+ }
128
+
92
129
  // List of methods to cache
93
130
  const cacheMethods = [
94
131
  'count', 'listIds', 'getMany', 'getAll', 'page', 'list', 'get'
@@ -110,14 +147,50 @@ export class CachePlugin extends Plugin {
110
147
  } else if (method === 'get') {
111
148
  key = await resource.cacheKeyFor({ action: method, params: { id: ctx.args[0] } });
112
149
  }
113
- // Try cache
114
- const [ok, err, cached] = await tryFn(() => resource.cache.get(key));
115
- if (ok && cached !== null && cached !== undefined) return cached;
116
- if (!ok && err.name !== 'NoSuchKey') throw err;
117
- // Not cached, call next
118
- const result = await next();
119
- await resource.cache.set(key, result);
120
- return result;
150
+ // Try cache with partition awareness
151
+ let cached;
152
+ if (this.driver instanceof PartitionAwareFilesystemCache) {
153
+ // Extract partition info for partition-aware cache
154
+ let partition, partitionValues;
155
+ if (method === 'list' || method === 'listIds' || method === 'count' || method === 'page') {
156
+ const args = ctx.args[0] || {};
157
+ partition = args.partition;
158
+ partitionValues = args.partitionValues;
159
+ }
160
+
161
+ const [ok, err, result] = await tryFn(() => resource.cache._get(key, {
162
+ resource: resource.name,
163
+ action: method,
164
+ partition,
165
+ partitionValues
166
+ }));
167
+
168
+ if (ok && result !== null && result !== undefined) return result;
169
+ if (!ok && err.name !== 'NoSuchKey') throw err;
170
+
171
+ // Not cached, call next
172
+ const freshResult = await next();
173
+
174
+ // Store with partition context
175
+ await resource.cache._set(key, freshResult, {
176
+ resource: resource.name,
177
+ action: method,
178
+ partition,
179
+ partitionValues
180
+ });
181
+
182
+ return freshResult;
183
+ } else {
184
+ // Standard cache behavior
185
+ const [ok, err, result] = await tryFn(() => resource.cache.get(key));
186
+ if (ok && result !== null && result !== undefined) return result;
187
+ if (!ok && err.name !== 'NoSuchKey') throw err;
188
+
189
+ // Not cached, call next
190
+ const freshResult = await next();
191
+ await resource.cache.set(key, freshResult);
192
+ return freshResult;
193
+ }
121
194
  });
122
195
  }
123
196
 
@@ -240,7 +313,13 @@ export class CachePlugin extends Plugin {
240
313
 
241
314
  const { includePartitions = true } = options;
242
315
 
243
- // Warm main cache using the wrapped method (which will call the original)
316
+ // Use partition-aware warming if available
317
+ if (this.driver instanceof PartitionAwareFilesystemCache && resource.warmPartitionCache) {
318
+ const partitionNames = resource.config.partitions ? Object.keys(resource.config.partitions) : [];
319
+ return await resource.warmPartitionCache(partitionNames, options);
320
+ }
321
+
322
+ // Fallback to standard warming
244
323
  await resource.getAll();
245
324
 
246
325
  // Warm partition caches if enabled
@@ -270,6 +349,77 @@ export class CachePlugin extends Plugin {
270
349
  }
271
350
  }
272
351
  }
352
+
353
+ // Partition-specific methods
354
+ async getPartitionCacheStats(resourceName, partition = null) {
355
+ if (!(this.driver instanceof PartitionAwareFilesystemCache)) {
356
+ throw new Error('Partition cache statistics are only available with PartitionAwareFilesystemCache');
357
+ }
358
+
359
+ return await this.driver.getPartitionStats(resourceName, partition);
360
+ }
361
+
362
+ async getCacheRecommendations(resourceName) {
363
+ if (!(this.driver instanceof PartitionAwareFilesystemCache)) {
364
+ throw new Error('Cache recommendations are only available with PartitionAwareFilesystemCache');
365
+ }
366
+
367
+ return await this.driver.getCacheRecommendations(resourceName);
368
+ }
369
+
370
+ async clearPartitionCache(resourceName, partition, partitionValues = {}) {
371
+ if (!(this.driver instanceof PartitionAwareFilesystemCache)) {
372
+ throw new Error('Partition cache clearing is only available with PartitionAwareFilesystemCache');
373
+ }
374
+
375
+ return await this.driver.clearPartition(resourceName, partition, partitionValues);
376
+ }
377
+
378
+ async analyzeCacheUsage() {
379
+ if (!(this.driver instanceof PartitionAwareFilesystemCache)) {
380
+ return { message: 'Cache usage analysis is only available with PartitionAwareFilesystemCache' };
381
+ }
382
+
383
+ const analysis = {
384
+ totalResources: Object.keys(this.database.resources).length,
385
+ resourceStats: {},
386
+ recommendations: {},
387
+ summary: {
388
+ mostUsedPartitions: [],
389
+ leastUsedPartitions: [],
390
+ suggestedOptimizations: []
391
+ }
392
+ };
393
+
394
+ // Analyze each resource
395
+ for (const [resourceName, resource] of Object.entries(this.database.resources)) {
396
+ try {
397
+ analysis.resourceStats[resourceName] = await this.driver.getPartitionStats(resourceName);
398
+ analysis.recommendations[resourceName] = await this.driver.getCacheRecommendations(resourceName);
399
+ } catch (error) {
400
+ analysis.resourceStats[resourceName] = { error: error.message };
401
+ }
402
+ }
403
+
404
+ // Generate summary
405
+ const allRecommendations = Object.values(analysis.recommendations).flat();
406
+ analysis.summary.mostUsedPartitions = allRecommendations
407
+ .filter(r => r.recommendation === 'preload')
408
+ .sort((a, b) => b.priority - a.priority)
409
+ .slice(0, 5);
410
+
411
+ analysis.summary.leastUsedPartitions = allRecommendations
412
+ .filter(r => r.recommendation === 'archive')
413
+ .slice(0, 5);
414
+
415
+ analysis.summary.suggestedOptimizations = [
416
+ `Consider preloading ${analysis.summary.mostUsedPartitions.length} high-usage partitions`,
417
+ `Archive ${analysis.summary.leastUsedPartitions.length} unused partitions`,
418
+ `Monitor cache hit rates for partition efficiency`
419
+ ];
420
+
421
+ return analysis;
422
+ }
273
423
  }
274
424
 
275
425
  export default CachePlugin;
@@ -10,9 +10,9 @@ export const CONSUMER_DRIVERS = {
10
10
  };
11
11
 
12
12
  /**
13
- * Cria uma instância de consumer baseado no driver
14
- * @param {string} driver - Tipo do driver (sqs, rabbitmq, kafka...)
15
- * @param {Object} config - Configuração do consumer
13
+ * Creates a consumer instance based on the driver
14
+ * @param {string} driver - Driver type (sqs, rabbitmq, kafka...)
15
+ * @param {Object} config - Consumer configuration
16
16
  * @returns {SqsConsumer|RabbitMqConsumer|KafkaConsumer}
17
17
  */
18
18
  export function createConsumer(driver, config) {